Compare commits
1843 Commits
android-v1
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
8059009ff3 | ||
|
32865f065c | ||
|
e03ef78049 | ||
|
45a820bb35 | ||
|
3c26159b79 | ||
|
cf67e0d4af | ||
|
1b2767167d | ||
|
f07bb5c275 | ||
|
0e2cc418e2 | ||
|
0340456d55 | ||
|
7aea2cec69 | ||
|
fa83107840 | ||
|
bb0bf46f81 | ||
|
694c3fed2d | ||
|
772e39b710 | ||
|
05e0a2c29d | ||
|
78e0efb95f | ||
|
a5f749cfd2 | ||
|
3d6c932e1b | ||
|
4488a1b95f | ||
|
e2808a90c6 | ||
|
755a972e02 | ||
|
8b1de22049 | ||
|
a9735123b7 | ||
|
5ccafa2838 | ||
|
e2926a4f82 | ||
|
09df315639 | ||
|
5a9b3b6c7c | ||
|
6da6f35ddd | ||
|
dcb5590842 | ||
|
5135c8a782 | ||
|
1b2f4fb036 | ||
|
76a4a445f0 | ||
|
20abb125a5 | ||
|
be9e50b4a1 | ||
|
02bfcf577d | ||
|
038efa10f2 | ||
|
dfa692569b | ||
|
9abc6a2e44 | ||
|
11f23f4e00 | ||
|
6a7d40d171 | ||
|
bf5601429e | ||
|
73ae8aaf2f | ||
|
7eb7bd98f3 | ||
|
10e22654ea | ||
|
ccfc80ad04 | ||
|
5e95278084 | ||
|
d69ba6bc75 | ||
|
d28fbe2d3b | ||
|
415e7b84da | ||
|
ac4986b620 | ||
|
9a4f4cbb65 | ||
|
8e32957111 | ||
|
91aa3703d4 | ||
|
a889762056 | ||
|
6478d6c9c9 | ||
|
7a681d0a4a | ||
|
83b6eba8bd | ||
|
2766ded5f6 | ||
|
ca0d966ed9 | ||
|
386c583b0e | ||
|
b3d34ad7e9 | ||
|
ba5c636dda | ||
|
ea16f6e0b1 | ||
|
0dd0dc5489 | ||
|
f3ab21ff43 | ||
|
5ac6b46efd | ||
|
6548f30a4b | ||
|
849d7983f6 | ||
|
e32e4423db | ||
|
7f5bf131a8 | ||
|
87a639df2b | ||
|
bdd8eab87e | ||
|
b9e5c8a387 | ||
|
d646a2dd01 | ||
|
71a3a0176e | ||
|
a363d119cf | ||
|
ff08bdbc0b | ||
|
71efff6827 | ||
|
7595fe4a8c | ||
|
7697e75466 | ||
|
b8fbaa2029 | ||
|
38bc750ecf | ||
|
6cfacb1a48 | ||
|
0b9078d034 | ||
|
86dc72b204 | ||
|
64b7bc3d62 | ||
|
086f9e1123 | ||
|
4fe70fe8ee | ||
|
95a1f40404 | ||
|
88f04509ee | ||
|
7eebd544d6 | ||
|
e369a8decf | ||
|
ad8054ba4b | ||
|
b47cb4e29a | ||
|
8c42ddf6c3 | ||
|
f7fcabbf41 | ||
|
38a51070fc | ||
|
44fa099a77 | ||
|
af6f3999df | ||
|
6fbeb35951 | ||
|
b2eadffde0 | ||
|
200ba2775f | ||
|
39ba021a79 | ||
|
2c6b291b9b | ||
|
770846be2e | ||
|
af4aa01b75 | ||
|
1bf2bec805 | ||
|
3a41ac9be0 | ||
|
f2c9cdd7f1 | ||
|
058f418cc7 | ||
|
5fa84b0dfb | ||
|
ec8ec3e38d | ||
|
0e6190b42b | ||
|
fcfee36c8c | ||
|
1d3d3b99bb | ||
|
2f80bf9647 | ||
|
675a4c795f | ||
|
ed3361df57 | ||
|
87396572e4 | ||
|
143b610291 | ||
|
c952c4591f | ||
|
e418701e68 | ||
|
6fc0ee3062 | ||
|
7b42d7d2c8 | ||
|
eb083ae925 | ||
|
905e65365f | ||
|
a0c04c0e6a | ||
|
635baa5b6f | ||
|
7591b614c5 | ||
|
5b5ec682c0 | ||
|
b0a80ddf65 | ||
|
893531f8c7 | ||
|
3d498e7a75 | ||
|
45ad201132 | ||
|
6e64b950ca | ||
|
6d4e67769c | ||
|
3aa0394062 | ||
|
b65767a43c | ||
|
3a9817d11e | ||
|
6a42ef50ec | ||
|
35b6b3fc46 | ||
|
f5515e3496 | ||
|
fd509bb4af | ||
|
b21c0f5d69 | ||
|
1033b3626f | ||
|
f407c8d756 | ||
|
6436dff94b | ||
|
3f7b4e10b6 | ||
|
36168a9a5d | ||
|
118540c733 | ||
|
cd5d412c69 | ||
|
e29fb3eb66 | ||
|
8ff1668c8f | ||
|
14fc73b388 | ||
|
f34330f101 | ||
|
2ba50321d4 | ||
|
38177c7e54 | ||
|
687e308a73 | ||
|
490db0db62 | ||
|
feb5f17479 | ||
|
fbb3543818 | ||
|
30d0dfb424 | ||
|
7239a2013c | ||
|
2361c5a5e7 | ||
|
38e8a881d5 | ||
|
d45d1b4225 | ||
|
e9c88dfdc4 | ||
|
fbb0ac5892 | ||
|
7a902bbd25 | ||
|
c75618eb8f | ||
|
74ee629266 | ||
|
8ecc58e1bf | ||
|
71078637db | ||
|
5460a977b1 | ||
|
a0dd0702fb | ||
|
3e48992eb4 | ||
|
bdb31f2890 | ||
|
0255546ae1 | ||
|
d066350eea | ||
|
a1e3260309 | ||
|
be1f57a8a6 | ||
|
ca4dfe0f0f | ||
|
fa69957d3f | ||
|
f7203ed7e2 | ||
|
331858bd4f | ||
|
4d2c9523a3 | ||
|
4d9d84a8f3 | ||
|
ec1089870f | ||
|
dbedefc021 | ||
|
f6b0da3f5e | ||
|
85bf89fd97 | ||
|
c2a80b12f0 | ||
|
981c97cca5 | ||
|
091cbc5355 | ||
|
e5a8114887 | ||
|
4779fc6f43 | ||
|
86e7daaec4 | ||
|
cab73a26e7 | ||
|
554ddb3b51 | ||
|
3b22bdb8ae | ||
|
5fdd07679e | ||
|
69f75a1520 | ||
|
f9b7acb8b1 | ||
|
91f700ad54 | ||
|
966aca7753 | ||
|
4de8816ed5 | ||
|
bea68a1056 | ||
|
6fea7116b6 | ||
|
2955914ca5 | ||
|
fd150b5b9d | ||
|
334ffad196 | ||
|
a796a9d179 | ||
|
917dcea28a | ||
|
c901228dc5 | ||
|
da21580785 | ||
|
4d92187327 | ||
|
207d433fb3 | ||
|
ffc311d7bd | ||
|
a1e8e71359 | ||
|
7942e74dc6 | ||
|
c4e21c2b6a | ||
|
0a06aa6f9f | ||
|
f985cfa25c | ||
|
6e143aef5c | ||
|
bf16aa6192 | ||
|
d96c58d192 | ||
|
e7e0264411 | ||
|
430a11282b | ||
|
9957b2798c | ||
|
2c5b0010bf | ||
|
1e3c6ed98c | ||
|
484f290eb0 | ||
|
06ad539941 | ||
|
5b84e80ac4 | ||
|
ca0f349348 | ||
|
d79089aea3 | ||
|
03611ad5ca | ||
|
c78c1cd3cf | ||
|
55afa7b5b7 | ||
|
a6c407b62b | ||
|
21897a3cd4 | ||
|
5796dd2098 | ||
|
d050071437 | ||
|
eaf8510f49 | ||
|
6ee2595dce | ||
|
0ecf2d6d9a | ||
|
50fd075168 | ||
|
6fa76bb83a | ||
|
b175c1fc94 | ||
|
b461625518 | ||
|
3819897ba1 | ||
|
6a031857ba | ||
|
0e57b7eb46 | ||
|
e21a0ba5b7 | ||
|
78f731e616 | ||
|
f6688a65ae | ||
|
035b9c6d1a | ||
|
266ff244d9 | ||
|
de1bfa5c34 | ||
|
478b8f00d8 | ||
|
860d2fd7f5 | ||
|
6ce091f4d8 | ||
|
ce595ac5e4 | ||
|
267436a00d | ||
|
97e0f4258a | ||
|
faa6ccc150 | ||
|
349cade946 | ||
|
0200aa92de | ||
|
60ed2cbee5 | ||
|
0e7b2f36c8 | ||
|
4083221b21 | ||
|
d55c511b4a | ||
|
7863e1dffe | ||
|
7cfdf778de | ||
|
075e55c077 | ||
|
dc818e8a0c | ||
|
caa58dd913 | ||
|
c84c3cd026 | ||
|
bda5ac9fb5 | ||
|
f928f645e5 | ||
|
53d7e906d4 | ||
|
e670b5d03f | ||
|
2a7d555859 | ||
|
e6675f500c | ||
|
c3f20d3ebc | ||
|
ff257060d1 | ||
|
68cde202a4 | ||
|
1a7a87e170 | ||
|
2990642923 | ||
|
0818de036e | ||
|
122bc29035 | ||
|
861cf8a1b2 | ||
|
7d6959e9e4 | ||
|
85091052e7 | ||
|
f46ad5bfda | ||
|
29f7937fc2 | ||
|
7fae9fda10 | ||
|
a37961dccc | ||
|
55155646aa | ||
|
7bffe86439 | ||
|
86136e0c6c | ||
|
21ae447d9c | ||
|
ad211b4b4e | ||
|
d6218f35fe | ||
|
7af0dcd19a | ||
|
e1a52c5606 | ||
|
468c345527 | ||
|
041bdc08a2 | ||
|
7535f1a8c6 | ||
|
0b24433db3 | ||
|
f7de0c5ffd | ||
|
62c48b9a46 | ||
|
3fafda9684 | ||
|
2cf1cda128 | ||
|
fedee9499b | ||
|
7c6c7f34ba | ||
|
0d65edd0e8 | ||
|
d57520c66a | ||
|
fa28ae1433 | ||
|
0acf9823e5 | ||
|
799ad5f1da | ||
|
9f6b3ccf40 | ||
|
df714c357d | ||
|
de5fdc84f8 | ||
|
b5b228af15 | ||
|
7a85628f46 | ||
|
075e76fc4b | ||
|
172a98fed4 | ||
|
409bdd7b78 | ||
|
d012c689e1 | ||
|
89ec0629e6 | ||
|
b3475ae195 | ||
|
a72ab67473 | ||
|
90fbfec914 | ||
|
49ef023a90 | ||
|
64bfd74f18 | ||
|
655e35056e | ||
|
a13ba63ab8 | ||
|
e2e00d4c87 | ||
|
159dc44f6c | ||
|
8fe2091926 | ||
|
c362c38dc0 | ||
|
316a52bbc2 | ||
|
0de9f6f944 | ||
|
3ba021fdd9 | ||
|
04c6579f2c | ||
|
bc7bd456a7 | ||
|
6d7511efbb | ||
|
a0fb99d78f | ||
|
685a52c2c5 | ||
|
fc77419ca1 | ||
|
bab3a12e92 | ||
|
21f0b90f48 | ||
|
83682ab513 | ||
|
7b987b5a8f | ||
|
cb5aa425c8 | ||
|
dd222381dd | ||
|
d7210811f6 | ||
|
3a43cfeebf | ||
|
31e33c6628 | ||
|
875f8d6997 | ||
|
aaaf27af6e | ||
|
43624ffa75 | ||
|
996b6623f1 | ||
|
613041b806 | ||
|
ff1d01a864 | ||
|
bcbbe10bf8 | ||
|
3de0abfc84 | ||
|
133fd03469 | ||
|
4f97c5c017 | ||
|
2d8fbac58c | ||
|
95f7ac4a4a | ||
|
6a56a6ccf0 | ||
|
1eb8df9fa6 | ||
|
485b4baebb | ||
|
5590d887c9 | ||
|
d00bfa997e | ||
|
1a8590e9b9 | ||
|
5a978977df | ||
|
5d763c7e6c | ||
|
050b089e72 | ||
|
733ea4027c | ||
|
10500c78b1 | ||
|
0040cc02a2 | ||
|
74afd20f0c | ||
|
dc9bde2184 | ||
|
5243ea7eb2 | ||
|
7d93492658 | ||
|
c6b56345f5 | ||
|
8a6fe20a69 | ||
|
6bcbedd6a4 | ||
|
4c935b78f9 | ||
|
94cddda6d0 | ||
|
1924ea062c | ||
|
07e88b2eeb | ||
|
e4a08c29d7 | ||
|
d60afcaabe | ||
|
1a091460ca | ||
|
8ebaa7f6eb | ||
|
e2a64e21a2 | ||
|
78ddd22f09 | ||
|
c546b7076a | ||
|
0e2bb5d784 | ||
|
5c069c38f5 | ||
|
451b9c0ae9 | ||
|
047897621a | ||
|
52e5cec585 | ||
|
bc98b65efa | ||
|
9250e77862 | ||
|
cd69e71945 | ||
|
e705e6e990 | ||
|
4638f11c5e | ||
|
9de7c15e93 | ||
|
61736546b4 | ||
|
82b6dd23a7 | ||
|
64427f0160 | ||
|
cbf47cb9ee | ||
|
dba3e4202d | ||
|
173cd6de4d | ||
|
3d333bd8f2 | ||
|
a82f8c7dd0 | ||
|
cde079e44e | ||
|
f8d20b61ea | ||
|
d279435502 | ||
|
eb2065128e | ||
|
10e81aa476 | ||
|
3e808f05fd | ||
|
e57bfad9b1 | ||
|
5f344f07d4 | ||
|
e1b7b64e1b | ||
|
ed3970be81 | ||
|
c27861d40f | ||
|
565dfba8c9 | ||
|
553a26eb63 | ||
|
9c85bc2cd1 | ||
|
e96bc9c48a | ||
|
0d036d8183 | ||
|
e5f2a7f2f5 | ||
|
016ce3dd61 | ||
|
afb375955e | ||
|
b702b0b40c | ||
|
91ecab51c5 | ||
|
7628506926 | ||
|
4e7f7c0c9c | ||
|
863f5bcf18 | ||
|
dccd489fcc | ||
|
333c3f6369 | ||
|
808413d0bf | ||
|
440be3d920 | ||
|
9b27a4f601 | ||
|
6e36ca32b4 | ||
|
85a9c303f2 | ||
|
7e9972d99f | ||
|
771975cd35 | ||
|
356f8e580b | ||
|
68268cb35d | ||
|
8e58ed12af | ||
|
beb428b246 | ||
|
4d81caff0b | ||
|
78372c9bac | ||
|
a4db1bc671 | ||
|
8ea1c373ed | ||
|
8ef27dfcdc | ||
|
23e43c7bc1 | ||
|
edb8f4c79f | ||
|
e115fa4bb3 | ||
|
52a2daddbf | ||
|
c400142996 | ||
|
219171a18c | ||
|
d7d573d9dd | ||
|
c4b17f8919 | ||
|
da2f4b96c7 | ||
|
08af9de190 | ||
|
9e2982992a | ||
|
c03ac5c5f1 | ||
|
5934f2f08e | ||
|
f136f40fdc | ||
|
d213e4ab57 | ||
|
782aae4ddf | ||
|
4f47bd7bcd | ||
|
4f76946140 | ||
|
aa60923cbd | ||
|
7670ce32b1 | ||
|
0b98632336 | ||
|
49c998de83 | ||
|
da69d6b2c9 | ||
|
a757aefce0 | ||
|
b2129cb8c4 | ||
|
27f14c175f | ||
|
aad49c520b | ||
|
af794a16d6 | ||
|
ca7266cd69 | ||
|
fb758afc81 | ||
|
b806f0da49 | ||
|
4e3b1f3e13 | ||
|
475467c41c | ||
|
28e5039873 | ||
|
1efc6e6151 | ||
|
e280a02643 | ||
|
788dc42684 | ||
|
01f2759a62 | ||
|
9419e3af9c | ||
|
6d220005cc | ||
|
6d68e61bbd | ||
|
29582623b0 | ||
|
4571e7853a | ||
|
d6e59c5238 | ||
|
6335cbedb8 | ||
|
f3344ce05d | ||
|
155d38d24a | ||
|
412f6d8316 | ||
|
d0f3ed80e0 | ||
|
b86f3b74bd | ||
|
60054d1d8b | ||
|
c40c6428d7 | ||
|
e708ecccee | ||
|
04f991d3bf | ||
|
d5d7368ba0 | ||
|
abff929d4e | ||
|
49edc82594 | ||
|
7dd7d0ec17 | ||
|
61aaf64f95 | ||
|
da35785951 | ||
|
e394034678 | ||
|
edf002ab32 | ||
|
50f2076981 | ||
|
a9ae78bcde | ||
|
0d7f9a2ab3 | ||
|
a5ee120281 | ||
|
12ebf44e22 | ||
|
220f5d0967 | ||
|
4ef05272c4 | ||
|
c3262aa5f8 | ||
|
42119c8f42 | ||
|
27cce03968 | ||
|
776aba1e49 | ||
|
9356841cfc | ||
|
7fc233e808 | ||
|
7b2eac3abd | ||
|
93323deea5 | ||
|
e8fa399e9e | ||
|
a974eb5d9f | ||
|
6a3f04274d | ||
|
f8e1395087 | ||
|
82b5af51e5 | ||
|
cf40c14a86 | ||
|
be2b2b7836 | ||
|
0cebae8032 | ||
|
6ebc77cbba | ||
|
542a5e88b7 | ||
|
72b36522e8 | ||
|
252d937405 | ||
|
a73b0309b9 | ||
|
c22283e799 | ||
|
1e51ab4a59 | ||
|
0c2f2667d3 | ||
|
496c9ddb91 | ||
|
5ad0b2eed9 | ||
|
577d62e783 | ||
|
dcb73c9916 | ||
|
6b2910c3c7 | ||
|
db04906416 | ||
|
54fceeb07d | ||
|
fa32678645 | ||
|
ee1df1a396 | ||
|
729be8767c | ||
|
8471f0d86d | ||
|
390b818d71 | ||
|
1a1c190ea3 | ||
|
40d82b80f1 | ||
|
7647ecbbc7 | ||
|
b2a5cf9dd0 | ||
|
cbf3ab2ec2 | ||
|
c4a37ff0ba | ||
|
bdc7ea4346 | ||
|
d4c4b9b10a | ||
|
4b9105edff | ||
|
c0980a5a9e | ||
|
272055fc1d | ||
|
cbb1851b12 | ||
|
45d758d52e | ||
|
49936ef095 | ||
|
986d4be601 | ||
|
b6ad9719ad | ||
|
96a1546da1 | ||
|
6884dd2b9e | ||
|
9c027e59c4 | ||
|
9e16ff3644 | ||
|
4000cb5d1c | ||
|
1030b412ff | ||
|
54f0fbcf6b | ||
|
1602182085 | ||
|
20bb1238c5 | ||
|
68fbe8125e | ||
|
23e6e6e69d | ||
|
ade5af2559 | ||
|
0a993dc012 | ||
|
4baa46507f | ||
|
0d8878abd3 | ||
|
f962084591 | ||
|
921b45286b | ||
|
301bfed05e | ||
|
62e7d6fa86 | ||
|
7e34cd4452 | ||
|
b35cb9a7ab | ||
|
18b836525c | ||
|
e34e49b88d | ||
|
5bf879c2d9 | ||
|
61d6309c0e | ||
|
17c9c0f9ef | ||
|
9bd62fd3d4 | ||
|
de73d4baa7 | ||
|
379ff5163b | ||
|
d9538ccb08 | ||
|
9289dbdf77 | ||
|
5719ae495a | ||
|
a89e3b7924 | ||
|
e576d09712 | ||
|
43600a7824 | ||
|
921f01d9dc | ||
|
dec5668582 | ||
|
e30bc12354 | ||
|
fca4fa666d | ||
|
59478160c8 | ||
|
8110fe89ef | ||
|
c7ed1b5eae | ||
|
25951e7097 | ||
|
687b9d1bef | ||
|
630e77b9eb | ||
|
68ff2e17b3 | ||
|
945d83608a | ||
|
833d473268 | ||
|
2256b0c5ec | ||
|
677aa7d59b | ||
|
4363005e92 | ||
|
2e3ef618db | ||
|
1adbbd14c6 | ||
|
4dfd7db729 | ||
|
e70562a102 | ||
|
a0e5947ba4 | ||
|
e841ea8a91 | ||
|
770a435029 | ||
|
49b56e84a7 | ||
|
ff1a6fdbbd | ||
|
2168090b96 | ||
|
33f7b680bc | ||
|
0957298cb8 | ||
|
08f2f982cf | ||
|
3376fbfa55 | ||
|
4a31e5fe73 | ||
|
baacec5ba6 | ||
|
95188b71b8 | ||
|
cf57be6e98 | ||
|
b691092d7a | ||
|
03e60fc028 | ||
|
2e25ec318f | ||
|
7236e5e9ae | ||
|
6f7dd51a98 | ||
|
db1dab9293 | ||
|
06f1b9e4d7 | ||
|
8f958ac931 | ||
|
eae63bfb79 | ||
|
8adfc81c30 | ||
|
0c516443e3 | ||
|
ad9bc0bf63 | ||
|
b0596670a6 | ||
|
998011ff43 | ||
|
081e1c5b62 | ||
|
edfd2c4d54 | ||
|
9d65a3a34c | ||
|
1a86cbdb9d | ||
|
849cb4456c | ||
|
1736717f2e | ||
|
50b75e1e63 | ||
|
179005dd6c | ||
|
6b3fe6b2cb | ||
|
c34872bb26 | ||
|
4845a21287 | ||
|
ddd513fe09 | ||
|
4ce118d459 | ||
|
99da184ba5 | ||
|
e2e4e62c4f | ||
|
229dd7a6dd | ||
|
1e0c4cc5cd | ||
|
b40ccc7a15 | ||
|
7d6b7e588c | ||
|
22cacd2c5b | ||
|
6a22e7836a | ||
|
32a67b9b33 | ||
|
b5dff09c28 | ||
|
c56d8153e8 | ||
|
eb5950d126 | ||
|
4241436e40 | ||
|
e93af7aed5 | ||
|
d2416f850e | ||
|
7af22eb006 | ||
|
3f1be5e7e7 | ||
|
a4e649c82d | ||
|
cde1a8f0a8 | ||
|
cda6eb7c2f | ||
|
3c3e6aeca0 | ||
|
99156311db | ||
|
91b2e5e703 | ||
|
573fd816d0 | ||
|
e6aa002758 | ||
|
361d46ac5d | ||
|
9bc7c2fd65 | ||
|
ce49f5f8b7 | ||
|
81e4cd319d | ||
|
71f905535f | ||
|
d3bff0a9e3 | ||
|
88e6315d09 | ||
|
3d933c5244 | ||
|
73af19314d | ||
|
1d71712c8a | ||
|
1333c35389 | ||
|
e0f5f47a15 | ||
|
34323042d5 | ||
|
aa86fa9986 | ||
|
d2d659d5a9 | ||
|
1595248b52 | ||
|
fc94c616b5 | ||
|
f6f0bcf1c3 | ||
|
6eeeda5dab | ||
|
58993d2ead | ||
|
7c0b608769 | ||
|
259be84a3e | ||
|
57c880cf85 | ||
|
0469fe76d7 | ||
|
a3e74320fa | ||
|
4e0f4397b2 | ||
|
b26aab3863 | ||
|
75ec97fe61 | ||
|
9a356453fc | ||
|
f0020b3393 | ||
|
ea9f1dc91d | ||
|
2ef77dcf1f | ||
|
29e7ec4cc9 | ||
|
5710e3fad0 | ||
|
a03aa62d58 | ||
|
2203a39917 | ||
|
bc58668483 | ||
|
0b4650f355 | ||
|
aecdec48ad | ||
|
e49198a0d4 | ||
|
6aa4553dd3 | ||
|
860e8a8f5a | ||
|
434037d793 | ||
|
214eae27da | ||
|
0567188fa8 | ||
|
4326902683 | ||
|
da3589149d | ||
|
69b4b4d1f4 | ||
|
dd4b46a88b | ||
|
6f2253b2f4 | ||
|
4c00d9512e | ||
|
6f511cb1e6 | ||
|
029e84f538 | ||
|
42b1db1d08 | ||
|
9d4b34cad7 | ||
|
cd9aff0f59 | ||
|
032816fffc | ||
|
1408f06c8d | ||
|
49f8d0c6d8 | ||
|
0d6443c30a | ||
|
e62d91dda8 | ||
|
0e122c9dc5 | ||
|
c2bd453e8c | ||
|
949ea7afb7 | ||
|
9f575101d2 | ||
|
2b4470054e | ||
|
b220613e54 | ||
|
11328babe8 | ||
|
735bc92bc4 | ||
|
6894b9b1b7 | ||
|
4de7815f31 | ||
|
4ce7b48468 | ||
|
77e4cb87ad | ||
|
dd6b43035e | ||
|
e76094c546 | ||
|
9c00dc4cab | ||
|
fc416de348 | ||
|
a2156be4ec | ||
|
cf427eba0f | ||
|
0050c90678 | ||
|
5eeff02dbe | ||
|
eb283efc20 | ||
|
87121c9c21 | ||
|
a2dbbbf832 | ||
|
fd251cd9a9 | ||
|
8ced2d288e | ||
|
242926d381 | ||
|
8c9a148e71 | ||
|
9e165fc7dc | ||
|
f46e4e0cec | ||
|
efcf5ecef4 | ||
|
b6ba843d09 | ||
|
915112e274 | ||
|
cc8f8fcd2c | ||
|
bda3ea9a35 | ||
|
a7aed1f93a | ||
|
a33f602f3b | ||
|
4d08b49578 | ||
|
21e049ab45 | ||
|
1d4234caea | ||
|
d1269de3a7 | ||
|
8c19fcf8fc | ||
|
beaba2be55 | ||
|
32c9ad1d59 | ||
|
a194513252 | ||
|
cd93a1d1e1 | ||
|
2867728996 | ||
|
394cc78851 | ||
|
76f0a26322 | ||
|
92d7a577a0 | ||
|
9c1219b188 | ||
|
f62bbfe286 | ||
|
fef176eb96 | ||
|
ed541dac3b | ||
|
4a175b2158 | ||
|
4076899e11 | ||
|
998bdf3b56 | ||
|
76b211eb6d | ||
|
f781cb3922 | ||
|
ced3e5d623 | ||
|
2a4812cb87 | ||
|
1f384c7ae4 | ||
|
01a3285636 | ||
|
53166cb3f5 | ||
|
893462ae87 | ||
|
949dbf45f1 | ||
|
d7dc625042 | ||
|
cc91c77f9e | ||
|
4847fd76de | ||
|
25b711a8da | ||
|
b5e50fa62e | ||
|
28e40a5c86 | ||
|
a8a7b7c07b | ||
|
299008688d | ||
|
42a674008f | ||
|
8fdc0bf17c | ||
|
4e3896c108 | ||
|
96cd56548e | ||
|
739fb2c3d2 | ||
|
0c98573700 | ||
|
8dc0b34fdc | ||
|
384ca09842 | ||
|
97d86825c2 | ||
|
f5a824b1e6 | ||
|
4fc11e77e8 | ||
|
8d16ad7035 | ||
|
3b1d84b00b | ||
|
3f540da31b | ||
|
3a20f1c245 | ||
|
e803f0c545 | ||
|
c9495c23a6 | ||
|
26aae9eea5 | ||
|
7d92136467 | ||
|
a7896b43d7 | ||
|
2e12b2655b | ||
|
a1f0bd1e6c | ||
|
4472590133 | ||
|
64f1214ad9 | ||
|
bd465a72cf | ||
|
1d1c2a6925 | ||
|
d68ba32533 | ||
|
d1a316032d | ||
|
b465042a56 | ||
|
8ff2418b02 | ||
|
f6640bcc32 | ||
|
fa3c0fd18a | ||
|
2ac03c18c4 | ||
|
51ee6128f3 | ||
|
53478056de | ||
|
83c791564a | ||
|
65d0032995 | ||
|
37c4f99341 | ||
|
adbc873b2a | ||
|
3567a57d6a | ||
|
b4e9fb157f | ||
|
1be3646a04 | ||
|
46b82f877b | ||
|
ef56eb4a52 | ||
|
6989f9fd16 | ||
|
7c3e8547de | ||
|
8268c3edba | ||
|
a8cc8763b0 | ||
|
09b4acf087 | ||
|
3b719ce53b | ||
|
83281197f1 | ||
|
ffda04f9b4 | ||
|
606893286a | ||
|
075b71746a | ||
|
01f1f3e957 | ||
|
88a9d5e802 | ||
|
7eebfae1c3 | ||
|
340fe76b8f | ||
|
e83678df3a | ||
|
0bbbb49a31 | ||
|
0e61115857 | ||
|
8d3ac6f6fe | ||
|
86e644be9a | ||
|
30201249b5 | ||
|
41155f5ef4 | ||
|
f308fe71f9 | ||
|
5a00214fd2 | ||
|
1b3e0f65e1 | ||
|
7cfc537870 | ||
|
53513db5b5 | ||
|
59402cf198 | ||
|
12efc02d91 | ||
|
f38b907680 | ||
|
8fcb46ca4a | ||
|
71ec9a193f | ||
|
393a545548 | ||
|
f88449fbb0 | ||
|
50ad4d05f2 | ||
|
8d0e562c8a | ||
|
c98e67c003 | ||
|
5565538b80 | ||
|
958979e1d7 | ||
|
685845e097 | ||
|
3813f9e417 | ||
|
40cf3fb4d0 | ||
|
3f88b16603 | ||
|
32c02275a2 | ||
|
c0d679b6c2 | ||
|
eb789b9b9a | ||
|
b1898141c3 | ||
|
3231bfaff0 | ||
|
6bb09c9c30 | ||
|
35d3fe03ab | ||
|
f05929cd17 | ||
|
982c9828da | ||
|
d6eacb2b33 | ||
|
0abe213fc2 | ||
|
a6716d55c5 | ||
|
fa0572de77 | ||
|
6dca4a0d6b | ||
|
eacfe1a9ac | ||
|
c223cdf10a | ||
|
38c42b7a15 | ||
|
56432dc773 | ||
|
d3b4379161 | ||
|
8a6fcdbcae | ||
|
061ce646d2 | ||
|
5ec7c16e3e | ||
|
5d629508c1 | ||
|
0a6f8b0cfe | ||
|
460f826672 | ||
|
cb16a10121 | ||
|
3b6131f1ca | ||
|
57225a36b9 | ||
|
3e313399c2 | ||
|
7947e14792 | ||
|
71098102c5 | ||
|
8e601e80df | ||
|
3b14cfcc54 | ||
|
61a0e43092 | ||
|
d08aaffe41 | ||
|
7d0def30f0 | ||
|
bb45d72a56 | ||
|
3943192c5d | ||
|
18d76807f6 | ||
|
01a30a7ccf | ||
|
3fb35d043b | ||
|
9b51bd484d | ||
|
879b556845 | ||
|
0df2a501dd | ||
|
6f64fdffcc | ||
|
19252af345 | ||
|
897f53b13e | ||
|
45cd8b7e3c | ||
|
922bbdd1b6 | ||
|
c24135577c | ||
|
3240ff40bc | ||
|
58b68cab0c | ||
|
0a0afd7245 | ||
|
de01606bff | ||
|
046474b484 | ||
|
277b2b9298 | ||
|
0b7296ae95 | ||
|
ce87dd55f0 | ||
|
07b724d65b | ||
|
bc1984298f | ||
|
9ed0bdfed2 | ||
|
57628e8986 | ||
|
fc8f53fd0e | ||
|
efd7cc6a0c | ||
|
7bfc3e1256 | ||
|
7f6ca1e527 | ||
|
71d9b1d441 | ||
|
a3d64d0a90 | ||
|
e7ec2ce6cf | ||
|
61dbdd5f7c | ||
|
e6888c451d | ||
|
899219abd2 | ||
|
7a4c7a13eb | ||
|
e8797f49b9 | ||
|
e17f3051f0 | ||
|
06091933e1 | ||
|
b30c65dd89 | ||
|
0eb18d206d | ||
|
3a9948e528 | ||
|
2bcddd38b2 | ||
|
5ff8808f69 | ||
|
28b1d8a324 | ||
|
5c1dd79435 | ||
|
706d59a6cc | ||
|
251f1bba55 | ||
|
cb1fd85ca4 | ||
|
11ddc55911 | ||
|
ee106105d8 | ||
|
19f5a144e5 | ||
|
18717bac79 | ||
|
28fa83c406 | ||
|
258e514a91 | ||
|
f92546d6eb | ||
|
693456164b | ||
|
7cd3e6b1f7 | ||
|
764e63d869 | ||
|
2c6f47f277 | ||
|
e41896d6f3 | ||
|
990591cc80 | ||
|
7b85c33213 | ||
|
4b4d0e8b25 | ||
|
4fb6af3c62 | ||
|
d7ffe7e294 | ||
|
3ff139d445 | ||
|
40443e0134 | ||
|
1f927c1285 | ||
|
5e82e62335 | ||
|
de954827df | ||
|
2cb24bf198 | ||
|
739a6a4a9c | ||
|
dfcf1193dc | ||
|
c72f92e22f | ||
|
f6d01ce7e1 | ||
|
fed9700587 | ||
|
12a3a9a89e | ||
|
590c62c371 | ||
|
df41f64b3c | ||
|
1849355245 | ||
|
fa1b471ea4 | ||
|
0a67f8c947 | ||
|
621d0260f4 | ||
|
f93fca7c5b | ||
|
f4d830c2ef | ||
|
1aa2844efa | ||
|
f22b2adaad | ||
|
b547f9aa13 | ||
|
e4166e9da7 | ||
|
1634fdb421 | ||
|
7f51035f91 | ||
|
70e71cbc2a | ||
|
ffd03bf34c | ||
|
f59a3dee78 | ||
|
3ba3037242 | ||
|
dbb269fef6 | ||
|
e209189faa | ||
|
2d7065cde2 | ||
|
59f5972c93 | ||
|
8bac5275c3 | ||
|
58d748e235 | ||
|
e69ac3e62a | ||
|
7fc8ac4c0f | ||
|
069dce69cd | ||
|
3bdf621026 | ||
|
2f62897fb6 | ||
|
dbdd602f50 | ||
|
d66fa87b2b | ||
|
124a959c8d | ||
|
127dce1cd6 | ||
|
44986a35a4 | ||
|
ea516301fd | ||
|
90b684457a | ||
|
8517e2aa42 | ||
|
b880be8b7c | ||
|
57fd1a7588 | ||
|
5ed458f634 | ||
|
ac12143d00 | ||
|
b6c36d1961 | ||
|
3c2de70baa | ||
|
f6c5620682 | ||
|
79b6f64bd0 | ||
|
ed89f55bff | ||
|
8841a92142 | ||
|
0bd19c97eb | ||
|
2fd026d107 | ||
|
5e7eb37ca7 | ||
|
6b10d5d821 | ||
|
0f4dbfbcbf | ||
|
99493174ec | ||
|
333253fd4f | ||
|
01470e8d3b | ||
|
bda2fe6717 | ||
|
d1f4c5be18 | ||
|
377adea51d | ||
|
cda3d20834 | ||
|
d11870b1eb | ||
|
53bda3eea7 | ||
|
30165e8d6a | ||
|
2202eb6570 | ||
|
720927f488 | ||
|
2858c0fce0 | ||
|
36c3521f40 | ||
|
98a3b99d17 | ||
|
95a06c4531 | ||
|
6ea77b36ce | ||
|
0cd7ebf9d3 | ||
|
a816498fc6 | ||
|
549c1a6767 | ||
|
f87d1f11b0 | ||
|
fb913bc33c | ||
|
53d7a51cb0 | ||
|
12da48c756 | ||
|
a0a6bdb684 | ||
|
eb4aa2c026 | ||
|
a9e789f845 | ||
|
89b76918bd | ||
|
e98575643c | ||
|
7c9e7743f1 | ||
|
435aa4845b | ||
|
9841488ce4 | ||
|
9c907989a5 | ||
|
f684d8e59a | ||
|
a1ad6c9712 | ||
|
b6ca3090df | ||
|
ff2d793fbb | ||
|
fcfb7f1111 | ||
|
6125cde223 | ||
|
c83391e624 | ||
|
a3a818ea74 | ||
|
54a4965503 | ||
|
2233d88c01 | ||
|
9680ab74a3 | ||
|
ef711af5b5 | ||
|
8a619e4b8b | ||
|
bc09d2c640 | ||
|
f82dfde6f4 | ||
|
312c7f2d27 | ||
|
953cc327c6 | ||
|
14cff96713 | ||
|
34b9af2ce0 | ||
|
6a6ee280c3 | ||
|
861387707a | ||
|
830e665366 | ||
|
f14ae68ea0 | ||
|
c7084bf27e | ||
|
fc8ffcbe46 | ||
|
77f089654e | ||
|
e7a12bb0dd | ||
|
22fe3a4e44 | ||
|
afb8b92528 | ||
|
5178f99100 | ||
|
72af564382 | ||
|
0a2b83998c | ||
|
73e79213dc | ||
|
e31ffc9474 | ||
|
fdb8706a5f | ||
|
4c0262bd82 | ||
|
3b2dcb37a6 | ||
|
46a3b020a6 | ||
|
8373392e99 | ||
|
695c2623c2 | ||
|
979e7f2486 | ||
|
e7a9f630ec | ||
|
4e8372174b | ||
|
1b8912d7e9 | ||
|
8c3669588b | ||
|
1b784fe3b0 | ||
|
5ab1d8dfd6 | ||
|
cda8b95bfa | ||
|
9664842b1a | ||
|
09836e1d34 | ||
|
8974e20c7f | ||
|
761a49803e | ||
|
a40028f0c0 | ||
|
d4fca7e313 | ||
|
6748d4d825 | ||
|
0a5ad1d628 | ||
|
4080958e10 | ||
|
95c4a717e3 | ||
|
c5b9353105 | ||
|
17595f7ceb | ||
|
dcf78e8a06 | ||
|
de0c54c3c3 | ||
|
38970e9a52 | ||
|
563f43168b | ||
|
6e235605ed | ||
|
0749e0b675 | ||
|
756f3e627c | ||
|
4b39ed42b1 | ||
|
abe85ca4bd | ||
|
a559565ace | ||
|
d35e3163ca | ||
|
f22ad85681 | ||
|
727bdaeea4 | ||
|
42f7764eed | ||
|
1fbc1073ca | ||
|
66b683e5e7 | ||
|
7d1f61e47b | ||
|
643e5a6a2a | ||
|
a1e7e29279 | ||
|
abf6c3f3f1 | ||
|
32c81ad8c2 | ||
|
0f461c4caa | ||
|
57ed718993 | ||
|
ef1ae63233 | ||
|
81ac200cc0 | ||
|
3a2d62f6c7 | ||
|
7f80f67fd6 | ||
|
cebd8de77a | ||
|
417218fc34 | ||
|
29586437c2 | ||
|
f51d0ad914 | ||
|
35294b5f97 | ||
|
758562cff9 | ||
|
da0678c6fe | ||
|
afe4fd70cc | ||
|
4cef383fe7 | ||
|
b58c30889e | ||
|
1561c0e4d7 | ||
|
32b11c15a4 | ||
|
5e06efc1b9 | ||
|
43bd88703c | ||
|
cdd70230af | ||
|
eaf3eef2d3 | ||
|
81ec8eaf83 | ||
|
23f7e350c6 | ||
|
cea368cd3f | ||
|
50c8f2ae61 | ||
|
ed0ecababb | ||
|
72aa4c40a5 | ||
|
4f6784e2e5 | ||
|
01f015a54f | ||
|
806acad22a | ||
|
1d322d8a39 | ||
|
aef94e6950 | ||
|
456fcec334 | ||
|
3b6937c2f0 | ||
|
7cdd1d41c1 | ||
|
1fc535a740 | ||
|
033b37077a | ||
|
07f6a4a08b | ||
|
8c1b592a51 | ||
|
9460f7a17a | ||
|
106260ed69 | ||
|
123162e946 | ||
|
54e81966e5 | ||
|
9bf6ab60bb | ||
|
4f0ff3cdfc | ||
|
47cfaaa5ab | ||
|
1f49788f21 | ||
|
7e4cf9aeda | ||
|
4b6964b683 | ||
|
3caf398021 | ||
|
8840631266 | ||
|
c4411bb895 | ||
|
f63668350b | ||
|
3fc54d7ffd | ||
|
2c6c20f44f | ||
|
08ee939951 | ||
|
463b1441d3 | ||
|
6754d4ee89 | ||
|
d5d0732bf3 | ||
|
d27cbaa663 | ||
|
70adf10f2e | ||
|
e75417d26e | ||
|
2ded983828 | ||
|
0c708f766b | ||
|
a801f8d8ed | ||
|
26fc26c9fe | ||
|
df4c07d204 | ||
|
cf565d1563 | ||
|
6b425cf543 | ||
|
6188e7a0fa | ||
|
310afb0ad6 | ||
|
7d7e1e1637 | ||
|
424c8a2723 | ||
|
187fb1b85d | ||
|
595fd7a9aa | ||
|
0027cb9036 | ||
|
db6878b978 | ||
|
1c78722573 | ||
|
fea83e28c4 | ||
|
84adf64271 | ||
|
74e2b0d15d | ||
|
df302206dd | ||
|
6d8941c005 | ||
|
971b20062f | ||
|
936f334b61 | ||
|
7e3a290939 | ||
|
c6466a780e | ||
|
43774ad3fb | ||
|
b3ba5b7747 | ||
|
599f4ccef4 | ||
|
a67600d264 | ||
|
ebf4c89ef0 | ||
|
aa7da784fc | ||
|
617ed42d8c | ||
|
5848e7d90d | ||
|
01d032261c | ||
|
54d06646aa | ||
|
81da46035a | ||
|
74d0f75802 | ||
|
f25a352dcb | ||
|
21ef8da45f | ||
|
1f3a1c49df | ||
|
a8b58aaec3 | ||
|
44f9b35d93 | ||
|
711af9beed | ||
|
971339ca9a | ||
|
f5a72ffbaf | ||
|
cf4331c5af | ||
|
07b85388fc | ||
|
553b086ba2 | ||
|
ff89537899 | ||
|
f20792889a | ||
|
ee22a7ff73 | ||
|
4fc4353859 | ||
|
f4f9e25e6b | ||
|
e54f9934b5 | ||
|
f599ae065a | ||
|
5bd9bf6a4e | ||
|
cb9e8d4f76 | ||
|
f64596672e | ||
|
961150b2d3 | ||
|
0cd8e1cbc0 | ||
|
b503aff5e9 | ||
|
54bde47c67 | ||
|
d345f8dc13 | ||
|
979b0c0e78 | ||
|
13525f3327 | ||
|
5d9c2c0904 | ||
|
c748281d86 | ||
|
fa619eba7c | ||
|
17a75f7cf5 | ||
|
a74cfbfb25 | ||
|
af01fed950 | ||
|
218b446915 | ||
|
a68df18cd5 | ||
|
f79326b2d5 | ||
|
b08dcdfd90 | ||
|
ced14e578f | ||
|
0528c6e970 | ||
|
df9c1e0aeb | ||
|
b6619b41df | ||
|
ab9675544c | ||
|
0d9f703c75 | ||
|
f6ee5dd0e7 | ||
|
423d880b92 | ||
|
41017b9ab8 | ||
|
e3314c859f | ||
|
8f794fdbc6 | ||
|
80b0773618 | ||
|
ac848241b9 | ||
|
b4432e2efc | ||
|
52f60a2cf6 | ||
|
03c8438050 | ||
|
0e1c36ccf1 | ||
|
1b2b68c485 | ||
|
5c36f3e78a | ||
|
d4ec8ae823 | ||
|
449a70d840 | ||
|
8375030135 | ||
|
e9f938b0fb | ||
|
b0e57a5990 | ||
|
f47610e6fd | ||
|
c131cb9bb8 | ||
|
023f775bd2 | ||
|
1127eb6e09 | ||
|
0d7437c7d2 | ||
|
6dbc691973 | ||
|
fe53200a3a | ||
|
c7f61271a0 | ||
|
b826e2d97b | ||
|
d8ad42b04a | ||
|
b3ca30b8b6 | ||
|
bdd9da3d22 | ||
|
eb43ddc701 | ||
|
f9c65a148f | ||
|
bd0b9dff51 | ||
|
3822309657 | ||
|
ac2ec65c81 | ||
|
c0943f1776 | ||
|
5a2ab5fae7 | ||
|
281e36fde7 | ||
|
1af1c445c6 | ||
|
e6d2e028ad | ||
|
8d25b8075d | ||
|
58201fd6c3 | ||
|
2b624a9aed | ||
|
792fd7c50d | ||
|
90d37a15bd | ||
|
ef57ee803f | ||
|
e2bfb74895 | ||
|
89b486a3ee | ||
|
d6c6ef20d4 | ||
|
6cb6e9541f | ||
|
a8acecb703 | ||
|
0938297250 | ||
|
a2da2f681c | ||
|
32477e901d | ||
|
1841c4dc11 | ||
|
f81dce3321 | ||
|
e15f84716a | ||
|
d11ecd8fac | ||
|
012e70d668 | ||
|
264ee4f319 | ||
|
4640d6d6e3 | ||
|
9db9d98419 | ||
|
f79d7b9626 | ||
|
a8da469523 | ||
|
3c5eb99c59 | ||
|
8dc14516d6 | ||
|
84efc6a04e | ||
|
86e3038cfe | ||
|
6898b9ca4c | ||
|
9eb62920f7 | ||
|
7cf267254f | ||
|
5a0e3cbbf2 | ||
|
4b376ec5c2 | ||
|
92b71d3eb2 | ||
|
c32d7de7c4 | ||
|
c83a61d45d | ||
|
429f2d5aab | ||
|
ed70cf571c | ||
|
fd77671575 | ||
|
9d915a916e | ||
|
acb90935c7 | ||
|
6301ba0a12 | ||
|
44e1245416 | ||
|
6527d9db83 | ||
|
2bcaf62a2f | ||
|
2db3998f11 | ||
|
04724c58d1 | ||
|
7ed9c2770c | ||
|
48883bfa13 | ||
|
b6d9e695d1 | ||
|
43bab3c1bd | ||
|
dd67602b87 | ||
|
c96a416c2c | ||
|
c4ca9cde32 | ||
|
6c5d208893 | ||
|
795fd8b58c | ||
|
c226940792 | ||
|
bdd0a6106f | ||
|
d1ea7ad3ea | ||
|
a2b1181f7c | ||
|
8cce2f17d5 | ||
|
658b911513 | ||
|
3c95979d94 | ||
|
2e32211a28 | ||
|
ba2874173d | ||
|
ba9598682c | ||
|
30bfd82683 | ||
|
10c6774c28 | ||
|
c4ad9019aa | ||
|
7c99ab9947 | ||
|
feb7778fe4 | ||
|
b45185780f | ||
|
4e032c0c55 | ||
|
2e2b35dfeb | ||
|
526ef7e1d2 | ||
|
a37005446a | ||
|
e012b927dc | ||
|
359b8d5545 | ||
|
23c592b322 | ||
|
9aeddf86f4 | ||
|
0e1887988e | ||
|
394f2df664 | ||
|
2a04378a0d | ||
|
bac68f2c42 | ||
|
0f0ff86ffa | ||
|
8b38752cbf | ||
|
3c24589450 | ||
|
65065a62d8 | ||
|
482e9340bc | ||
|
69d490996e | ||
|
3494937e34 | ||
|
41ba1043be | ||
|
cc57de60c0 | ||
|
60a2b9e5c6 | ||
|
8e1fb666a5 | ||
|
f4ad777bbf | ||
|
2eacf6146a | ||
|
fe2ba34cb4 | ||
|
84daa0db61 | ||
|
b9118a90be | ||
|
ef2ffd4e52 | ||
|
5e3063abe0 | ||
|
f460b2497a | ||
|
c080d7054f | ||
|
61dd4cefbc | ||
|
63d99b2d70 | ||
|
55332d7671 | ||
|
16635defcd | ||
|
595cf3fcad | ||
|
c9b9f82130 | ||
|
f5bca733d7 | ||
|
494e235e18 | ||
|
85219a6004 | ||
|
e4a7851e57 | ||
|
b7529b40b5 | ||
|
74827e5324 | ||
|
2e16cc5433 | ||
|
7f41bc5703 | ||
|
a2380fb752 | ||
|
f6a902809d | ||
|
33a853397d | ||
|
4f02481899 | ||
|
b18076565f | ||
|
853ddc5840 | ||
|
7930ab66c6 | ||
|
c7716c0d59 | ||
|
49cbb254d0 | ||
|
cf9246796d | ||
|
e1dee546dc | ||
|
da6fdad2de | ||
|
567596643c | ||
|
cb617e1b14 | ||
|
facf8afa8b | ||
|
f0dd61a711 | ||
|
e958211a13 | ||
|
0ed170b5bc | ||
|
473d3453a2 | ||
|
fa9d7b0408 | ||
|
d4a28f48c9 | ||
|
ead6fff861 | ||
|
c7d06b35cd | ||
|
fa939e5c76 | ||
|
1bf2601f4f | ||
|
feb0c02c9a | ||
|
40a34a7c05 | ||
|
c62dcd96b0 | ||
|
1364d6786d | ||
|
9f2666aef9 | ||
|
a6a351e68d | ||
|
1db38a9699 | ||
|
c57db1834f | ||
|
3aeb49b469 | ||
|
80b467eead | ||
|
61572f287a | ||
|
f136664c11 | ||
|
0e545baf10 | ||
|
e65e647359 | ||
|
238268884e | ||
|
4c210d0956 | ||
|
5f32c6466a | ||
|
71bd39a8a3 | ||
|
ffb660f0f4 | ||
|
dde23632c1 | ||
|
9d26f13db0 | ||
|
2a4c9c4427 | ||
|
3bfde26b74 | ||
|
a419bc7253 | ||
|
89e0dad88b | ||
|
ff1ee1249b | ||
|
ba9cfd8041 | ||
|
80a51e02a4 | ||
|
a2e2a9a2f5 | ||
|
49e4c37cac | ||
|
11d323d8b7 | ||
|
784ba45f1f | ||
|
e534414874 | ||
|
01f4faf8f1 | ||
|
b33d30ca47 | ||
|
1ba3fae101 | ||
|
9550347e04 | ||
|
398946d39a | ||
|
05faf55e8d | ||
|
4cf5525e20 | ||
|
62e91c44d7 | ||
|
e4ec4ae92b | ||
|
c1f5dfd9cc | ||
|
0c0efeac1f | ||
|
5e0f2642e3 | ||
|
93966b0fa1 | ||
|
e90abf3517 | ||
|
d3fa0dce96 | ||
|
58a7c2fa94 | ||
|
962a8700c2 | ||
|
b5c704e2bb | ||
|
e7b52b19d7 | ||
|
903c2e6d92 | ||
|
abcb1ac760 | ||
|
b6bf76cc4c | ||
|
2bf87655da | ||
|
d4b19f19a1 | ||
|
d8ccc38d5b | ||
|
c8c9f80cc5 | ||
|
577bef5704 | ||
|
4e3b8a06ea | ||
|
363632ffa7 | ||
|
994c99f47f | ||
|
96571baadc | ||
|
4ce2b2c948 | ||
|
5d69f7a0a7 | ||
|
69ddcc6e30 | ||
|
bcb1f36ad8 | ||
|
34c65a686c | ||
|
0b32741a12 | ||
|
dbb321a3cc | ||
|
a6e4f47adf | ||
|
fb6dee32ac | ||
|
984dd6f2c0 | ||
|
02bde2c6e9 | ||
|
782d24cc04 | ||
|
4d0af575e5 | ||
|
be8bda8e73 | ||
|
1242de532e | ||
|
7d7ec7f15e | ||
|
ca112ec5d3 | ||
|
5deb8cf76d | ||
|
a2c9737c17 | ||
|
d3fca3d6cc | ||
|
16554b22c7 | ||
|
d5574098f0 | ||
|
f5a683f25c | ||
|
5f04adb392 | ||
|
edd0f7e255 | ||
|
67145d9104 | ||
|
003e2afff7 | ||
|
6e9d70c5cb | ||
|
4821b4cdf2 | ||
|
734d4db431 | ||
|
317aaed0ac | ||
|
9778098d6c | ||
|
5b1755f988 | ||
|
2a772895dd | ||
|
5fbb01cf2f | ||
|
f9e0870b4e | ||
|
a58f1e9b4b | ||
|
6fc0d89b30 | ||
|
2dcadab7d2 | ||
|
bb3307e156 | ||
|
ecd07f1209 | ||
|
266cb1174f | ||
|
bfb9b77b6e | ||
|
01b1361dcb | ||
|
3a921720d6 | ||
|
cdfd3d9c31 | ||
|
9961fb64bb | ||
|
3137c355cf | ||
|
16abaf60d2 | ||
|
9004b710ea | ||
|
6ebac21c2b | ||
|
99f79faf83 | ||
|
613fa20806 | ||
|
1b5f812278 | ||
|
3a9643c1ea | ||
|
aee7f5a8ac | ||
|
d3cd378922 | ||
|
4f5e7367d0 | ||
|
2280fb5c43 | ||
|
96fb7c2087 | ||
|
6e994fd8b9 | ||
|
a7cde1e269 | ||
|
f8310ba0d5 | ||
|
b239c3faba | ||
|
3c2281dbf9 | ||
|
ac07bf784d | ||
|
067455542f | ||
|
5bfeaa357b | ||
|
fe27a64331 | ||
|
ed638612aa | ||
|
1d7ec83510 | ||
|
75c710232d | ||
|
5af52afadb | ||
|
0f4324c2f8 | ||
|
b48e1dac94 | ||
|
f0ca8e1e31 | ||
|
74b83eb71e | ||
|
28dce0fbb5 | ||
|
c12d402c7e | ||
|
014f5b123c | ||
|
58601dfc04 | ||
|
9fe7f0adae | ||
|
ea1374371f | ||
|
bce4294529 | ||
|
de409b632a | ||
|
a677b2e844 | ||
|
c63bb19cb6 | ||
|
72fd77812e | ||
|
40f3e72bd1 | ||
|
d6d86f2aff | ||
|
c71809438b | ||
|
3e6e1a0a36 | ||
|
f590ce4a34 | ||
|
67608e29c8 | ||
|
d5c2982093 | ||
|
90fad2a3ab | ||
|
bc7c82e3da | ||
|
cb824f7dd7 | ||
|
32c47a96f1 | ||
|
4e3f8893f7 | ||
|
ca3946689a | ||
|
e2ad2dfcaa | ||
|
d6f7893c56 | ||
|
8c65a7cc31 | ||
|
aabb9be7de | ||
|
544f93bf22 | ||
|
f81dbf4a4c | ||
|
fbec8263a3 | ||
|
68d77a69e6 | ||
|
f2ef2446c6 | ||
|
875cb5387a | ||
|
ae9ecdad40 | ||
|
86a0e34975 | ||
|
1141074745 | ||
|
efc46d9989 | ||
|
2b45f745b6 | ||
|
37fb81e9b2 | ||
|
255a4fac93 | ||
|
3e3fb88de8 | ||
|
e4cf03ae46 | ||
|
554a3eb10d | ||
|
61881b528a | ||
|
c2507cbc4e | ||
|
ed0f6d165c | ||
|
8e22d38eb3 | ||
|
2599c425c3 | ||
|
0e15821a81 | ||
|
c1bb51c12b | ||
|
1532b6d159 | ||
|
945018b698 | ||
|
df7b981e5e | ||
|
4fe495675b | ||
|
7828eef2ad | ||
|
694f81b75f | ||
|
8364b6e08d | ||
|
3f4328ce9d | ||
|
9e0bf1acb2 | ||
|
c9e130a771 | ||
|
26331f61e1 | ||
|
694672859a | ||
|
858ead40b9 | ||
|
b07fe5cc34 | ||
|
0317171097 | ||
|
9741a3a53d | ||
|
7937fab5ff | ||
|
f595be07d4 | ||
|
eef106c99b | ||
|
dbe1833f92 | ||
|
520dc0ae21 | ||
|
c9be287f4a | ||
|
711f5dcaba | ||
|
ebc0aa9809 | ||
|
dcaaf50a5a | ||
|
3370b57134 | ||
|
55c5ddedf4 | ||
|
5e8b09f5af | ||
|
1acffce62d | ||
|
8555ecce87 | ||
|
4df5f668dc | ||
|
cceebeebef | ||
|
c4f19465a6 | ||
|
e868102c98 | ||
|
0d4a1837f5 | ||
|
d6a4436313 | ||
|
03b5c6aa5e | ||
|
250cd47e02 | ||
|
943fef32e7 | ||
|
408634671c | ||
|
570b5856ba | ||
|
d114d14e87 | ||
|
32791f502e | ||
|
083ab0c788 | ||
|
003c4c4e26 | ||
|
f08f89ebd4 | ||
|
3c973144c4 | ||
|
82e99ca658 | ||
|
b04d750cec | ||
|
c804e9f541 | ||
|
7753f3f842 | ||
|
c985b7c682 | ||
|
4509919c22 | ||
|
89b164c7ca | ||
|
e52d17b39a | ||
|
5014914dc9 | ||
|
122ab83a84 | ||
|
7a985c2c8a | ||
|
b11ad30a31 | ||
|
5914fc97df | ||
|
e41ae1832d | ||
|
89b50909ed | ||
|
edccd7412f | ||
|
c76beae057 | ||
|
23c5934a7d | ||
|
a078947d6d | ||
|
0faaf660b4 | ||
|
5ba98b4200 | ||
|
c36513b99d | ||
|
97814531fa | ||
|
fd3e335a02 | ||
|
e676fa2b57 | ||
|
122cbbf673 | ||
|
271793b324 | ||
|
134b31933b | ||
|
0ec5518a62 | ||
|
76931370d7 | ||
|
8cf0e4517a | ||
|
e75c62bf0f | ||
|
058285e0b9 | ||
|
795568d8c2 | ||
|
df4933fddd | ||
|
4046a51472 | ||
|
45845f645d | ||
|
d7fd8944f7 | ||
|
3cee671f25 | ||
|
8f2e5faff3 | ||
|
39ddd934f6 | ||
|
9f8a46b9d9 | ||
|
c6698eaea6 | ||
|
8a96cf3434 | ||
|
74d255c056 | ||
|
71aa841265 | ||
|
14a93a9f26 | ||
|
e1fd9c6922 | ||
|
b9db747b5c | ||
|
4a56c76901 | ||
|
6bb3184a72 | ||
|
7fb8fbd450 | ||
|
9d5bba472e | ||
|
e6d821a45f | ||
|
72f0027e21 | ||
|
29a13a9943 | ||
|
3691ae4d13 | ||
|
4dda397c29 | ||
|
b4b058998d | ||
|
10919e415e | ||
|
4966d74864 | ||
|
c70ecb30a5 | ||
|
acc0d17e0f | ||
|
b509b878bf | ||
|
760086307b | ||
|
dfbe37fdaf | ||
|
37e7ea0b52 | ||
|
44bf518244 | ||
|
63cb9b4968 | ||
|
a6cecc103c |
41
.eslintignore
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
*.min.js
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
_mydocs/
|
||||||
|
_releases/
|
||||||
|
Assets/
|
||||||
|
CliClient/build
|
||||||
|
CliClient/locales
|
||||||
|
CliClient/node_modules
|
||||||
|
CliClient/tests-build
|
||||||
|
CliClient/tests/enex_to_md
|
||||||
|
CliClient/tests/html_to_md
|
||||||
|
CliClient/tests/logs
|
||||||
|
CliClient/tests/support
|
||||||
|
CliClient/tests/sync
|
||||||
|
CliClient/tests/tmp
|
||||||
|
Clipper/joplin-webclipper/content_scripts/JSDOMParser.js
|
||||||
|
Clipper/joplin-webclipper/content_scripts/Readability-readerable.js
|
||||||
|
Clipper/joplin-webclipper/content_scripts/Readability.js
|
||||||
|
Clipper/joplin-webclipper/dist
|
||||||
|
Clipper/joplin-webclipper/icons
|
||||||
|
Clipper/joplin-webclipper/popup/build
|
||||||
|
Clipper/joplin-webclipper/popup/node_modules
|
||||||
|
docs/
|
||||||
|
ElectronClient/app/dist
|
||||||
|
ElectronClient/app/lib
|
||||||
|
ElectronClient/app/lib/vendor/sjcl-rn.js
|
||||||
|
ElectronClient/app/lib/vendor/sjcl.js
|
||||||
|
ElectronClient/app/locales
|
||||||
|
ElectronClient/app/node_modules
|
||||||
|
highlight.pack.js
|
||||||
|
node_modules/
|
||||||
|
ReactNativeClient/android
|
||||||
|
ReactNativeClient/ios
|
||||||
|
ReactNativeClient/lib/vendor/
|
||||||
|
ReactNativeClient/lib/welcomeAssets.js
|
||||||
|
ReactNativeClient/locales
|
||||||
|
ReactNativeClient/node_modules
|
||||||
|
readme/
|
||||||
|
Tools/node_modules
|
||||||
|
Tools/PortableAppsLauncher
|
53
.eslintrc.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
module.exports = {
|
||||||
|
'env': {
|
||||||
|
'browser': true,
|
||||||
|
'es6': true,
|
||||||
|
'node': true,
|
||||||
|
},
|
||||||
|
'extends': ['eslint:recommended'],
|
||||||
|
'globals': {
|
||||||
|
'Atomics': 'readonly',
|
||||||
|
'SharedArrayBuffer': 'readonly',
|
||||||
|
|
||||||
|
// Jasmine variables
|
||||||
|
'expect': 'readonly',
|
||||||
|
'describe': 'readonly',
|
||||||
|
'it': 'readonly',
|
||||||
|
'beforeEach': 'readonly',
|
||||||
|
'jasmine': 'readonly',
|
||||||
|
|
||||||
|
// React Native variables
|
||||||
|
'__DEV__': 'readonly',
|
||||||
|
|
||||||
|
// Clipper variables
|
||||||
|
'browserSupportsPromises_': true,
|
||||||
|
'chrome': 'readonly',
|
||||||
|
'browser': 'readonly',
|
||||||
|
},
|
||||||
|
'parserOptions': {
|
||||||
|
'ecmaVersion': 2018,
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
},
|
||||||
|
"sourceType": "module",
|
||||||
|
},
|
||||||
|
'rules': {
|
||||||
|
"react/jsx-uses-react": "error",
|
||||||
|
"react/jsx-uses-vars": "error",
|
||||||
|
// Ignore all unused function arguments, because in some
|
||||||
|
// case they are kept to indicate the function signature.
|
||||||
|
"no-unused-vars": ["error", { "argsIgnorePattern": ".*" }],
|
||||||
|
"no-constant-condition": 0,
|
||||||
|
"no-prototype-builtins": 0,
|
||||||
|
"space-in-parens": ["error", "never"],
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"eol-last": ["error", "always"],
|
||||||
|
"quotes": ["error", "single"],
|
||||||
|
"indent": ["error", "tab"],
|
||||||
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
],
|
||||||
|
};
|
5
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
patreon: joplin
|
||||||
|
github: laurent22
|
||||||
|
custom: https://joplinapp.org/donate/
|
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
👉 Please follow one of these issue templates:
|
||||||
|
- https://github.com/laurent22/joplin/issues/new/choose
|
||||||
|
|
||||||
|
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: "🐛 Bug Report"
|
||||||
|
about: Report a reproducible bug or regression in Joplin.
|
||||||
|
title: ''
|
||||||
|
labels: 'bug'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please provide a clear and concise description of what the bug is. (In the section Steps To Reproduce.)
|
||||||
|
Include screenshots if needed.
|
||||||
|
Please test using the latest Joplin release to make sure your issue has not already been fixed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
IMPORTANT: If you are reporting a clipper bug, please include an example URL that shows the issue.
|
||||||
|
Without the URL the issue is likely to be closed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
Joplin version:
|
||||||
|
Platform:
|
||||||
|
OS specifcs:
|
||||||
|
<!--
|
||||||
|
Platform can be one of: macOS, Linux, Windows, Android, iOS, terminal (or a combination)
|
||||||
|
OS specifcs: e.g. OS version, Linux distribution, Android/iOS version, ...
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Steps To Reproduce
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Issues without reproduction steps are likely to stall.
|
||||||
|
-->
|
||||||
|
|
||||||
|
Describe what you expected to happen:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Logfile
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please attach a debug log. Issues without a debug log are likely to stall.
|
||||||
|
For information on how to collect a log file: https://joplinapp.org/debugging/
|
||||||
|
-->
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Report an accepted feature request.
|
||||||
|
title: '[Feature request] '
|
||||||
|
labels: 'feature request'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please search open issues first - many features have already been requested!
|
||||||
|
-->
|
||||||
|
|
||||||
|
🚨 A feature request that has not been accepted on the forum will be closed! 🚨
|
||||||
|
|
||||||
|
## Has it been discussed in the forum? Link to topic.
|
||||||
|
<!--
|
||||||
|
Feature requests must be discussed and accepted in the forum first. https://discourse.joplinapp.org
|
||||||
|
Please provide a link to the topic.
|
||||||
|
Feature requests without a link to the discussion/topic on the forum will be closed.
|
||||||
|
-->
|
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: "🤔 Questions and Help"
|
||||||
|
about: The issue tracker is not for questions. Please ask questions on https://discourse.joplinapp.org/.
|
||||||
|
title: 'Question: '
|
||||||
|
labels: 'question'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🚨 The issue tracker is not for questions. 🚨
|
||||||
|
|
||||||
|
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek.
|
||||||
|
|
||||||
|
## Questions and Help
|
||||||
|
|
||||||
|
Please read the [documentation](https://joplinapp.org/) and [FAQ](https://joplinapp.org/faq/) first.
|
||||||
|
|
||||||
|
### https://discourse.joplinapp.org/
|
||||||
|
|
||||||
|
If you have still questions related to Joplin, please open a topic in the [forum](https://discourse.joplinapp.org/).
|
||||||
|
You can use your GitHub credentials to login to the forum.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- Documentation: https://joplinapp.org
|
||||||
|
- FAQ: https://joplinapp.org/faq/
|
||||||
|
- Forum: https://discourse.joplinapp.org
|
||||||
|
- How to enable end-to-end encryption: https://joplinapp.org/e2ee/
|
||||||
|
- API documentation: https://joplinapp.org/api/
|
||||||
|
- How to enable debug mode: https://joplinapp.org/debugging/
|
13
.github/PULL_REQUEST_TEMPLATE
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
Please prefix the title with the platform you are targetting:
|
||||||
|
|
||||||
|
- "Desktop" for the Windows/macOS/Linux app (Electron app)
|
||||||
|
- "Mobile" for the mobile app (or "Android" / "iOS" if the pull request only applies to one of the mobile platforms)
|
||||||
|
- "CLI" for the CLI app
|
||||||
|
|
||||||
|
For example: "Desktop: Added new setting to change font", or "Mobile: Fixed config screen error"
|
||||||
|
|
||||||
|
PLEASE READ THE GUIDE FIRST: https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
|
-->
|
25
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 90
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 7
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- "good first issue"
|
||||||
|
- "essential"
|
||||||
|
- "essential-reviewed"
|
||||||
|
- "help wanted"
|
||||||
|
- "nice to have"
|
||||||
|
- "upstream"
|
||||||
|
- "backlog"
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs.
|
||||||
|
You may also label this issue as "backlog" and I will leave it open.
|
||||||
|
Thank you for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.
|
||||||
|
only: issues
|
2
.gitignore
vendored
Executable file → Normal file
@@ -39,5 +39,7 @@ node_modules
|
|||||||
Tools/github_oauth_token.txt
|
Tools/github_oauth_token.txt
|
||||||
_releases
|
_releases
|
||||||
ReactNativeClient/lib/csstojs/
|
ReactNativeClient/lib/csstojs/
|
||||||
|
ReactNativeClient/lib/rnInjectedJs/
|
||||||
ElectronClient/app/gui/note-viewer/fonts/
|
ElectronClient/app/gui/note-viewer/fonts/
|
||||||
|
ElectronClient/app/gui/note-viewer/lib.js
|
||||||
Tools/commit_hook.txt
|
Tools/commit_hook.txt
|
10
.travis.yml
@@ -3,6 +3,14 @@ if: tag IS present
|
|||||||
|
|
||||||
rvm: 2.3.3
|
rvm: 2.3.3
|
||||||
|
|
||||||
|
# It's important to only build production branches otherwise Electron Builder
|
||||||
|
# might take assets from dev branches and overwrite those of production.
|
||||||
|
# https://docs.travis-ci.com/user/customizing-the-build/#Building-Specific-Branches
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
@@ -50,4 +58,4 @@ script:
|
|||||||
npm install
|
npm install
|
||||||
cd ../ElectronClient/app
|
cd ../ElectronClient/app
|
||||||
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
||||||
npm install && yarn dist
|
npm install && USE_HARD_LINKS=false yarn dist
|
||||||
|
Before Width: | Height: | Size: 986 B |
BIN
Assets/AdresseSupport.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
Assets/AdresseTranslation.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
62
Assets/JoplinLetter.svg
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="116.54575mm"
|
||||||
|
height="131.19589mm"
|
||||||
|
viewBox="0 0 116.54575 131.19589"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||||
|
sodipodi:docname="JoplinLetter.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.49497475"
|
||||||
|
inkscape:cx="152.11122"
|
||||||
|
inkscape:cy="-26.090631"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-2.7903623,-2.175533)">
|
||||||
|
<path
|
||||||
|
style="fill:#000000;stroke-width:0.26458332"
|
||||||
|
d="m 43.790458,133.13317 c -8.32317,-1.11843 -12.937,-2.40956 -18.46857,-5.16822 -10.21924,-5.09644 -18.1023498,-13.95338 -21.1745998,-23.79038 -1.22214,-3.91319 -1.3607,-4.872332 -1.35685,-9.392712 0.003,-3.72804 0.0907,-4.66941 0.59927,-6.44569 1.0664,-3.7246 2.49409,-6.1704 5.19529,-8.90014 3.2574198,-3.29184 6.6565798,-4.77332 11.3929598,-4.96548 4.53189,-0.18388 7.54661,0.59927 10.40386,2.70266 1.82035,1.34007 3.67693,3.96421 4.71565,6.66525 0.65839,1.71204 0.70959,2.1839 0.90042,8.29756 0.19973,6.39855 0.36372,7.6318 1.39223,10.469902 1.40468,3.87611 3.78939,6.56189 7.33039,8.25588 3.20047,1.53108 5.63801,2.00183 9.60817,1.8556 2.58182,-0.0951 3.60332,-0.25442 5.15337,-0.80371 4.61358,-1.63493 8.46322,-5.31381 10.31326,-9.85579 1.91154,-4.693002 1.90785,-4.609372 1.90213,-43.127082 -0.005,-33.78395 -0.0106,-34.14337 -0.54484,-35.32188 -1.30698,-2.882895 -2.68223,-3.398165 -9.66971,-3.622945 l -5.12472,-0.16486 V 10.998334 2.175533 l 31.41927,0.06723 31.419272,0.06723 0.0697,8.755726 0.0697,8.755724 -5.09675,0.1793 c -2.82759,0.0995 -5.60596,0.33101 -6.24051,0.52006 -1.72896,0.5151 -2.82899,1.538795 -3.52569,3.281045 l -0.61059,1.5269 -0.16762,34.7927 c -0.16988,35.26321 -0.19381,36.08914 -1.18496,40.914372 -1.81292,8.82581 -8.301582,17.89221 -16.959672,23.69719 -6.95182,4.66099 -14.48972,7.21214 -24.82645,8.40235 -2.7431,0.31585 -14.57797,0.31433 -16.93333,-0.002 z"
|
||||||
|
id="path21"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 336 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 244 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.png
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.psd
Normal file
28
BUILD.md
@@ -1,15 +1,14 @@
|
|||||||
|
[](https://travis-ci.org/laurent22/joplin) [](https://ci.appveyor.com/project/laurent22/joplin)
|
||||||
|
|
||||||
# General information
|
# General information
|
||||||
|
|
||||||
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when building each app.
|
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when building each app.
|
||||||
- The translations are built by running CliClient/build-translation.sh. You normally don't need to run this if you haven't updated the translation since the compiled files are on the repository.
|
|
||||||
|
|
||||||
## macOS dependencies
|
## macOS dependencies
|
||||||
|
|
||||||
brew install yarn node
|
brew install yarn node
|
||||||
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
|
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
|
||||||
source ~/.bash_profile
|
source ~/.bash_profile
|
||||||
|
|
||||||
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
|
|
||||||
|
|
||||||
## Linux and Windows (WSL) dependencies
|
## Linux and Windows (WSL) dependencies
|
||||||
|
|
||||||
@@ -37,12 +36,33 @@ yarn dist
|
|||||||
|
|
||||||
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
|
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
|
||||||
|
|
||||||
For node-gyp to work, you might need to install the `windows-build-tools` using `npm install --global windows-build-tools`.
|
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`.
|
||||||
|
|
||||||
|
If you get the error `libtool: unrecognized option '-static'`, follow the instructions [in this post](https://stackoverflow.com/a/38552393/561309) to use the correct libtool version.
|
||||||
|
|
||||||
That will create the executable file in the `dist` directory.
|
That will create the executable file in the `dist` directory.
|
||||||
|
|
||||||
From `/ElectronClient` you can also run `run.sh` to run the app for testing.
|
From `/ElectronClient` you can also run `run.sh` to run the app for testing.
|
||||||
|
|
||||||
|
## Building Electron application on Windows
|
||||||
|
|
||||||
|
```
|
||||||
|
cd Tools
|
||||||
|
npm install
|
||||||
|
cd ..\ElectronClient\app
|
||||||
|
xcopy /C /I /H /R /Y /S ..\..\ReactNativeClient\lib lib
|
||||||
|
npm install
|
||||||
|
yarn dist
|
||||||
|
```
|
||||||
|
|
||||||
|
If node-gyp does not works (MSBUILD: error MSB3428: Could not load the Visual C++ component "VCBuild.exe"), you might need to install the `windows-build-tools` using `npm install --global windows-build-tools`.
|
||||||
|
|
||||||
|
If `yarn dist` fails, it may need administrative rights.
|
||||||
|
|
||||||
|
If you get an `error MSB8020: The build tools for v140 cannot be found.` try to run with a different toolset version, eg `npm install --toolset=v141` (See [here](https://github.com/mapbox/node-sqlite3/issues/1124) for more info).
|
||||||
|
|
||||||
|
The [building\_win32\_tips on this page](./readme/building_win32_tips.md) might be helpful.
|
||||||
|
|
||||||
# Building the Mobile application
|
# Building the Mobile application
|
||||||
|
|
||||||
First you need to setup React Native to build projects with native code. For this, follow the instructions on the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) tutorial, in the "Building Projects with Native Code" tab.
|
First you need to setup React Native to build projects with native code. For this, follow the instructions on the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) tutorial, in the "Building Projects with Native Code" tab.
|
||||||
|
@@ -1,20 +1,73 @@
|
|||||||
|
# User support
|
||||||
|
|
||||||
|
The [Joplin Forum](https://discourse.joplinapp.org/) is the community driven place for user support, general discussion about Joplin, problems with installation, new features and software development questions. It is possible to login with your GitHub account. Don't use the issue tracker for support questions.
|
||||||
|
|
||||||
# Reporting a bug
|
# Reporting a bug
|
||||||
|
|
||||||
Please check first that it [has not already been reported](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Also consider [enabling debug mode](https://github.com/laurent22/joplin/blob/master/README_debugging.md) before reporting the issue so that you can provide as much details as possible to help fix it.
|
File bugs in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Please follow these guidelines:
|
||||||
|
|
||||||
If possible, **please provide a screenshot**. A screenshot showing the problem is often more useful than a paragraph describing it as it can make it immediately clear what the issue is.
|
- Search existing issues first, make sure yours hasn't already been reported.
|
||||||
|
- Consider [enabling debug mode](https://joplinapp.org/debugging/) so that you can provide as much details as possible when reporting the issue.
|
||||||
|
- Stay on topic, but describe the issue in detail so that others can reproduce it.
|
||||||
|
- **Provide a screenshot** if possible. A screenshot showing the problem is often more useful than a paragraph describing it.
|
||||||
|
- For web clipper bugs, **please provide the URL causing the issue**. Sometimes the clipper works in one page but not in another so it is important to know what URL has a problem.
|
||||||
|
|
||||||
# Feature requests
|
# Feature requests
|
||||||
|
|
||||||
Again, please check that it has not already been requested. If it has, simply **up-vote the issue** - the ones with the most up-votes are likely to be implemented. Adding a "+1" comment does nothing.
|
Please check that your request has not already been posted in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). If it has, **up-voting the issue** increases the chances it'll be noticed and implemented in the future. "+1" comments are not tracked.
|
||||||
|
|
||||||
# Adding new features
|
As a general rule, suggestions to *improve Joplin* should be posted first in the [Joplin Forum](https://discourse.joplinapp.org/) for discussion.
|
||||||
|
|
||||||
If you want to add a new feature, consider asking about it before implementing it to make sure it is within the scope of the project. Of course you are free to create the pull request directly but it is not guaranteed it is going to be accepted.
|
Avoid listing multiple requests in one report in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). One issue per request makes it easier to track and discuss it.
|
||||||
|
|
||||||
|
Finally, when submitting a pull request, don't forget to [test your code](#unit-tests).
|
||||||
|
|
||||||
|
# Contribute to the project
|
||||||
|
|
||||||
|
## Contributing to Joplin's translation
|
||||||
|
|
||||||
|
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
|
||||||
|
|
||||||
|
## Contributing to Joplin's code
|
||||||
|
|
||||||
|
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
|
||||||
|
|
||||||
|
- Bug fixes are always welcome. Start by reviewing the list of [essential issues](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Aessential)
|
||||||
|
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
|
||||||
|
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
|
||||||
|
|
||||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||||
|
|
||||||
# Coding style
|
## Coding style
|
||||||
|
|
||||||
- Only use tabs for indentation, not spaces.
|
Coding style is enforced by a pre-commit hook that runs eslint. This hook is installed whenever running `npm install` on any of the application directory. If for some reason the pre-commit hook didn't get installed, you can manually install it by running `npm install` at the root of the repository.
|
||||||
- Do not remove or add optional characters from other lines (such as colons or new line characters) as it can make the commit needlessly big, and create conflicts with other changes.
|
|
||||||
|
## Unit tests
|
||||||
|
|
||||||
|
When submitting a pull request for a new feature or bug fix, please add unit tests for your code. Unit testing GUI changes is not always possible so it is not required, but any change in a file under /lib for example should be unit tested.
|
||||||
|
|
||||||
|
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
|
||||||
|
|
||||||
|
cd CliClient
|
||||||
|
npm i
|
||||||
|
|
||||||
|
To run the test units, you must have an instance of the cli app running. In a first window navigate into `CliClient` and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
> If you get an error like `Error: Cannot find module '../locales/index.js'`, this means you must (a) rebuild translations or (b) take > them from one of the other apps. To do option b, you can run the following command to copy them from the `ReactNativeClient` directory:>
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> cd .. # Return to the root of the project
|
||||||
|
> rsync -aP ./ReactNativeClient/locales/ ./CliClient/build/locales/
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Then run the tests in a second window. To run all the test units:
|
||||||
|
|
||||||
|
./run_test.sh
|
||||||
|
|
||||||
|
To run just one particular file:
|
||||||
|
|
||||||
|
./run_test.sh markdownUtils # Don't add the .js extension
|
4
CliClient/.gitignore
vendored
@@ -13,9 +13,11 @@ tests/fuzzing.*
|
|||||||
tests/fuzzing -*
|
tests/fuzzing -*
|
||||||
tests/logs/*
|
tests/logs/*
|
||||||
tests/cli-integration/
|
tests/cli-integration/
|
||||||
|
tests/tmp/
|
||||||
*.mo
|
*.mo
|
||||||
*.*~
|
*.*~
|
||||||
tests/sync
|
tests/sync
|
||||||
out.txt
|
out.txt
|
||||||
linkToLocal.sh
|
linkToLocal.sh
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
tests/support/dropbox-auth.txt
|
@@ -1,14 +1,11 @@
|
|||||||
const { _ } = require('lib/locale.js');
|
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
|
||||||
const { netUtils } = require('lib/net-utils.js');
|
const { netUtils } = require('lib/net-utils.js');
|
||||||
|
|
||||||
const http = require("http");
|
const http = require('http');
|
||||||
const urlParser = require("url");
|
const urlParser = require('url');
|
||||||
const enableServerDestroy = require('server-destroy');
|
const enableServerDestroy = require('server-destroy');
|
||||||
|
|
||||||
class ResourceServer {
|
class ResourceServer {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.server_ = null;
|
this.server_ = null;
|
||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
@@ -40,7 +37,7 @@ class ResourceServer {
|
|||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]);
|
this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]);
|
||||||
if (!this.port_) {
|
if (!this.port_) {
|
||||||
this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin');
|
this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -48,11 +45,10 @@ class ResourceServer {
|
|||||||
this.server_ = http.createServer();
|
this.server_ = http.createServer();
|
||||||
|
|
||||||
this.server_.on('request', async (request, response) => {
|
this.server_.on('request', async (request, response) => {
|
||||||
|
const writeResponse = message => {
|
||||||
const writeResponse = (message) => {
|
|
||||||
response.write(message);
|
response.write(message);
|
||||||
response.end();
|
response.end();
|
||||||
}
|
};
|
||||||
|
|
||||||
const url = urlParser.parse(request.url, true);
|
const url = urlParser.parse(request.url, true);
|
||||||
let resourceId = url.pathname.split('/');
|
let resourceId = url.pathname.split('/');
|
||||||
@@ -69,6 +65,7 @@ class ResourceServer {
|
|||||||
if (!done) throw new Error('Unhandled resource: ' + resourceId);
|
if (!done) throw new Error('Unhandled resource: ' + resourceId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
response.setHeader('Content-Type', 'text/plain');
|
response.setHeader('Content-Type', 'text/plain');
|
||||||
|
// eslint-disable-next-line require-atomic-updates
|
||||||
response.statusCode = 400;
|
response.statusCode = 400;
|
||||||
response.write(error.message);
|
response.write(error.message);
|
||||||
}
|
}
|
||||||
@@ -76,7 +73,7 @@ class ResourceServer {
|
|||||||
response.end();
|
response.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.server_.on('error', (error) => {
|
this.server_.on('error', error => {
|
||||||
this.logger().error('Resource server:', error);
|
this.logger().error('Resource server:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,7 +88,6 @@ class ResourceServer {
|
|||||||
if (this.server_) this.server_.destroy();
|
if (this.server_) this.server_.destroy();
|
||||||
this.server_ = null;
|
this.server_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ResourceServer;
|
module.exports = ResourceServer;
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
const { reducer, defaultState } = require('lib/reducer.js');
|
const { reducer, defaultState } = require('lib/reducer.js');
|
||||||
const { splitCommandString } = require('lib/string-utils.js');
|
const { splitCommandString } = require('lib/string-utils.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
|
const Entities = require('html-entities').AllHtmlEntities;
|
||||||
|
const htmlentities = new Entities().encode;
|
||||||
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const tk = require('terminal-kit');
|
const tk = require('terminal-kit');
|
||||||
@@ -17,12 +19,10 @@ const Renderer = require('tkwidgets/framework/Renderer.js');
|
|||||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||||
|
|
||||||
const BaseWidget = require('tkwidgets/BaseWidget.js');
|
const BaseWidget = require('tkwidgets/BaseWidget.js');
|
||||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
|
||||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||||
const HLayoutWidget = require('tkwidgets/HLayoutWidget.js');
|
const HLayoutWidget = require('tkwidgets/HLayoutWidget.js');
|
||||||
const VLayoutWidget = require('tkwidgets/VLayoutWidget.js');
|
const VLayoutWidget = require('tkwidgets/VLayoutWidget.js');
|
||||||
const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js');
|
const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js');
|
||||||
const RootWidget = require('tkwidgets/RootWidget.js');
|
|
||||||
const WindowWidget = require('tkwidgets/WindowWidget.js');
|
const WindowWidget = require('tkwidgets/WindowWidget.js');
|
||||||
|
|
||||||
const NoteWidget = require('./gui/NoteWidget.js');
|
const NoteWidget = require('./gui/NoteWidget.js');
|
||||||
@@ -34,7 +34,6 @@ const StatusBarWidget = require('./gui/StatusBarWidget.js');
|
|||||||
const ConsoleWidget = require('./gui/ConsoleWidget.js');
|
const ConsoleWidget = require('./gui/ConsoleWidget.js');
|
||||||
|
|
||||||
class AppGui {
|
class AppGui {
|
||||||
|
|
||||||
constructor(app, store, keymap) {
|
constructor(app, store, keymap) {
|
||||||
try {
|
try {
|
||||||
this.app_ = app;
|
this.app_ = app;
|
||||||
@@ -47,12 +46,12 @@ class AppGui {
|
|||||||
// Some keys are directly handled by the tkwidget framework
|
// Some keys are directly handled by the tkwidget framework
|
||||||
// so they need to be remapped in a different way.
|
// so they need to be remapped in a different way.
|
||||||
this.tkWidgetKeys_ = {
|
this.tkWidgetKeys_ = {
|
||||||
'focus_next': 'TAB',
|
focus_next: 'TAB',
|
||||||
'focus_previous': 'SHIFT_TAB',
|
focus_previous: 'SHIFT_TAB',
|
||||||
'move_up': 'UP',
|
move_up: 'UP',
|
||||||
'move_down': 'DOWN',
|
move_down: 'DOWN',
|
||||||
'page_down': 'PAGE_DOWN',
|
page_down: 'PAGE_DOWN',
|
||||||
'page_up': 'PAGE_UP',
|
page_up: 'PAGE_UP',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.renderer_ = null;
|
this.renderer_ = null;
|
||||||
@@ -61,7 +60,7 @@ class AppGui {
|
|||||||
|
|
||||||
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
||||||
|
|
||||||
this.app_.on('modelAction', async (event) => {
|
this.app_.on('modelAction', async event => {
|
||||||
await this.handleModelAction(event.action);
|
await this.handleModelAction(event.action);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,7 +79,7 @@ class AppGui {
|
|||||||
reg.setupRecurrentSync();
|
reg.setupRecurrentSync();
|
||||||
DecryptionWorker.instance().scheduleStart();
|
DecryptionWorker.instance().scheduleStart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.fullScreen(false);
|
if (this.term_) { this.fullScreen(false); }
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -131,7 +130,7 @@ class AppGui {
|
|||||||
};
|
};
|
||||||
folderList.name = 'folderList';
|
folderList.name = 'folderList';
|
||||||
folderList.vStretch = true;
|
folderList.vStretch = true;
|
||||||
folderList.on('currentItemChange', async (event) => {
|
folderList.on('currentItemChange', async event => {
|
||||||
const item = folderList.currentItem;
|
const item = folderList.currentItem;
|
||||||
|
|
||||||
if (item === '-') {
|
if (item === '-') {
|
||||||
@@ -166,7 +165,7 @@ class AppGui {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.rootWidget_.connect(folderList, (state) => {
|
this.rootWidget_.connect(folderList, state => {
|
||||||
return {
|
return {
|
||||||
selectedFolderId: state.selectedFolderId,
|
selectedFolderId: state.selectedFolderId,
|
||||||
selectedTagId: state.selectedTagId,
|
selectedTagId: state.selectedTagId,
|
||||||
@@ -193,7 +192,7 @@ class AppGui {
|
|||||||
id: note ? note.id : null,
|
id: note ? note.id : null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.rootWidget_.connect(noteList, (state) => {
|
this.rootWidget_.connect(noteList, state => {
|
||||||
return {
|
return {
|
||||||
selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||||
items: state.notes,
|
items: state.notes,
|
||||||
@@ -207,7 +206,7 @@ class AppGui {
|
|||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
};
|
};
|
||||||
this.rootWidget_.connect(noteText, (state) => {
|
this.rootWidget_.connect(noteText, state => {
|
||||||
return {
|
return {
|
||||||
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
|
||||||
notes: state.notes,
|
notes: state.notes,
|
||||||
@@ -222,7 +221,7 @@ class AppGui {
|
|||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
};
|
};
|
||||||
this.rootWidget_.connect(noteMetadata, (state) => {
|
this.rootWidget_.connect(noteMetadata, state => {
|
||||||
return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null };
|
return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null };
|
||||||
});
|
});
|
||||||
noteMetadata.hide();
|
noteMetadata.hide();
|
||||||
@@ -287,7 +286,9 @@ class AppGui {
|
|||||||
|
|
||||||
addCommandToConsole(cmd) {
|
addCommandToConsole(cmd) {
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
|
||||||
|
if (isConfigPassword) return;
|
||||||
|
this.stdout(chalk.cyan.bold('> ' + cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
setupKeymap(keymap) {
|
setupKeymap(keymap) {
|
||||||
@@ -403,7 +404,7 @@ class AppGui {
|
|||||||
activeListItem() {
|
activeListItem() {
|
||||||
const widget = this.widget('mainWindow').focusedWidget;
|
const widget = this.widget('mainWindow').focusedWidget;
|
||||||
if (!widget) return null;
|
if (!widget) return null;
|
||||||
|
|
||||||
if (widget.name == 'noteList' || widget.name == 'folderList') {
|
if (widget.name == 'noteList' || widget.name == 'folderList') {
|
||||||
return widget.currentItem;
|
return widget.currentItem;
|
||||||
}
|
}
|
||||||
@@ -425,18 +426,14 @@ class AppGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async processFunctionCommand(cmd) {
|
async processFunctionCommand(cmd) {
|
||||||
|
|
||||||
if (cmd === 'activate') {
|
if (cmd === 'activate') {
|
||||||
|
|
||||||
const w = this.widget('mainWindow').focusedWidget;
|
const w = this.widget('mainWindow').focusedWidget;
|
||||||
if (w.name === 'folderList') {
|
if (w.name === 'folderList') {
|
||||||
this.widget('noteList').focus();
|
this.widget('noteList').focus();
|
||||||
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
||||||
this.processPromptCommand('edit $n');
|
this.processPromptCommand('edit $n');
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd === 'delete') {
|
} else if (cmd === 'delete') {
|
||||||
|
|
||||||
if (this.widget('folderList').hasFocus) {
|
if (this.widget('folderList').hasFocus) {
|
||||||
const item = this.widget('folderList').selectedJoplinItem;
|
const item = this.widget('folderList').selectedJoplinItem;
|
||||||
|
|
||||||
@@ -457,9 +454,7 @@ class AppGui {
|
|||||||
} else {
|
} else {
|
||||||
this.stdout(_('Please select the note or notebook to be deleted first.'));
|
this.stdout(_('Please select the note or notebook to be deleted first.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd === 'toggle_console') {
|
} else if (cmd === 'toggle_console') {
|
||||||
|
|
||||||
if (!this.consoleIsShown()) {
|
if (!this.consoleIsShown()) {
|
||||||
this.showConsole();
|
this.showConsole();
|
||||||
this.minimizeConsole();
|
this.minimizeConsole();
|
||||||
@@ -470,22 +465,15 @@ class AppGui {
|
|||||||
this.maximizeConsole();
|
this.maximizeConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd === 'toggle_metadata') {
|
} else if (cmd === 'toggle_metadata') {
|
||||||
|
|
||||||
this.toggleNoteMetadata();
|
this.toggleNoteMetadata();
|
||||||
|
|
||||||
} else if (cmd === 'enter_command_line_mode') {
|
} else if (cmd === 'enter_command_line_mode') {
|
||||||
|
|
||||||
const cmd = await this.widget('statusBar').prompt();
|
const cmd = await this.widget('statusBar').prompt();
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
this.addCommandToConsole(cmd);
|
this.addCommandToConsole(cmd);
|
||||||
await this.processPromptCommand(cmd);
|
await this.processPromptCommand(cmd);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
throw new Error('Unknown command: ' + cmd);
|
throw new Error('Unknown command: ' + cmd);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,9 +482,9 @@ class AppGui {
|
|||||||
cmd = cmd.trim();
|
cmd = cmd.trim();
|
||||||
if (!cmd.length) return;
|
if (!cmd.length) return;
|
||||||
|
|
||||||
this.logger().info('Got command: ' + cmd);
|
// this.logger().debug('Got command: ' + cmd);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let note = this.widget('noteList').currentItem;
|
let note = this.widget('noteList').currentItem;
|
||||||
let folder = this.widget('folderList').currentItem;
|
let folder = this.widget('folderList').currentItem;
|
||||||
let args = splitCommandString(cmd);
|
let args = splitCommandString(cmd);
|
||||||
@@ -506,7 +494,7 @@ class AppGui {
|
|||||||
args[i] = note ? note.id : '';
|
args[i] = note ? note.id : '';
|
||||||
} else if (args[i] == '$b') {
|
} else if (args[i] == '$b') {
|
||||||
args[i] = folder ? folder.id : '';
|
args[i] = folder ? folder.id : '';
|
||||||
} else if (args[i] == '$c') {
|
} else if (args[i] == '$c') {
|
||||||
const item = this.activeListItem();
|
const item = this.activeListItem();
|
||||||
args[i] = item ? item.id : '';
|
args[i] = item ? item.id : '';
|
||||||
}
|
}
|
||||||
@@ -518,7 +506,7 @@ class AppGui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.widget('console').scrollBottom();
|
this.widget('console').scrollBottom();
|
||||||
|
|
||||||
// Invalidate so that the screen is redrawn in case inputting a command has moved
|
// Invalidate so that the screen is redrawn in case inputting a command has moved
|
||||||
// the GUI up (in particular due to autocompletion), it's moved back to the right position.
|
// the GUI up (in particular due to autocompletion), it's moved back to the right position.
|
||||||
this.widget('root').invalidate();
|
this.widget('root').invalidate();
|
||||||
@@ -598,7 +586,7 @@ class AppGui {
|
|||||||
async setupResourceServer() {
|
async setupResourceServer() {
|
||||||
const linkStyle = chalk.blue.underline;
|
const linkStyle = chalk.blue.underline;
|
||||||
const noteTextWidget = this.widget('noteText');
|
const noteTextWidget = this.widget('noteText');
|
||||||
const resourceIdRegex = /^:\/[a-f0-9]+$/i
|
const resourceIdRegex = /^:\/[a-f0-9]+$/i;
|
||||||
const noteLinks = {};
|
const noteLinks = {};
|
||||||
|
|
||||||
const hasProtocol = function(s, protocols) {
|
const hasProtocol = function(s, protocols) {
|
||||||
@@ -608,7 +596,7 @@ class AppGui {
|
|||||||
if (s.indexOf(protocols[i] + '://') === 0) return true;
|
if (s.indexOf(protocols[i] + '://') === 0) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
// By default, before the server is started, only the regular
|
// By default, before the server is started, only the regular
|
||||||
// URLs appear in blue.
|
// URLs appear in blue.
|
||||||
@@ -632,16 +620,33 @@ class AppGui {
|
|||||||
const link = noteLinks[path];
|
const link = noteLinks[path];
|
||||||
|
|
||||||
if (link.type === 'url') {
|
if (link.type === 'url') {
|
||||||
response.writeHead(302, { 'Location': link.url });
|
response.writeHead(302, { Location: link.url });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (link.type === 'resource') {
|
if (link.type === 'item') {
|
||||||
const resourceId = link.id;
|
const itemId = link.id;
|
||||||
let resource = await Resource.load(resourceId);
|
let item = await BaseItem.loadItemById(itemId);
|
||||||
if (!resource) throw new Error('No resource with ID ' + resourceId); // Should be nearly impossible
|
if (!item) throw new Error('No item with ID ' + itemId); // Should be nearly impossible
|
||||||
if (resource.mime) response.setHeader('Content-Type', resource.mime);
|
|
||||||
response.write(await Resource.content(resource));
|
if (item.type_ === BaseModel.TYPE_RESOURCE) {
|
||||||
|
if (item.mime) response.setHeader('Content-Type', item.mime);
|
||||||
|
response.write(await Resource.content(item));
|
||||||
|
} else if (item.type_ === BaseModel.TYPE_NOTE) {
|
||||||
|
const html = [
|
||||||
|
`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="client-nojs" lang="en" dir="ltr">
|
||||||
|
<head><meta charset="UTF-8"/></head><body>
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
html.push('<pre>' + htmlentities(item.title) + '\n\n' + htmlentities(item.body) + '</pre>');
|
||||||
|
html.push('</body></html>');
|
||||||
|
response.write(html.join(''));
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported item type: ' + item.type_);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,9 +662,9 @@ class AppGui {
|
|||||||
|
|
||||||
if (resourceIdRegex.test(url)) {
|
if (resourceIdRegex.test(url)) {
|
||||||
noteLinks[index] = {
|
noteLinks[index] = {
|
||||||
type: 'resource',
|
type: 'item',
|
||||||
id: url.substr(2),
|
id: url.substr(2),
|
||||||
};
|
};
|
||||||
} else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) {
|
} else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) {
|
||||||
noteLinks[index] = {
|
noteLinks[index] = {
|
||||||
type: 'url',
|
type: 'url',
|
||||||
@@ -691,7 +696,6 @@ class AppGui {
|
|||||||
term.grabInput();
|
term.grabInput();
|
||||||
|
|
||||||
term.on('key', async (name, matches, data) => {
|
term.on('key', async (name, matches, data) => {
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Handle special shortcuts
|
// Handle special shortcuts
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -709,13 +713,13 @@ class AppGui {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'CTRL_C' ) {
|
if (name === 'CTRL_C') {
|
||||||
const cmd = this.app().currentCommand();
|
const cmd = this.app().currentCommand();
|
||||||
if (!cmd || !cmd.cancellable() || this.commandCancelCalled_) {
|
if (!cmd || !cmd.cancellable() || this.commandCancelCalled_) {
|
||||||
this.stdout(_('Press Ctrl+D or type "exit" to exit the application'));
|
this.stdout(_('Press Ctrl+D or type "exit" to exit the application'));
|
||||||
} else {
|
} else {
|
||||||
this.commandCancelCalled_ = true;
|
this.commandCancelCalled_ = true;
|
||||||
await cmd.cancel()
|
await cmd.cancel();
|
||||||
this.commandCancelCalled_ = false;
|
this.commandCancelCalled_ = false;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -724,8 +728,8 @@ class AppGui {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Build up current shortcut
|
// Build up current shortcut
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
const now = (new Date()).getTime();
|
const now = new Date().getTime();
|
||||||
|
|
||||||
if (now - this.lastShortcutKeyTime_ > 800 || this.isSpecialKey(name)) {
|
if (now - this.lastShortcutKeyTime_ > 800 || this.isSpecialKey(name)) {
|
||||||
this.currentShortcutKeys_ = [name];
|
this.currentShortcutKeys_ = [name];
|
||||||
@@ -755,7 +759,7 @@ class AppGui {
|
|||||||
if (statusBar.promptActive) processShortcutKeys = false;
|
if (statusBar.promptActive) processShortcutKeys = false;
|
||||||
|
|
||||||
if (processShortcutKeys) {
|
if (processShortcutKeys) {
|
||||||
this.logger().info('Shortcut:', shortcutKey, keymapItem);
|
this.logger().debug('Shortcut:', shortcutKey, keymapItem);
|
||||||
|
|
||||||
this.currentShortcutKeys_ = [];
|
this.currentShortcutKeys_ = [];
|
||||||
|
|
||||||
@@ -793,7 +797,6 @@ class AppGui {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppGui.INPUT_MODE_NORMAL = 1;
|
AppGui.INPUT_MODE_NORMAL = 1;
|
||||||
|
@@ -1,30 +1,21 @@
|
|||||||
const { BaseApplication } = require('lib/BaseApplication');
|
const { BaseApplication } = require('lib/BaseApplication');
|
||||||
const { createStore, applyMiddleware } = require('redux');
|
|
||||||
const { reducer, defaultState } = require('lib/reducer.js');
|
|
||||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
const ResourceService = require('lib/services/ResourceService');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { Logger } = require('lib/logger.js');
|
|
||||||
const { sprintf } = require('sprintf-js');
|
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const { fileExtension } = require('lib/path-utils.js');
|
const { fileExtension } = require('lib/path-utils.js');
|
||||||
const { shim } = require('lib/shim.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js');
|
|
||||||
const os = require('os');
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
const EventEmitter = require('events');
|
|
||||||
const Cache = require('lib/Cache');
|
const Cache = require('lib/Cache');
|
||||||
|
const RevisionService = require('lib/services/RevisionService');
|
||||||
|
|
||||||
class Application extends BaseApplication {
|
class Application extends BaseApplication {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -73,7 +64,7 @@ class Application extends BaseApplication {
|
|||||||
// const response = await cliUtils.promptMcq(msg, answers);
|
// const response = await cliUtils.promptMcq(msg, answers);
|
||||||
// if (!response) return null;
|
// if (!response) return null;
|
||||||
|
|
||||||
return output[response - 1];
|
// return output[response - 1];
|
||||||
} else {
|
} else {
|
||||||
return output.length ? output[0] : null;
|
return output.length ? output[0] : null;
|
||||||
}
|
}
|
||||||
@@ -95,10 +86,12 @@ class Application extends BaseApplication {
|
|||||||
const parent = options.parent ? options.parent : app().currentFolder();
|
const parent = options.parent ? options.parent : app().currentFolder();
|
||||||
const ItemClass = BaseItem.itemClass(type);
|
const ItemClass = BaseItem.itemClass(type);
|
||||||
|
|
||||||
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { // Handle it as pattern
|
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) {
|
||||||
|
// Handle it as pattern
|
||||||
if (!parent) throw new Error(_('No notebook selected.'));
|
if (!parent) throw new Error(_('No notebook selected.'));
|
||||||
return await Note.previews(parent.id, { titlePattern: pattern });
|
return await Note.previews(parent.id, { titlePattern: pattern });
|
||||||
} else { // Single item
|
} else {
|
||||||
|
// Single item
|
||||||
let item = null;
|
let item = null;
|
||||||
if (type == BaseModel.TYPE_NOTE) {
|
if (type == BaseModel.TYPE_NOTE) {
|
||||||
if (!parent) throw new Error(_('No notebook has been specified.'));
|
if (!parent) throw new Error(_('No notebook has been specified.'));
|
||||||
@@ -124,15 +117,15 @@ class Application extends BaseApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupCommand(cmd) {
|
setupCommand(cmd) {
|
||||||
cmd.setStdout((text) => {
|
cmd.setStdout(text => {
|
||||||
return this.stdout(text);
|
return this.stdout(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
cmd.setDispatcher((action) => {
|
cmd.setDispatcher(action => {
|
||||||
if (this.store()) {
|
if (this.store()) {
|
||||||
return this.store().dispatch(action);
|
return this.store().dispatch(action);
|
||||||
} else {
|
} else {
|
||||||
return (action) => {};
|
return action => {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -183,9 +176,9 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
commands(uiType = null) {
|
commands(uiType = null) {
|
||||||
if (!this.allCommandsLoaded_) {
|
if (!this.allCommandsLoaded_) {
|
||||||
fs.readdirSync(__dirname).forEach((path) => {
|
fs.readdirSync(__dirname).forEach(path => {
|
||||||
if (path.indexOf('command-') !== 0) return;
|
if (path.indexOf('command-') !== 0) return;
|
||||||
const ext = fileExtension(path)
|
const ext = fileExtension(path);
|
||||||
if (ext != 'js') return;
|
if (ext != 'js') return;
|
||||||
|
|
||||||
let CommandClass = require('./' + path);
|
let CommandClass = require('./' + path);
|
||||||
@@ -274,31 +267,39 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
dummyGui() {
|
dummyGui() {
|
||||||
return {
|
return {
|
||||||
isDummy: () => { return true; },
|
isDummy: () => {
|
||||||
prompt: (initialText = '', promptString = '', options = null) => { return cliUtils.prompt(initialText, promptString, options); },
|
return true;
|
||||||
|
},
|
||||||
|
prompt: (initialText = '', promptString = '', options = null) => {
|
||||||
|
return cliUtils.prompt(initialText, promptString, options);
|
||||||
|
},
|
||||||
showConsole: () => {},
|
showConsole: () => {},
|
||||||
maximizeConsole: () => {},
|
maximizeConsole: () => {},
|
||||||
stdout: (text) => { console.info(text); },
|
stdout: text => {
|
||||||
fullScreen: (b=true) => {},
|
console.info(text);
|
||||||
|
},
|
||||||
|
fullScreen: (b = true) => {},
|
||||||
exit: () => {},
|
exit: () => {},
|
||||||
showModalOverlay: (text) => {},
|
showModalOverlay: text => {},
|
||||||
hideModalOverlay: () => {},
|
hideModalOverlay: () => {},
|
||||||
stdoutMaxWidth: () => { return 78; },
|
stdoutMaxWidth: () => {
|
||||||
|
return 100;
|
||||||
|
},
|
||||||
forceRender: () => {},
|
forceRender: () => {},
|
||||||
termSaveState: () => {},
|
termSaveState: () => {},
|
||||||
termRestoreState: (state) => {},
|
termRestoreState: state => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async execCommand(argv) {
|
async execCommand(argv) {
|
||||||
if (!argv.length) return this.execCommand(['help']);
|
if (!argv.length) return this.execCommand(['help']);
|
||||||
reg.logger().info('execCommand()', argv);
|
// reg.logger().debug('execCommand()', argv);
|
||||||
const commandName = argv[0];
|
const commandName = argv[0];
|
||||||
this.activeCommand_ = this.findCommandByName(commandName);
|
this.activeCommand_ = this.findCommandByName(commandName);
|
||||||
|
|
||||||
let outException = null;
|
let outException = null;
|
||||||
try {
|
try {
|
||||||
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
|
||||||
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
|
||||||
await this.activeCommand_.action(cmdArgs);
|
await this.activeCommand_.action(cmdArgs);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -314,24 +315,24 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
async loadKeymaps() {
|
async loadKeymaps() {
|
||||||
const defaultKeyMap = [
|
const defaultKeyMap = [
|
||||||
{ "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
|
{ keys: [':'], type: 'function', command: 'enter_command_line_mode' },
|
||||||
{ "keys": ["TAB"], "type": "function", "command": "focus_next" },
|
{ keys: ['TAB'], type: 'function', command: 'focus_next' },
|
||||||
{ "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
|
{ keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' },
|
||||||
{ "keys": ["UP"], "type": "function", "command": "move_up" },
|
{ keys: ['UP'], type: 'function', command: 'move_up' },
|
||||||
{ "keys": ["DOWN"], "type": "function", "command": "move_down" },
|
{ keys: ['DOWN'], type: 'function', command: 'move_down' },
|
||||||
{ "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
|
{ keys: ['PAGE_UP'], type: 'function', command: 'page_up' },
|
||||||
{ "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
|
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
|
||||||
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
|
{ keys: ['ENTER'], type: 'function', command: 'activate' },
|
||||||
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
|
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
|
||||||
{ "keys": [" "], "command": "todo toggle $n" },
|
{ keys: [' '], command: 'todo toggle $n' },
|
||||||
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
|
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
|
||||||
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
|
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
|
||||||
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
|
{ keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 },
|
||||||
{ "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
|
{ keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 },
|
||||||
{ "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
|
{ keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 },
|
||||||
{ "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
|
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
|
||||||
{ "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 },
|
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
|
||||||
{ "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 }
|
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Filter the keymap item by command so that items in keymap.json can override
|
// Filter the keymap item by command so that items in keymap.json can override
|
||||||
@@ -339,7 +340,7 @@ class Application extends BaseApplication {
|
|||||||
const itemsByCommand = {};
|
const itemsByCommand = {};
|
||||||
|
|
||||||
for (let i = 0; i < defaultKeyMap.length; i++) {
|
for (let i = 0; i < defaultKeyMap.length; i++) {
|
||||||
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]
|
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = Setting.value('profileDir') + '/keymap.json';
|
const filePath = Setting.value('profileDir') + '/keymap.json';
|
||||||
@@ -372,7 +373,7 @@ class Application extends BaseApplication {
|
|||||||
async start(argv) {
|
async start(argv) {
|
||||||
argv = await super.start(argv);
|
argv = await super.start(argv);
|
||||||
|
|
||||||
cliUtils.setStdout((object) => {
|
cliUtils.setStdout(object => {
|
||||||
return this.stdout(object);
|
return this.stdout(object);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -383,16 +384,26 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
||||||
|
|
||||||
|
await this.applySettingsSideEffects();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.execCommand(argv);
|
await this.execCommand(argv);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.showStackTraces_) {
|
if (this.showStackTraces_) {
|
||||||
console.info(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
console.info(error.message);
|
console.info(error.message);
|
||||||
}
|
}
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} else { // Otherwise open the GUI
|
|
||||||
|
await Setting.saveAll();
|
||||||
|
|
||||||
|
// Need to call exit() explicitely, otherwise Node wait for any timeout to complete
|
||||||
|
// https://stackoverflow.com/questions/18050095
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
// Otherwise open the GUI
|
||||||
this.initRedux();
|
this.initRedux();
|
||||||
|
|
||||||
const keymap = await this.loadKeymaps();
|
const keymap = await this.loadKeymaps();
|
||||||
@@ -411,6 +422,10 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
const tags = await Tag.allWithNotes();
|
const tags = await Tag.allWithNotes();
|
||||||
|
|
||||||
|
ResourceService.runInBackground();
|
||||||
|
|
||||||
|
RevisionService.instance().runInBackground();
|
||||||
|
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'TAG_UPDATE_ALL',
|
type: 'TAG_UPDATE_ALL',
|
||||||
items: tags,
|
items: tags,
|
||||||
@@ -422,7 +437,6 @@ class Application extends BaseApplication {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let application_ = null;
|
let application_ = null;
|
||||||
@@ -433,4 +447,4 @@ function app() {
|
|||||||
return application_;
|
return application_;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { app };
|
module.exports = { app };
|
||||||
|
@@ -14,11 +14,11 @@ async function handleAutocompletionPromise(line) {
|
|||||||
//should look for commmands it could be
|
//should look for commmands it could be
|
||||||
if (words.length == 1) {
|
if (words.length == 1) {
|
||||||
if (names.indexOf(words[0]) === -1) {
|
if (names.indexOf(words[0]) === -1) {
|
||||||
let x = names.filter((n) => n.indexOf(words[0]) === 0);
|
let x = names.filter(n => n.indexOf(words[0]) === 0);
|
||||||
if (x.length === 1) {
|
if (x.length === 1) {
|
||||||
return x[0] + ' ';
|
return x[0] + ' ';
|
||||||
}
|
}
|
||||||
return x.length > 0 ? x.map((a) => a + ' ') : line;
|
return x.length > 0 ? x.map(a => a + ' ') : line;
|
||||||
} else {
|
} else {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -34,9 +34,9 @@ async function handleAutocompletionPromise(line) {
|
|||||||
let next = words.length > 1 ? words[words.length - 1] : '';
|
let next = words.length > 1 ? words[words.length - 1] : '';
|
||||||
let l = [];
|
let l = [];
|
||||||
if (next[0] === '-') {
|
if (next[0] === '-') {
|
||||||
for (let i = 0; i<metadata.options.length; i++) {
|
for (let i = 0; i < metadata.options.length; i++) {
|
||||||
const options = metadata.options[i][0].split(' ');
|
const options = metadata.options[i][0].split(' ');
|
||||||
//if there are multiple options then they will be seperated by comma and
|
//if there are multiple options then they will be separated by comma and
|
||||||
//space. The comma should be removed
|
//space. The comma should be removed
|
||||||
if (options[0][options[0].length - 1] === ',') {
|
if (options[0][options[0].length - 1] === ',') {
|
||||||
options[0] = options[0].slice(0, -1);
|
options[0] = options[0].slice(0, -1);
|
||||||
@@ -55,20 +55,19 @@ async function handleAutocompletionPromise(line) {
|
|||||||
if (l.length === 0) {
|
if (l.length === 0) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
let ret = l.map(a=>toCommandLine(a));
|
let ret = l.map(a => toCommandLine(a));
|
||||||
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
//Complete an argument
|
//Complete an argument
|
||||||
//Determine the number of positional arguments by counting the number of
|
//Determine the number of positional arguments by counting the number of
|
||||||
//words that don't start with a - less one for the command name
|
//words that don't start with a - less one for the command name
|
||||||
const positionalArgs = words.filter((a)=>a.indexOf('-') !== 0).length - 1;
|
const positionalArgs = words.filter(a => a.indexOf('-') !== 0).length - 1;
|
||||||
|
|
||||||
let cmdUsage = yargParser(metadata.usage)['_'];
|
let cmdUsage = yargParser(metadata.usage)['_'];
|
||||||
cmdUsage.splice(0, 1);
|
cmdUsage.splice(0, 1);
|
||||||
|
|
||||||
if (cmdUsage.length >= positionalArgs) {
|
if (cmdUsage.length >= positionalArgs) {
|
||||||
|
|
||||||
let argName = cmdUsage[positionalArgs - 1];
|
let argName = cmdUsage[positionalArgs - 1];
|
||||||
argName = cliUtils.parseCommandArg(argName).name;
|
argName = cliUtils.parseCommandArg(argName).name;
|
||||||
|
|
||||||
@@ -76,23 +75,23 @@ async function handleAutocompletionPromise(line) {
|
|||||||
|
|
||||||
if (argName == 'note' || argName == 'note-pattern') {
|
if (argName == 'note' || argName == 'note-pattern') {
|
||||||
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
||||||
l.push(...notes.map((n) => n.title));
|
l.push(...notes.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'notebook') {
|
if (argName == 'notebook') {
|
||||||
const folders = await Folder.search({ titlePattern: next + '*' });
|
const folders = await Folder.search({ titlePattern: next + '*' });
|
||||||
l.push(...folders.map((n) => n.title));
|
l.push(...folders.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'item') {
|
if (argName == 'item') {
|
||||||
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
||||||
const folders = await Folder.search({ titlePattern: next + '*' });
|
const folders = await Folder.search({ titlePattern: next + '*' });
|
||||||
l.push(...notes.map((n) => n.title), folders.map((n) => n.title));
|
l.push(...notes.map(n => n.title), folders.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'tag') {
|
if (argName == 'tag') {
|
||||||
let tags = await Tag.search({ titlePattern: next + '*' });
|
let tags = await Tag.search({ titlePattern: next + '*' });
|
||||||
l.push(...tags.map((n) => n.title));
|
l.push(...tags.map(n => n.title));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argName == 'file') {
|
if (argName == 'file') {
|
||||||
@@ -113,12 +112,11 @@ async function handleAutocompletionPromise(line) {
|
|||||||
if (l.length === 1) {
|
if (l.length === 1) {
|
||||||
return toCommandLine([...words.slice(0, -1), l[0]]);
|
return toCommandLine([...words.slice(0, -1), l[0]]);
|
||||||
} else if (l.length > 1) {
|
} else if (l.length > 1) {
|
||||||
let ret = l.map(a=>toCommandLine(a));
|
let ret = l.map(a => toCommandLine(a));
|
||||||
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
return line;
|
return line;
|
||||||
|
|
||||||
}
|
}
|
||||||
function handleAutocompletion(str, callback) {
|
function handleAutocompletion(str, callback) {
|
||||||
handleAutocompletionPromise(str).then(function(res) {
|
handleAutocompletionPromise(str).then(function(res) {
|
||||||
@@ -127,19 +125,21 @@ function handleAutocompletion(str, callback) {
|
|||||||
}
|
}
|
||||||
function toCommandLine(args) {
|
function toCommandLine(args) {
|
||||||
if (Array.isArray(args)) {
|
if (Array.isArray(args)) {
|
||||||
return args.map(function(a) {
|
return args
|
||||||
if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
.map(function(a) {
|
||||||
return "'" + a + "'";
|
if (a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
||||||
} else if (a.indexOf("'") !== -1) {
|
return '\'' + a + '\'';
|
||||||
return '"' + a + '"';
|
} else if (a.indexOf('\'') !== -1) {
|
||||||
} else {
|
return '"' + a + '"';
|
||||||
return a;
|
} else {
|
||||||
}
|
return a;
|
||||||
}).join(' ');
|
}
|
||||||
|
})
|
||||||
|
.join(' ');
|
||||||
} else {
|
} else {
|
||||||
if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
if (args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
||||||
return "'" + args + "' ";
|
return '\'' + args + '\' ';
|
||||||
} else if (args.indexOf("'") !== -1) {
|
} else if (args.indexOf('\'') !== -1) {
|
||||||
return '"' + args + '" ';
|
return '"' + args + '" ';
|
||||||
} else {
|
} else {
|
||||||
return args + ' ';
|
return args + ' ';
|
||||||
@@ -151,9 +151,9 @@ function getArguments(line) {
|
|||||||
let inDoubleQuotes = false;
|
let inDoubleQuotes = false;
|
||||||
let currentWord = '';
|
let currentWord = '';
|
||||||
let parsed = [];
|
let parsed = [];
|
||||||
for(let i = 0; i<line.length; i++) {
|
for (let i = 0; i < line.length; i++) {
|
||||||
if(line[i] === '"') {
|
if (line[i] === '"') {
|
||||||
if(inDoubleQuotes) {
|
if (inDoubleQuotes) {
|
||||||
inDoubleQuotes = false;
|
inDoubleQuotes = false;
|
||||||
//maybe push word to parsed?
|
//maybe push word to parsed?
|
||||||
//currentWord += '"';
|
//currentWord += '"';
|
||||||
@@ -161,8 +161,8 @@ function getArguments(line) {
|
|||||||
inDoubleQuotes = true;
|
inDoubleQuotes = true;
|
||||||
//currentWord += '"';
|
//currentWord += '"';
|
||||||
}
|
}
|
||||||
} else if(line[i] === "'") {
|
} else if (line[i] === '\'') {
|
||||||
if(inSingleQuotes) {
|
if (inSingleQuotes) {
|
||||||
inSingleQuotes = false;
|
inSingleQuotes = false;
|
||||||
//maybe push word to parsed?
|
//maybe push word to parsed?
|
||||||
//currentWord += "'";
|
//currentWord += "'";
|
||||||
@@ -170,8 +170,7 @@ function getArguments(line) {
|
|||||||
inSingleQuotes = true;
|
inSingleQuotes = true;
|
||||||
//currentWord += "'";
|
//currentWord += "'";
|
||||||
}
|
}
|
||||||
} else if (/\s/.test(line[i]) &&
|
} else if (/\s/.test(line[i]) && !(inDoubleQuotes || inSingleQuotes)) {
|
||||||
!(inDoubleQuotes || inSingleQuotes)) {
|
|
||||||
if (currentWord !== '') {
|
if (currentWord !== '') {
|
||||||
parsed.push(currentWord);
|
parsed.push(currentWord);
|
||||||
currentWord = '';
|
currentWord = '';
|
||||||
|
@@ -2,7 +2,6 @@ const { _ } = require('lib/locale.js');
|
|||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
|
|
||||||
class BaseCommand {
|
class BaseCommand {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.stdout_ = null;
|
this.stdout_ = null;
|
||||||
this.prompt_ = null;
|
this.prompt_ = null;
|
||||||
@@ -32,10 +31,6 @@ class BaseCommand {
|
|||||||
return this.compatibleUis().indexOf(ui) >= 0;
|
return this.compatibleUis().indexOf(ui) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
aliases() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -97,7 +92,6 @@ class BaseCommand {
|
|||||||
logger() {
|
logger() {
|
||||||
return reg.logger();
|
return reg.logger();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { BaseCommand };
|
module.exports = { BaseCommand };
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
|
const { fileExtension, dirname } = require('lib/path-utils.js');
|
||||||
const wrap_ = require('word-wrap');
|
const wrap_ = require('word-wrap');
|
||||||
const { _, setLocale, languageCode } = require('lib/locale.js');
|
const { languageCode } = require('lib/locale.js');
|
||||||
|
|
||||||
const rootDir = dirname(dirname(__dirname));
|
const rootDir = dirname(dirname(__dirname));
|
||||||
const MAX_WIDTH = 78;
|
const MAX_WIDTH = 78;
|
||||||
@@ -22,14 +22,14 @@ function renderOptions(options) {
|
|||||||
let option = options[i];
|
let option = options[i];
|
||||||
const flag = option[0];
|
const flag = option[0];
|
||||||
const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2);
|
const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2);
|
||||||
|
|
||||||
let r = wrap(option[1], indent);
|
let r = wrap(option[1], indent);
|
||||||
r = r.substr(flag.length + (INDENT + INDENT).length);
|
r = r.substr(flag.length + (INDENT + INDENT).length);
|
||||||
r = INDENT + INDENT + flag + r;
|
r = INDENT + INDENT + flag + r;
|
||||||
output.push(r);
|
output.push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCommand(cmd) {
|
function renderCommand(cmd) {
|
||||||
@@ -44,14 +44,14 @@ function renderCommand(cmd) {
|
|||||||
output.push('');
|
output.push('');
|
||||||
output.push(optionString);
|
output.push(optionString);
|
||||||
}
|
}
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCommands() {
|
function getCommands() {
|
||||||
let output = [];
|
let output = [];
|
||||||
fs.readdirSync(__dirname).forEach((path) => {
|
fs.readdirSync(__dirname).forEach(path => {
|
||||||
if (path.indexOf('command-') !== 0) return;
|
if (path.indexOf('command-') !== 0) return;
|
||||||
const ext = fileExtension(path)
|
const ext = fileExtension(path);
|
||||||
if (ext != 'js') return;
|
if (ext != 'js') return;
|
||||||
|
|
||||||
let CommandClass = require('./' + path);
|
let CommandClass = require('./' + path);
|
||||||
@@ -87,14 +87,14 @@ function getHeader() {
|
|||||||
let description = [];
|
let description = [];
|
||||||
description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.');
|
description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.');
|
||||||
description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.');
|
description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.');
|
||||||
description.push("\n\n");
|
description.push('\n\n');
|
||||||
description.push('The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive.');
|
description.push('The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive.');
|
||||||
description.push("\n\n");
|
description.push('\n\n');
|
||||||
description.push('Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).');
|
description.push('Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).');
|
||||||
|
|
||||||
output.push(wrap(description.join(''), INDENT));
|
output.push(wrap(description.join(''), INDENT));
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFooter() {
|
function getFooter() {
|
||||||
@@ -102,7 +102,7 @@ function getFooter() {
|
|||||||
|
|
||||||
output.push('WEBSITE');
|
output.push('WEBSITE');
|
||||||
output.push('');
|
output.push('');
|
||||||
output.push(INDENT + 'http://joplin.cozic.net');
|
output.push(INDENT + 'https://joplinapp.org');
|
||||||
|
|
||||||
output.push('');
|
output.push('');
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ function getFooter() {
|
|||||||
const licenseText = fs.readFileSync(filePath, 'utf8');
|
const licenseText = fs.readFileSync(filePath, 'utf8');
|
||||||
output.push(wrap(licenseText, INDENT));
|
output.push(wrap(licenseText, INDENT));
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -128,12 +128,12 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headerText = getHeader();
|
const headerText = getHeader();
|
||||||
const commandsText = commandBlocks.join("\n\n");
|
const commandsText = commandBlocks.join('\n\n');
|
||||||
const footerText = getFooter();
|
const footerText = getFooter();
|
||||||
|
|
||||||
console.info(headerText + "\n\n" + 'USAGE' + "\n\n" + commandsText + "\n\n" + footerText);
|
console.info(headerText + '\n\n' + 'USAGE' + '\n\n' + commandsText + '\n\n' + footerText);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
main().catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
"use strict"
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
@@ -10,7 +10,7 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const exec = require('child_process').exec
|
const exec = require('child_process').exec;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
console.error('Unhandled promise rejection', p, 'reason:', reason);
|
console.error('Unhandled promise rejection', p, 'reason:', reason);
|
||||||
@@ -32,8 +32,8 @@ db.setLogger(dbLogger);
|
|||||||
|
|
||||||
function createClient(id) {
|
function createClient(id) {
|
||||||
return {
|
return {
|
||||||
'id': id,
|
id: id,
|
||||||
'profileDir': baseDir + '/client' + id,
|
profileDir: baseDir + '/client' + id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,14 +72,7 @@ function assertEquals(expected, real) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clearDatabase() {
|
async function clearDatabase() {
|
||||||
await db.transactionExecBatch([
|
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
|
||||||
'DELETE FROM folders',
|
|
||||||
'DELETE FROM notes',
|
|
||||||
'DELETE FROM tags',
|
|
||||||
'DELETE FROM note_tags',
|
|
||||||
'DELETE FROM resources',
|
|
||||||
'DELETE FROM deleted_items',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testUnits = {};
|
const testUnits = {};
|
||||||
@@ -101,7 +94,7 @@ testUnits.testFolders = async () => {
|
|||||||
|
|
||||||
folders = await Folder.all();
|
folders = await Folder.all();
|
||||||
assertEquals(0, folders.length);
|
assertEquals(0, folders.length);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testNotes = async () => {
|
testUnits.testNotes = async () => {
|
||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
@@ -121,16 +114,16 @@ testUnits.testNotes = async () => {
|
|||||||
notes = await Note.all();
|
notes = await Note.all();
|
||||||
assertEquals(2, notes.length);
|
assertEquals(2, notes.length);
|
||||||
|
|
||||||
await execCommand(client, "rm -f 'blabla*'");
|
await execCommand(client, 'rm -f \'blabla*\'');
|
||||||
|
|
||||||
notes = await Note.all();
|
notes = await Note.all();
|
||||||
assertEquals(2, notes.length);
|
assertEquals(2, notes.length);
|
||||||
|
|
||||||
await execCommand(client, "rm -f 'n*'");
|
await execCommand(client, 'rm -f \'n*\'');
|
||||||
|
|
||||||
notes = await Note.all();
|
notes = await Note.all();
|
||||||
assertEquals(0, notes.length);
|
assertEquals(0, notes.length);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testCat = async () => {
|
testUnits.testCat = async () => {
|
||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
@@ -145,7 +138,7 @@ testUnits.testCat = async () => {
|
|||||||
|
|
||||||
r = await execCommand(client, 'cat -v mynote');
|
r = await execCommand(client, 'cat -v mynote');
|
||||||
assertTrue(r.indexOf(note.id) >= 0);
|
assertTrue(r.indexOf(note.id) >= 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testConfig = async () => {
|
testUnits.testConfig = async () => {
|
||||||
await execCommand(client, 'config editor vim');
|
await execCommand(client, 'config editor vim');
|
||||||
@@ -159,7 +152,7 @@ testUnits.testConfig = async () => {
|
|||||||
let r = await execCommand(client, 'config');
|
let r = await execCommand(client, 'config');
|
||||||
assertTrue(r.indexOf('editor') >= 0);
|
assertTrue(r.indexOf('editor') >= 0);
|
||||||
assertTrue(r.indexOf('subl') >= 0);
|
assertTrue(r.indexOf('subl') >= 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testCp = async () => {
|
testUnits.testCp = async () => {
|
||||||
await execCommand(client, 'mkbook nb2');
|
await execCommand(client, 'mkbook nb2');
|
||||||
@@ -180,7 +173,7 @@ testUnits.testCp = async () => {
|
|||||||
notes = await Note.previews(f2.id);
|
notes = await Note.previews(f2.id);
|
||||||
assertEquals(1, notes.length);
|
assertEquals(1, notes.length);
|
||||||
assertEquals(notesF1[0].title, notes[0].title);
|
assertEquals(notesF1[0].title, notes[0].title);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testLs = async () => {
|
testUnits.testLs = async () => {
|
||||||
await execCommand(client, 'mkbook nb1');
|
await execCommand(client, 'mkbook nb1');
|
||||||
@@ -190,7 +183,7 @@ testUnits.testLs = async () => {
|
|||||||
|
|
||||||
assertTrue(r.indexOf('note1') >= 0);
|
assertTrue(r.indexOf('note1') >= 0);
|
||||||
assertTrue(r.indexOf('note2') >= 0);
|
assertTrue(r.indexOf('note2') >= 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
testUnits.testMv = async () => {
|
testUnits.testMv = async () => {
|
||||||
await execCommand(client, 'mkbook nb2');
|
await execCommand(client, 'mkbook nb2');
|
||||||
@@ -210,14 +203,14 @@ testUnits.testMv = async () => {
|
|||||||
await execCommand(client, 'mknote note2');
|
await execCommand(client, 'mknote note2');
|
||||||
await execCommand(client, 'mknote note3');
|
await execCommand(client, 'mknote note3');
|
||||||
await execCommand(client, 'mknote blabla');
|
await execCommand(client, 'mknote blabla');
|
||||||
await execCommand(client, "mv 'note*' nb2");
|
await execCommand(client, 'mv \'note*\' nb2');
|
||||||
|
|
||||||
notes1 = await Note.previews(f1.id);
|
notes1 = await Note.previews(f1.id);
|
||||||
notes2 = await Note.previews(f2.id);
|
notes2 = await Note.previews(f2.id);
|
||||||
|
|
||||||
assertEquals(1, notes1.length);
|
assertEquals(1, notes1.length);
|
||||||
assertEquals(4, notes2.length);
|
assertEquals(4, notes2.length);
|
||||||
}
|
};
|
||||||
|
|
||||||
async function main(argv) {
|
async function main(argv) {
|
||||||
await fs.remove(baseDir);
|
await fs.remove(baseDir);
|
||||||
@@ -243,7 +236,7 @@ async function main(argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main(process.argv).catch((error) => {
|
main(process.argv).catch(error => {
|
||||||
console.info('');
|
console.info('');
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
});
|
});
|
||||||
|
@@ -16,7 +16,7 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
|
|||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
let row = rows[i];
|
let row = rows[i];
|
||||||
|
|
||||||
for (let j = 0; j < row.length; j++) {
|
for (let j = 0; j < row.length; j++) {
|
||||||
let item = row[j];
|
let item = row[j];
|
||||||
let width = item ? item.toString().length : 0;
|
let width = item ? item.toString().length : 0;
|
||||||
@@ -26,7 +26,6 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines = [];
|
|
||||||
for (let row = 0; row < rows.length; row++) {
|
for (let row = 0; row < rows.length; row++) {
|
||||||
let line = [];
|
let line = [];
|
||||||
for (let col = 0; col < colWidths.length; col++) {
|
for (let col = 0; col < colWidths.length; col++) {
|
||||||
@@ -37,7 +36,7 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
|
|||||||
}
|
}
|
||||||
logFunction(line.join(' '));
|
logFunction(line.join(' '));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.parseFlags = function(flags) {
|
cliUtils.parseFlags = function(flags) {
|
||||||
let output = {};
|
let output = {};
|
||||||
@@ -56,7 +55,7 @@ cliUtils.parseFlags = function(flags) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.parseCommandArg = function(arg) {
|
cliUtils.parseCommandArg = function(arg) {
|
||||||
if (arg.length <= 2) throw new Error('Invalid command arg: ' + arg);
|
if (arg.length <= 2) throw new Error('Invalid command arg: ' + arg);
|
||||||
@@ -72,7 +71,7 @@ cliUtils.parseCommandArg = function(arg) {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid command arg: ' + arg);
|
throw new Error('Invalid command arg: ' + arg);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.makeCommandArgs = function(cmd, argv) {
|
cliUtils.makeCommandArgs = function(cmd, argv) {
|
||||||
let cmdUsage = cmd.usage();
|
let cmdUsage = cmd.usage();
|
||||||
@@ -85,7 +84,6 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
|||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
if (options[i].length != 2) throw new Error('Invalid options: ' + options[i]);
|
if (options[i].length != 2) throw new Error('Invalid options: ' + options[i]);
|
||||||
let flags = options[i][0];
|
let flags = options[i][0];
|
||||||
let text = options[i][1];
|
|
||||||
|
|
||||||
flags = cliUtils.parseFlags(flags);
|
flags = cliUtils.parseFlags(flags);
|
||||||
|
|
||||||
@@ -125,27 +123,27 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
|||||||
output.options = argOptions;
|
output.options = argOptions;
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.promptMcq = function(message, answers) {
|
cliUtils.promptMcq = function(message, answers) {
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
message += "\n\n";
|
message += '\n\n';
|
||||||
for (let n in answers) {
|
for (let n in answers) {
|
||||||
if (!answers.hasOwnProperty(n)) continue;
|
if (!answers.hasOwnProperty(n)) continue;
|
||||||
message += _('%s: %s', n, answers[n]) + "\n";
|
message += _('%s: %s', n, answers[n]) + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
message += "\n";
|
message += '\n';
|
||||||
message += _('Your choice: ');
|
message += _('Your choice: ');
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
rl.question(message, (answer) => {
|
rl.question(message, answer => {
|
||||||
rl.close();
|
rl.close();
|
||||||
|
|
||||||
if (!(answer in answers)) {
|
if (!(answer in answers)) {
|
||||||
@@ -156,7 +154,7 @@ cliUtils.promptMcq = function(message, answers) {
|
|||||||
resolve(answer);
|
resolve(answer);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.promptConfirm = function(message, answers = null) {
|
cliUtils.promptConfirm = function(message, answers = null) {
|
||||||
if (!answers) answers = [_('Y'), _('n')];
|
if (!answers) answers = [_('Y'), _('n')];
|
||||||
@@ -164,19 +162,19 @@ cliUtils.promptConfirm = function(message, answers = null) {
|
|||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
message += ' (' + answers.join('/') + ')';
|
message += ' (' + answers.join('/') + ')';
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
rl.question(message + ' ', (answer) => {
|
rl.question(message + ' ', answer => {
|
||||||
const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase();
|
const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase();
|
||||||
rl.close();
|
rl.close();
|
||||||
resolve(ok);
|
resolve(ok);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
// Note: initialText is there to have the same signature as statusBar.prompt() so that
|
// Note: initialText is there to have the same signature as statusBar.prompt() so that
|
||||||
// it can be a drop-in replacement, however initialText is not used (and cannot be
|
// it can be a drop-in replacement, however initialText is not used (and cannot be
|
||||||
@@ -189,10 +187,9 @@ cliUtils.prompt = function(initialText = '', promptString = ':', options = null)
|
|||||||
|
|
||||||
const mutableStdout = new Writable({
|
const mutableStdout = new Writable({
|
||||||
write: function(chunk, encoding, callback) {
|
write: function(chunk, encoding, callback) {
|
||||||
if (!this.muted)
|
if (!this.muted) process.stdout.write(chunk, encoding);
|
||||||
process.stdout.write(chunk, encoding);
|
|
||||||
callback();
|
callback();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
@@ -204,15 +201,15 @@ cliUtils.prompt = function(initialText = '', promptString = ':', options = null)
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
mutableStdout.muted = false;
|
mutableStdout.muted = false;
|
||||||
|
|
||||||
rl.question(promptString, (answer) => {
|
rl.question(promptString, answer => {
|
||||||
rl.close();
|
rl.close();
|
||||||
if (!!options.secure) this.stdout_('');
|
if (options.secure) this.stdout_('');
|
||||||
resolve(answer);
|
resolve(answer);
|
||||||
});
|
});
|
||||||
|
|
||||||
mutableStdout.muted = !!options.secure;
|
mutableStdout.muted = !!options.secure;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let redrawStarted_ = false;
|
let redrawStarted_ = false;
|
||||||
let redrawLastLog_ = null;
|
let redrawLastLog_ = null;
|
||||||
@@ -220,7 +217,7 @@ let redrawLastUpdateTime_ = 0;
|
|||||||
|
|
||||||
cliUtils.setStdout = function(v) {
|
cliUtils.setStdout = function(v) {
|
||||||
this.stdout_ = v;
|
this.stdout_ = v;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.redraw = function(s) {
|
cliUtils.redraw = function(s) {
|
||||||
const now = time.unixMs();
|
const now = time.unixMs();
|
||||||
@@ -233,8 +230,8 @@ cliUtils.redraw = function(s) {
|
|||||||
redrawLastLog_ = s;
|
redrawLastLog_ = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
redrawStarted_ = true;
|
redrawStarted_ = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
cliUtils.redrawDone = function() {
|
cliUtils.redrawDone = function() {
|
||||||
if (!redrawStarted_) return;
|
if (!redrawStarted_) return;
|
||||||
@@ -245,6 +242,6 @@ cliUtils.redrawDone = function() {
|
|||||||
|
|
||||||
redrawLastLog_ = null;
|
redrawLastLog_ = null;
|
||||||
redrawStarted_ = false;
|
redrawStarted_ = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = { cliUtils };
|
module.exports = { cliUtils };
|
||||||
|
295
CliClient/app/command-apidoc.js
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
|
const BaseModel = require('lib/BaseModel');
|
||||||
|
const { toTitleCase } = require('lib/string-utils.js');
|
||||||
|
const { reg } = require('lib/registry.js');
|
||||||
|
const markdownUtils = require('lib/markdownUtils');
|
||||||
|
const { Database } = require('lib/database.js');
|
||||||
|
|
||||||
|
class Command extends BaseCommand {
|
||||||
|
usage() {
|
||||||
|
return 'apidoc';
|
||||||
|
}
|
||||||
|
|
||||||
|
description() {
|
||||||
|
return 'Build the API doc';
|
||||||
|
}
|
||||||
|
|
||||||
|
createPropertiesTable(tableFields) {
|
||||||
|
const headers = [
|
||||||
|
{ name: 'name', label: 'Name' },
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
label: 'Type',
|
||||||
|
filter: value => {
|
||||||
|
return Database.enumName('fieldType', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ name: 'description', label: 'Description' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return markdownUtils.createMarkdownTable(headers, tableFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
async action(args) {
|
||||||
|
const models = [
|
||||||
|
{
|
||||||
|
type: BaseModel.TYPE_NOTE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: BaseModel.TYPE_FOLDER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: BaseModel.TYPE_RESOURCE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: BaseModel.TYPE_TAG,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
lines.push('# Joplin API');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('When the Web Clipper service is enabled, Joplin exposes a [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) which allows third-party applications to access Joplin\'s data and to create, modify or delete notes, notebooks, resources or tags.');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('```javascript');
|
||||||
|
lines.push('let port = null;');
|
||||||
|
lines.push('for (let portToTest = 41184; portToTest <= 41194; portToTest++) {');
|
||||||
|
lines.push(' const result = pingPort(portToTest); // Call GET /ping');
|
||||||
|
lines.push(' if (result == \'JoplinClipperServer\') {');
|
||||||
|
lines.push(' port = portToTest; // Found the port');
|
||||||
|
lines.push(' break;');
|
||||||
|
lines.push(' }');
|
||||||
|
lines.push('}');
|
||||||
|
lines.push('```');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# Authorisation');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('To prevent unauthorised applications from accessing the API, the calls must be authentified. To do so, you must provide a token as a query parameter for each API call. You can get this token from the Joplin desktop application, on the Web Clipper Options screen.');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('This would be an example of valid cURL call using a token:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl http://localhost:41184/notes?token=ABCD123ABCD123ABCD123ABCD123ABCD123');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('In the documentation below, the token will not be specified every time however you will need to include it.');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# Using the API');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('All the calls, unless noted otherwise, receives and send **JSON data**. For example to create a new note:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl --data \'{ "title": "My note", "body": "Some note in **Markdown**"}\' http://localhost:41184/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('In the documentation below, the calls may include special parameters such as :id or :note_id. You would replace this with the item ID or note ID.');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('For example, for the endpoint `DELETE /tags/:id/notes/:note_id`, to remove the tag with ID "ABCD1234" from the note with ID "EFGH789", you would run for example:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl -X DELETE http://localhost:41184/tags/ABCD1234/notes/EFGH789');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('The four verbs supported by the API are the following ones:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('* **GET**: To retrieve items (notes, notebooks, etc.).');
|
||||||
|
lines.push('* **POST**: To create new items. In general most item properties are optional. If you omit any, a default value will be used.');
|
||||||
|
lines.push('* **PUT**: To update an item. Note in a REST API, traditionally PUT is used to completely replace an item, however in this API it will only replace the properties that are provided. For example if you PUT {"title": "my new title"}, only the "title" property will be changed. The other properties will be left untouched (they won\'t be cleared nor changed).');
|
||||||
|
lines.push('* **DELETE**: To delete items.');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# Filtering data');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('You can change the fields that will be returned by the API using the `fields=` query parameter, which takes a list of comma separated fields. For example, to get the longitude and latitude of a note, use this:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl http://localhost:41184/notes/ABCD123?fields=longitude,latitude');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('To get the IDs only of all the tags:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl http://localhost:41184/tags?fields=id');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# Error handling');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('In case of an error, an HTTP status code >= 400 will be returned along with a JSON object that provides more info about the error. The JSON object is in the format `{ "error": "description of error" }`.');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# About the property types');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('* Text is UTF-8.');
|
||||||
|
lines.push('* All date/time are Unix timestamps in milliseconds.');
|
||||||
|
lines.push('* Booleans are integer values 0 or 1.');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# Testing if the service is available');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Call **GET /ping** to check if the service is available. It should return "JoplinClipperServer" if it works.');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('# Searching');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Call **GET /search?query=YOUR_QUERY** to search for notes. This end-point supports the `field` parameter which is recommended to use so that you only get the data that you need. The query syntax is as described in the main documentation: https://joplinapp.org/#searching');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
for (let i = 0; i < models.length; i++) {
|
||||||
|
const model = models[i];
|
||||||
|
const ModelClass = BaseItem.getClassByItemType(model.type);
|
||||||
|
const tableName = ModelClass.tableName();
|
||||||
|
let tableFields = reg.db().tableFields(tableName, { includeDescription: true });
|
||||||
|
const singular = tableName.substr(0, tableName.length - 1);
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_NOTE) {
|
||||||
|
tableFields = tableFields.slice();
|
||||||
|
tableFields.push({
|
||||||
|
name: 'body_html',
|
||||||
|
type: Database.enumId('fieldType', 'text'),
|
||||||
|
description: 'Note body, in HTML format',
|
||||||
|
});
|
||||||
|
tableFields.push({
|
||||||
|
name: 'base_url',
|
||||||
|
type: Database.enumId('fieldType', 'text'),
|
||||||
|
description: 'If `body_html` is provided and contains relative URLs, provide the `base_url` parameter too so that all the URLs can be converted to absolute ones. The base URL is basically where the HTML was fetched from, minus the query (everything after the \'?\'). For example if the original page was `https://stackoverflow.com/search?q=%5Bjava%5D+test`, the base URL is `https://stackoverflow.com/search`.',
|
||||||
|
});
|
||||||
|
tableFields.push({
|
||||||
|
name: 'image_data_url',
|
||||||
|
type: Database.enumId('fieldType', 'text'),
|
||||||
|
description: 'An image to attach to the note, in [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.',
|
||||||
|
});
|
||||||
|
tableFields.push({
|
||||||
|
name: 'crop_rect',
|
||||||
|
type: Database.enumId('fieldType', 'text'),
|
||||||
|
description: 'If an image is provided, you can also specify an optional rectangle that will be used to crop the image. In format `{ x: x, y: y, width: width, height: height }`',
|
||||||
|
});
|
||||||
|
// tableFields.push({
|
||||||
|
// name: 'tags',
|
||||||
|
// type: Database.enumId('fieldType', 'text'),
|
||||||
|
// description: 'Comma-separated list of tags. eg. `tag1,tag2`.',
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('# ' + toTitleCase(tableName));
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||||
|
lines.push('This is actually a notebook. Internally notebooks are called "folders".');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('## Properties');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(this.createPropertiesTable(tableFields));
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('## GET /' + tableName);
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets all ' + tableName);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||||
|
lines.push('The folders are returned as a tree. The sub-notebooks of a notebook, if any, are under the `children` key.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('## GET /' + tableName + '/:id');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets ' + singular + ' with ID :id');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_TAG) {
|
||||||
|
lines.push('## GET /tags/:id/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets all the notes with this tag.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_NOTE) {
|
||||||
|
lines.push('## GET /notes/:id/tags');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets all the tags attached to this note.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||||
|
lines.push('## GET /folders/:id/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets all the notes inside this folder.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_RESOURCE) {
|
||||||
|
lines.push('## GET /resources/:id/file');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Gets the actual file associated with this resource.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('## POST /' + tableName);
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Creates a new ' + singular);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_RESOURCE) {
|
||||||
|
lines.push('Creating a new resource is special because you also need to upload the file. Unlike other API calls, this one must have the "multipart/form-data" Content-Type. The file data must be passed to the "data" form field, and the other properties to the "props" form field. An example of a valid call with cURL would be:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_TAG) {
|
||||||
|
lines.push('## POST /tags/:id/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Post a note to this endpoint to add the tag to the note. The note data must at least contain an ID property (all other properties will be ignored).');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_NOTE) {
|
||||||
|
lines.push('You can either specify the note body as Markdown by setting the `body` parameter, or in HTML by setting the `body_html`.');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Examples:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('* Create a note from some Markdown text');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(' curl --data \'{ "title": "My note", "body": "Some note in **Markdown**"}\' http://127.0.0.1:41184/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('* Create a note from some HTML');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(' curl --data \'{ "title": "My note", "body_html": "Some note in <b>HTML</b>"}\' http://127.0.0.1:41184/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('* Create a note and attach an image to it:');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(' curl --data \'{ "title": "Image test", "body": "Here is Joplin icon:", "image_data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII="}\' http://127.0.0.1:41184/notes');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('### Creating a note with a specific ID');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('When a new note is created, it is automatically assigned a new unique ID so **normally you do not need to set the ID**. However, if for some reason you want to set it, you can supply it as the `id` property. It needs to be a 32 characters long hexadecimal string. **Make sure it is unique**, for example by generating it using whatever GUID function is available in your programming language.');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(' curl --data \'{ "id": "00a87474082744c1a8515da6aa5792d2", "title": "My note with custom ID"}\' http://127.0.0.1:41184/notes');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('## PUT /' + tableName + '/:id');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Sets the properties of the ' + singular + ' with ID :id');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('## DELETE /' + tableName + '/:id');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Deletes the ' + singular + ' with ID :id');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (model.type === BaseModel.TYPE_TAG) {
|
||||||
|
lines.push('## DELETE /tags/:id/notes/:note_id');
|
||||||
|
lines.push('');
|
||||||
|
lines.push('Remove the tag from the note.');
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stdout(lines.join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Command;
|
@@ -3,10 +3,8 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { shim } = require('lib/shim.js');
|
const { shim } = require('lib/shim.js');
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'attach <note> <file>';
|
return 'attach <note> <file>';
|
||||||
}
|
}
|
||||||
@@ -26,7 +24,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
await shim.attachFileToNote(note, localFilePath);
|
await shim.attachFileToNote(note, localFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'cat <note>';
|
return 'cat <note>';
|
||||||
}
|
}
|
||||||
@@ -16,9 +14,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-v, --verbose', _('Displays the complete information about note.')]];
|
||||||
['-v, --verbose', _('Displays the complete information about note.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -30,10 +26,13 @@ class Command extends BaseCommand {
|
|||||||
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);
|
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);
|
||||||
this.stdout(content);
|
this.stdout(content);
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -4,36 +4,33 @@ const { app } = require('./app.js');
|
|||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'config [name] [value]';
|
return 'config [name] [value]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _("Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.");
|
return _('Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-v, --verbose', _('Also displays unset and hidden config variables.')]];
|
||||||
['-v, --verbose', _('Also displays unset and hidden config variables.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
const verbose = args.options.verbose;
|
const verbose = args.options.verbose;
|
||||||
|
|
||||||
const renderKeyValue = (name) => {
|
const renderKeyValue = name => {
|
||||||
const md = Setting.settingMetadata(name);
|
const md = Setting.settingMetadata(name);
|
||||||
let value = Setting.value(name);
|
let value = Setting.value(name);
|
||||||
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
||||||
if (md.secure) value = '********';
|
if (md.secure && value) value = '********';
|
||||||
|
|
||||||
if (Setting.isEnum(name)) {
|
if (Setting.isEnum(name)) {
|
||||||
return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
|
return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
|
||||||
} else {
|
} else {
|
||||||
return _('%s = %s', name, value);
|
return _('%s = %s', name, value);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!args.name && !args.value) {
|
if (!args.name && !args.value) {
|
||||||
let keys = Setting.keys(!verbose, 'cli');
|
let keys = Setting.keys(!verbose, 'cli');
|
||||||
@@ -43,15 +40,23 @@ class Command extends BaseCommand {
|
|||||||
if (!verbose && !value) continue;
|
if (!verbose && !value) continue;
|
||||||
this.stdout(renderKeyValue(keys[i]));
|
this.stdout(renderKeyValue(keys[i]));
|
||||||
}
|
}
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.name && !args.value) {
|
if (args.name && !args.value) {
|
||||||
this.stdout(renderKeyValue(args.name));
|
this.stdout(renderKeyValue(args.name));
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +69,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
await Setting.saveAll();
|
await Setting.saveAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'cp <note> [notebook]';
|
return 'cp <note> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -33,7 +31,6 @@ class Command extends BaseCommand {
|
|||||||
Note.updateGeolocation(newNote.id);
|
Note.updateGeolocation(newNote.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,12 +2,10 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'done <note>';
|
return 'done <note>';
|
||||||
}
|
}
|
||||||
@@ -35,7 +33,6 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
await Command.handleAction(this, args, true);
|
await Command.handleAction(this, args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,12 +1,9 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'dump';
|
return 'dump';
|
||||||
}
|
}
|
||||||
@@ -35,10 +32,9 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
items = items.concat(tags);
|
items = items.concat(tags);
|
||||||
|
|
||||||
this.stdout(JSON.stringify(items));
|
this.stdout(JSON.stringify(items));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,20 +1,21 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
const EncryptionService = require('lib/services/EncryptionService');
|
const EncryptionService = require('lib/services/EncryptionService');
|
||||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||||
const MasterKey = require('lib/models/MasterKey');
|
|
||||||
const BaseItem = require('lib/models/BaseItem');
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
const { shim } = require('lib/shim');
|
||||||
|
const pathUtils = require('lib/path-utils.js');
|
||||||
|
const imageType = require('image-type');
|
||||||
|
const readChunk = require('read-chunk');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'e2ee <command> [path]';
|
return 'e2ee <command> [path]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.');
|
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file` and `target-status`.');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
@@ -22,6 +23,7 @@ class Command extends BaseCommand {
|
|||||||
// This is here mostly for testing - shouldn't be used
|
// This is here mostly for testing - shouldn't be used
|
||||||
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
||||||
['-v, --verbose', 'More verbose output for the `target-status` command'],
|
['-v, --verbose', 'More verbose output for the `target-status` command'],
|
||||||
|
['-o, --output <directory>', 'Output directory'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +32,18 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
|
const askForMasterKey = async error => {
|
||||||
|
const masterKeyId = error.masterKeyId;
|
||||||
|
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
|
if (!password) {
|
||||||
|
this.stdout(_('Operation cancelled'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||||
|
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
if (args.command === 'enable') {
|
if (args.command === 'enable') {
|
||||||
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
if (!password) {
|
if (!password) {
|
||||||
@@ -47,30 +61,29 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.command === 'decrypt') {
|
if (args.command === 'decrypt') {
|
||||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
if (args.path) {
|
||||||
|
const plainText = await EncryptionService.instance().decryptString(args.path);
|
||||||
|
this.stdout(plainText);
|
||||||
|
} else {
|
||||||
|
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await DecryptionWorker.instance().start();
|
await DecryptionWorker.instance().start();
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'masterKeyNotLoaded') {
|
if (error.code === 'masterKeyNotLoaded') {
|
||||||
const masterKeyId = error.masterKeyId;
|
const ok = await askForMasterKey(error);
|
||||||
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
if (!ok) return;
|
||||||
if (!password) {
|
continue;
|
||||||
this.stdout(_('Operation cancelled'));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
|
||||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
throw error;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.stdout(_('Completed decryption.'));
|
this.stdout(_('Completed decryption.'));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -80,21 +93,49 @@ class Command extends BaseCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.command === 'decrypt-file') {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const outputDir = options.output ? options.output : require('os').tmpdir();
|
||||||
|
let outFile = outputDir + '/' + pathUtils.filename(args.path) + '.' + Date.now() + '.bin';
|
||||||
|
await EncryptionService.instance().decryptFile(args.path, outFile);
|
||||||
|
const buffer = await readChunk(outFile, 0, 64);
|
||||||
|
const detectedType = imageType(buffer);
|
||||||
|
|
||||||
|
if (detectedType) {
|
||||||
|
const newOutFile = outFile + '.' + detectedType.ext;
|
||||||
|
await shim.fsDriver().move(outFile, newOutFile);
|
||||||
|
outFile = newOutFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stdout(outFile);
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'masterKeyNotLoaded') {
|
||||||
|
const ok = await askForMasterKey(error);
|
||||||
|
if (!ok) return;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.command === 'target-status') {
|
if (args.command === 'target-status') {
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const pathUtils = require('lib/path-utils.js');
|
|
||||||
const fsDriver = new (require('lib/fs-driver-node.js').FsDriverNode)();
|
|
||||||
|
|
||||||
const targetPath = args.path;
|
const targetPath = args.path;
|
||||||
if (!targetPath) throw new Error('Please specify the sync target path.');
|
if (!targetPath) throw new Error('Please specify the sync target path.');
|
||||||
|
|
||||||
const dirPaths = function(targetPath) {
|
const dirPaths = function(targetPath) {
|
||||||
let paths = [];
|
let paths = [];
|
||||||
fs.readdirSync(targetPath).forEach((path) => {
|
fs.readdirSync(targetPath).forEach(path => {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
});
|
});
|
||||||
return paths;
|
return paths;
|
||||||
}
|
};
|
||||||
|
|
||||||
let itemCount = 0;
|
let itemCount = 0;
|
||||||
let resourceCount = 0;
|
let resourceCount = 0;
|
||||||
@@ -178,7 +219,6 @@ class Command extends BaseCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,15 +3,11 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { uuid } = require('lib/uuid.js');
|
const { uuid } = require('lib/uuid.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
const { time } = require('lib/time-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'edit <note>';
|
return 'edit <note>';
|
||||||
}
|
}
|
||||||
@@ -21,20 +17,19 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let watcher = null;
|
|
||||||
let tempFilePath = null;
|
let tempFilePath = null;
|
||||||
|
|
||||||
const onFinishedEditing = async () => {
|
const onFinishedEditing = async () => {
|
||||||
if (tempFilePath) fs.removeSync(tempFilePath);
|
if (tempFilePath) fs.removeSync(tempFilePath);
|
||||||
}
|
};
|
||||||
|
|
||||||
const textEditorPath = () => {
|
const textEditorPath = () => {
|
||||||
if (Setting.value('editor')) return Setting.value('editor');
|
if (Setting.value('editor')) return Setting.value('editor');
|
||||||
if (process.env.EDITOR) return process.env.EDITOR;
|
if (process.env.EDITOR) return process.env.EDITOR;
|
||||||
throw new Error(_('No text editor is defined. Please set it using `config editor <editor-path>`'));
|
throw new Error(_('No text editor is defined. Please set it using `config editor <editor-path>`'));
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Load note or create it if it doesn't exist
|
// Load note or create it if it doesn't exist
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -76,18 +71,30 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
this.logger().info('Disabling fullscreen...');
|
this.logger().info('Disabling fullscreen...');
|
||||||
|
|
||||||
app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
|
app()
|
||||||
await app().gui().forceRender();
|
.gui()
|
||||||
const termState = app().gui().termSaveState();
|
.showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
|
||||||
|
await app()
|
||||||
|
.gui()
|
||||||
|
.forceRender();
|
||||||
|
const termState = app()
|
||||||
|
.gui()
|
||||||
|
.termSaveState();
|
||||||
|
|
||||||
const spawnSync = require('child_process').spawnSync;
|
const spawnSync = require('child_process').spawnSync;
|
||||||
const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
||||||
|
|
||||||
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
|
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
|
||||||
|
|
||||||
app().gui().termRestoreState(termState);
|
app()
|
||||||
app().gui().hideModalOverlay();
|
.gui()
|
||||||
app().gui().forceRender();
|
.termRestoreState(termState);
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.hideModalOverlay();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.forceRender();
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Save the note and clean up
|
// Save the note and clean up
|
||||||
@@ -107,13 +114,11 @@ class Command extends BaseCommand {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await onFinishedEditing();
|
await onFinishedEditing();
|
||||||
|
} catch (error) {
|
||||||
} catch(error) {
|
|
||||||
await onFinishedEditing();
|
await onFinishedEditing();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,7 +3,6 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'exit';
|
return 'exit';
|
||||||
}
|
}
|
||||||
@@ -19,7 +18,6 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
await app().exit();
|
await app().exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { _ } = require('lib/locale.js');
|
|
||||||
const { ReportService } = require('lib/services/report.js');
|
const { ReportService } = require('lib/services/report.js');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'export-sync-status';
|
return 'export-sync-status';
|
||||||
}
|
}
|
||||||
@@ -23,14 +20,17 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
const service = new ReportService();
|
const service = new ReportService();
|
||||||
const csv = await service.basicItemList({ format: 'csv' });
|
const csv = await service.basicItemList({ format: 'csv' });
|
||||||
const filePath = Setting.value('profileDir') + '/syncReport-' + (new Date()).getTime() + '.csv';
|
const filePath = Setting.value('profileDir') + '/syncReport-' + new Date().getTime() + '.csv';
|
||||||
await fs.writeFileSync(filePath, csv);
|
await fs.writeFileSync(filePath, csv);
|
||||||
this.stdout('Sync status exported to ' + filePath);
|
this.stdout('Sync status exported to ' + filePath);
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,59 +1,49 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Exporter } = require('lib/services/exporter.js');
|
const InteropService = require('lib/services/InteropService.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Note = require('lib/models/Note.js');
|
|
||||||
const { reg } = require('lib/registry.js');
|
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'export <directory>';
|
return 'export <path>';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _('Exports Joplin data to the given directory. By default, it will export the complete database including notebooks, notes, tags and resources.');
|
return _('Exports Joplin data to the given path. By default, it will export the complete database including notebooks, notes, tags and resources.');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
const service = new InteropService();
|
||||||
['--note <note>', _('Exports only the given note.')],
|
const formats = service
|
||||||
['--notebook <notebook>', _('Exports only the given notebook.')],
|
.modules()
|
||||||
];
|
.filter(m => m.type === 'exporter')
|
||||||
|
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
|
||||||
|
|
||||||
|
return [['--format <format>', _('Destination format: %s', formats.join(', '))], ['--note <note>', _('Exports only the given note.')], ['--notebook <notebook>', _('Exports only the given notebook.')]];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let exportOptions = {};
|
let exportOptions = {};
|
||||||
exportOptions.destDir = args.directory;
|
exportOptions.path = args.path;
|
||||||
exportOptions.writeFile = (filePath, data) => {
|
|
||||||
return fs.writeFile(filePath, data);
|
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
||||||
};
|
|
||||||
exportOptions.copyFile = (source, dest) => {
|
|
||||||
return fs.copy(source, dest, { overwrite: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (args.options.note) {
|
if (args.options.note) {
|
||||||
|
|
||||||
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() });
|
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() });
|
||||||
if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note));
|
if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note));
|
||||||
exportOptions.sourceNoteIds = notes.map((n) => n.id);
|
exportOptions.sourceNoteIds = notes.map(n => n.id);
|
||||||
|
|
||||||
} else if (args.options.notebook) {
|
} else if (args.options.notebook) {
|
||||||
|
|
||||||
const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook);
|
const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook);
|
||||||
if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook));
|
if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook));
|
||||||
exportOptions.sourceFolderIds = folders.map((n) => n.id);
|
exportOptions.sourceFolderIds = folders.map(n => n.id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const exporter = new Exporter();
|
const service = new InteropService();
|
||||||
const result = await exporter.export(exportOptions);
|
const result = await service.export(exportOptions);
|
||||||
|
|
||||||
reg.logger().info('Export result: ', result);
|
result.warnings.map(w => this.stdout(w));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'geoloc <note>';
|
return 'geoloc <note>';
|
||||||
}
|
}
|
||||||
@@ -23,9 +21,10 @@ class Command extends BaseCommand {
|
|||||||
const url = Note.geolocationUrl(item);
|
const url = Note.geolocationUrl(item);
|
||||||
this.stdout(url);
|
this.stdout(url);
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
|
.gui()
|
||||||
|
.showConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { renderCommandHelp } = require('./help-utils.js');
|
const { renderCommandHelp } = require('./help-utils.js');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const Setting = require('lib/models/Setting.js');
|
|
||||||
const { wrap } = require('lib/string-utils.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'help [command]';
|
return 'help [command]';
|
||||||
}
|
}
|
||||||
@@ -28,7 +24,7 @@ class Command extends BaseCommand {
|
|||||||
output.push(command);
|
output.push(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
output.sort((a, b) => a.name() < b.name() ? -1 : +1);
|
output.sort((a, b) => (a.name() < b.name() ? -1 : +1));
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -37,34 +33,40 @@ class Command extends BaseCommand {
|
|||||||
const stdoutWidth = app().commandStdoutMaxWidth();
|
const stdoutWidth = app().commandStdoutMaxWidth();
|
||||||
|
|
||||||
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
||||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'http://joplin.cozic.net/terminal/#shortcuts'));
|
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplinapp.org/terminal/#shortcuts'));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
|
|
||||||
if (app().gui().isDummy()) {
|
if (
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.isDummy()
|
||||||
|
) {
|
||||||
throw new Error(_('Shortcuts are not available in CLI mode.'));
|
throw new Error(_('Shortcuts are not available in CLI mode.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const keymap = app().gui().keymap();
|
const keymap = app()
|
||||||
|
.gui()
|
||||||
|
.keymap();
|
||||||
|
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let i = 0; i < keymap.length; i++) {
|
for (let i = 0; i < keymap.length; i++) {
|
||||||
const item = keymap[i];
|
const item = keymap[i];
|
||||||
const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k);
|
const keys = item.keys.map(k => (k === ' ' ? '(SPACE)' : k));
|
||||||
rows.push([keys.join(', '), item.command]);
|
rows.push([keys.join(', '), item.command]);
|
||||||
}
|
}
|
||||||
|
|
||||||
cliUtils.printArray(this.stdout.bind(this), rows);
|
cliUtils.printArray(this.stdout.bind(this), rows);
|
||||||
} else if (args.command === 'all') {
|
} else if (args.command === 'all') {
|
||||||
const commands = this.allCommands();
|
const commands = this.allCommands();
|
||||||
const output = commands.map((c) => renderCommandHelp(c));
|
const output = commands.map(c => renderCommandHelp(c));
|
||||||
this.stdout(output.join('\n\n'));
|
this.stdout(output.join('\n\n'));
|
||||||
} else if (args.command) {
|
} else if (args.command) {
|
||||||
const command = app().findCommandByName(args['command']);
|
const command = app().findCommandByName(args['command']);
|
||||||
if (!command) throw new Error(_('Cannot find "%s".', args.command));
|
if (!command) throw new Error(_('Cannot find "%s".', args.command));
|
||||||
this.stdout(renderCommandHelp(command, stdoutWidth));
|
this.stdout(renderCommandHelp(command, stdoutWidth));
|
||||||
} else {
|
} else {
|
||||||
const commandNames = this.allCommands().map((a) => a.name());
|
const commandNames = this.allCommands().map(a => a.name());
|
||||||
|
|
||||||
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
|
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
@@ -72,20 +74,23 @@ class Command extends BaseCommand {
|
|||||||
this.stdout('');
|
this.stdout('');
|
||||||
this.stdout(commandNames.join(', '));
|
this.stdout(commandNames.join(', '));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
this.stdout(_('In any command, a note or notebook can be refered to by title or ID, or using the shortcuts `$n` or `$b` for, respectively, the currently selected note or notebook. `$c` can be used to refer to the currently selected item.'));
|
this.stdout(_('In any command, a note or notebook can be referred to by title or ID, or using the shortcuts `$n` or `$b` for, respectively, the currently selected note or notebook. `$c` can be used to refer to the currently selected item.'));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
this.stdout(_('To move from one pane to another, press Tab or Shift+Tab.'));
|
this.stdout(_('To move from one pane to another, press Tab or Shift+Tab.'));
|
||||||
this.stdout(_('Use the arrows and page up/down to scroll the lists and text areas (including this console).'));
|
this.stdout(_('Use the arrows and page up/down to scroll the lists and text areas (including this console).'));
|
||||||
this.stdout(_('To maximise/minimise the console, press "TC".'));
|
this.stdout(_('To maximise/minimise the console, press "tc".'));
|
||||||
this.stdout(_('To enter command line mode, press ":"'));
|
this.stdout(_('To enter command line mode, press ":"'));
|
||||||
this.stdout(_('To exit command line mode, press ESCAPE'));
|
this.stdout(_('To exit command line mode, press ESCAPE'));
|
||||||
this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`'));
|
this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`'));
|
||||||
}
|
}
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,68 +0,0 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
|
||||||
const { app } = require('./app.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const { importEnex } = require('lib/import-enex');
|
|
||||||
const { filename, basename } = require('lib/path-utils.js');
|
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
return 'import-enex <file> [notebook]';
|
|
||||||
}
|
|
||||||
|
|
||||||
description() {
|
|
||||||
return _('Imports an Evernote notebook file (.enex file).');
|
|
||||||
}
|
|
||||||
|
|
||||||
options() {
|
|
||||||
return [
|
|
||||||
['-f, --force', _('Do not ask for confirmation.')],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
async action(args) {
|
|
||||||
let filePath = args.file;
|
|
||||||
let folder = null;
|
|
||||||
let folderTitle = args['notebook'];
|
|
||||||
let force = args.options.force === true;
|
|
||||||
|
|
||||||
if (!folderTitle) folderTitle = filename(filePath);
|
|
||||||
folder = await Folder.loadByField('title', folderTitle);
|
|
||||||
const msg = folder ? _('File "%s" will be imported into existing notebook "%s". Continue?', basename(filePath), folderTitle) : _('New notebook "%s" will be created and file "%s" will be imported into it. Continue?', folderTitle, basename(filePath));
|
|
||||||
const ok = force ? true : await this.prompt(msg);
|
|
||||||
if (!ok) return;
|
|
||||||
|
|
||||||
let lastProgress = '';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
onProgress: (progressState) => {
|
|
||||||
let line = [];
|
|
||||||
line.push(_('Found: %d.', progressState.loaded));
|
|
||||||
line.push(_('Created: %d.', progressState.created));
|
|
||||||
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
|
||||||
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
|
|
||||||
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
|
||||||
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
|
|
||||||
lastProgress = line.join(' ');
|
|
||||||
cliUtils.redraw(lastProgress);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
let s = error.trace ? error.trace : error.toString();
|
|
||||||
this.stdout(s);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
folder = !folder ? await Folder.save({ title: folderTitle }) : folder;
|
|
||||||
|
|
||||||
app().gui().showConsole();
|
|
||||||
this.stdout(_('Importing notes...'));
|
|
||||||
await importEnex(folder.id, filePath, options);
|
|
||||||
cliUtils.redrawDone();
|
|
||||||
this.stdout(_('The notes have been imported: %s', lastProgress));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Command;
|
|
70
CliClient/app/command-import.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const InteropService = require('lib/services/InteropService.js');
|
||||||
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
const { app } = require('./app.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
|
class Command extends BaseCommand {
|
||||||
|
usage() {
|
||||||
|
return 'import <path> [notebook]';
|
||||||
|
}
|
||||||
|
|
||||||
|
description() {
|
||||||
|
return _('Imports data into Joplin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
options() {
|
||||||
|
const service = new InteropService();
|
||||||
|
const formats = service
|
||||||
|
.modules()
|
||||||
|
.filter(m => m.type === 'importer')
|
||||||
|
.map(m => m.format);
|
||||||
|
|
||||||
|
return [['--format <format>', _('Source format: %s', ['auto'].concat(formats).join(', '))], ['-f, --force', _('Do not ask for confirmation.')]];
|
||||||
|
}
|
||||||
|
|
||||||
|
async action(args) {
|
||||||
|
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
|
||||||
|
|
||||||
|
if (args.notebook && !folder) throw new Error(_('Cannot find "%s".', args.notebook));
|
||||||
|
|
||||||
|
const importOptions = {};
|
||||||
|
importOptions.path = args.path;
|
||||||
|
importOptions.format = args.options.format ? args.options.format : 'auto';
|
||||||
|
importOptions.destinationFolderId = folder ? folder.id : null;
|
||||||
|
|
||||||
|
let lastProgress = '';
|
||||||
|
|
||||||
|
// onProgress/onError supported by Enex import only
|
||||||
|
|
||||||
|
importOptions.onProgress = progressState => {
|
||||||
|
let line = [];
|
||||||
|
line.push(_('Found: %d.', progressState.loaded));
|
||||||
|
line.push(_('Created: %d.', progressState.created));
|
||||||
|
if (progressState.updated) line.push(_('Updated: %d.', progressState.updated));
|
||||||
|
if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped));
|
||||||
|
if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated));
|
||||||
|
if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged));
|
||||||
|
lastProgress = line.join(' ');
|
||||||
|
cliUtils.redraw(lastProgress);
|
||||||
|
};
|
||||||
|
|
||||||
|
importOptions.onError = error => {
|
||||||
|
let s = error.trace ? error.trace : error.toString();
|
||||||
|
this.stdout(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
this.stdout(_('Importing notes...'));
|
||||||
|
const service = new InteropService();
|
||||||
|
const result = await service.import(importOptions);
|
||||||
|
result.warnings.map(w => this.stdout(w));
|
||||||
|
cliUtils.redrawDone();
|
||||||
|
if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Command;
|
@@ -10,7 +10,6 @@ const { time } = require('lib/time-utils.js');
|
|||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'ls [note-pattern]';
|
return 'ls [note-pattern]';
|
||||||
}
|
}
|
||||||
@@ -22,16 +21,9 @@ class Command extends BaseCommand {
|
|||||||
enabled() {
|
enabled() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-n, --limit <num>', _('Displays only the first top <num> notes.')], ['-s, --sort <field>', _('Sorts the item by <field> (eg. title, updated_time, created_time).')], ['-r, --reverse', _('Reverses the sorting order.')], ['-t, --type <type>', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')], ['-f, --format <format>', _('Either "text" or "json"')], ['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
|
||||||
['-n, --limit <num>', _('Displays only the first top <num> notes.')],
|
|
||||||
['-s, --sort <field>', _('Sorts the item by <field> (eg. title, updated_time, created_time).')],
|
|
||||||
['-r, --reverse', _('Reverses the sorting order.')],
|
|
||||||
['-t, --type <type>', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')],
|
|
||||||
['-f, --format <format>', _('Either "text" or "json"')],
|
|
||||||
['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -93,7 +85,7 @@ class Command extends BaseCommand {
|
|||||||
row.push(await Folder.noteCount(item.id));
|
row.push(await Folder.noteCount(item.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
row.push(time.unixMsToLocalDateTime(item.user_updated_time));
|
row.push(time.formatMsToLocal(item.user_updated_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = item.title;
|
let title = item.title;
|
||||||
@@ -105,7 +97,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
if (hasTodos) {
|
if (hasTodos) {
|
||||||
if (item.is_todo) {
|
if (item.is_todo) {
|
||||||
row.push(sprintf('[%s]', !!item.todo_completed ? 'X' : ' '));
|
row.push(sprintf('[%s]', item.todo_completed ? 'X' : ' '));
|
||||||
} else {
|
} else {
|
||||||
row.push(' ');
|
row.push(' ');
|
||||||
}
|
}
|
||||||
@@ -118,9 +110,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
cliUtils.printArray(this.stdout.bind(this), rows);
|
cliUtils.printArray(this.stdout.bind(this), rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,10 +2,8 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const { reg } = require('lib/registry.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mkbook <new-notebook>';
|
return 'mkbook <new-notebook>';
|
||||||
}
|
}
|
||||||
@@ -14,15 +12,10 @@ class Command extends BaseCommand {
|
|||||||
return _('Creates a new notebook.');
|
return _('Creates a new notebook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
aliases() {
|
|
||||||
return ['mkdir'];
|
|
||||||
}
|
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mknote <new-note>';
|
return 'mknote <new-note>';
|
||||||
}
|
}
|
||||||
@@ -26,7 +25,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
app().switchCurrentFolder(app().currentFolder());
|
app().switchCurrentFolder(app().currentFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mktodo <new-todo>';
|
return 'mktodo <new-todo>';
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,6 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
app().switchCurrentFolder(app().currentFolder());
|
app().switchCurrentFolder(app().currentFolder());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -6,7 +6,6 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'mv <note> [notebook]';
|
return 'mv <note> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -18,7 +17,7 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
const pattern = args['note'];
|
const pattern = args['note'];
|
||||||
const destination = args['notebook'];
|
const destination = args['notebook'];
|
||||||
|
|
||||||
const folder = await Folder.loadByField('title', destination);
|
const folder = await Folder.loadByField('title', destination);
|
||||||
if (!folder) throw new Error(_('Cannot find "%s".', destination));
|
if (!folder) throw new Error(_('Cannot find "%s".', destination));
|
||||||
|
|
||||||
@@ -29,7 +28,6 @@ class Command extends BaseCommand {
|
|||||||
await Note.moveToFolder(notes[i].id, folder.id);
|
await Note.moveToFolder(notes[i].id, folder.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -6,7 +6,6 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'ren <item> <name>';
|
return 'ren <item> <name>';
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,6 @@ class Command extends BaseCommand {
|
|||||||
await Note.save(newItem);
|
await Note.save(newItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'rmbook <notebook>';
|
return 'rmbook <notebook>';
|
||||||
}
|
}
|
||||||
@@ -18,9 +14,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-f, --force', _('Deletes the notebook without asking for confirmation.')]];
|
||||||
['-f, --force', _('Deletes the notebook without asking for confirmation.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -29,12 +23,11 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
|
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
|
||||||
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
|
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
|
||||||
const ok = force ? true : await this.prompt(_('Delete notebook? All notes within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' });
|
const ok = force ? true : await this.prompt(_('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' });
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
await Folder.delete(folder.id);
|
await Folder.delete(folder.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'rmnote <note-pattern>';
|
return 'rmnote <note-pattern>';
|
||||||
}
|
}
|
||||||
@@ -18,9 +14,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['-f, --force', _('Deletes the notes without asking for confirmation.')]];
|
||||||
['-f, --force', _('Deletes the notes without asking for confirmation.')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -32,10 +26,9 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
|
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
let ids = notes.map((n) => n.id);
|
let ids = notes.map(n => n.id);
|
||||||
await Note.batchDelete(ids);
|
await Note.batchDelete(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
|
||||||
const { sprintf } = require('sprintf-js');
|
|
||||||
const { time } = require('lib/time-utils.js');
|
|
||||||
const { uuid } = require('lib/uuid.js');
|
const { uuid } = require('lib/uuid.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'search <pattern> [notebook]';
|
return 'search <pattern> [notebook]';
|
||||||
}
|
}
|
||||||
@@ -49,37 +44,7 @@ class Command extends BaseCommand {
|
|||||||
type: 'SEARCH_SELECT',
|
type: 'SEARCH_SELECT',
|
||||||
id: searchId,
|
id: searchId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// let fields = Note.previewFields();
|
|
||||||
// fields.push('body');
|
|
||||||
// const notes = await Note.previews(folder ? folder.id : null, {
|
|
||||||
// fields: fields,
|
|
||||||
// anywherePattern: '*' + pattern + '*',
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const fragmentLength = 50;
|
|
||||||
|
|
||||||
// let parents = {};
|
|
||||||
|
|
||||||
// for (let i = 0; i < notes.length; i++) {
|
|
||||||
// const note = notes[i];
|
|
||||||
// const parent = parents[note.parent_id] ? parents[note.parent_id] : await Folder.load(note.parent_id);
|
|
||||||
// parents[note.parent_id] = parent;
|
|
||||||
|
|
||||||
// const idx = note.body.indexOf(pattern);
|
|
||||||
// let line = '';
|
|
||||||
// if (idx >= 0) {
|
|
||||||
// let fragment = note.body.substr(Math.max(0, idx - fragmentLength / 2), fragmentLength);
|
|
||||||
// fragment = fragment.replace(/\n/g, ' ');
|
|
||||||
// line = sprintf('%s: %s / %s: %s', BaseModel.shortId(note.id), parent.title, note.title, fragment);
|
|
||||||
// } else {
|
|
||||||
// line = sprintf('%s: %s / %s', BaseModel.shortId(note.id), parent.title, note.title);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.stdout(line);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
57
CliClient/app/command-server.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
const { Logger } = require('lib/logger.js');
|
||||||
|
const { shim } = require('lib/shim');
|
||||||
|
|
||||||
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
return 'server <command>';
|
||||||
|
}
|
||||||
|
|
||||||
|
description() {
|
||||||
|
return _('Start, stop or check the API server. To specify on which port it should run, set the api.port config variable. Commands are (%s).', ['start', 'stop', 'status'].join('|')) + ' This is an experimental feature - use at your own risks! It is recommended that the server runs off its own separate profile so that no two CLI instances access that profile at the same time. Use --profile to specify the profile path.';
|
||||||
|
}
|
||||||
|
|
||||||
|
async action(args) {
|
||||||
|
const command = args.command;
|
||||||
|
|
||||||
|
const ClipperServer = require('lib/ClipperServer');
|
||||||
|
const stdoutFn = (s) => this.stdout(s);
|
||||||
|
const clipperLogger = new Logger();
|
||||||
|
clipperLogger.addTarget('file', { path: Setting.value('profileDir') + '/log-clipper.txt' });
|
||||||
|
clipperLogger.addTarget('console', { console: {
|
||||||
|
info: stdoutFn,
|
||||||
|
warn: stdoutFn,
|
||||||
|
error: stdoutFn,
|
||||||
|
}});
|
||||||
|
ClipperServer.instance().setDispatch(action => {});
|
||||||
|
ClipperServer.instance().setLogger(clipperLogger);
|
||||||
|
|
||||||
|
const pidPath = Setting.value('profileDir') + '/clipper-pid.txt';
|
||||||
|
const runningOnPort = await ClipperServer.instance().isRunning();
|
||||||
|
|
||||||
|
if (command === 'start') {
|
||||||
|
if (runningOnPort) {
|
||||||
|
this.stdout(_('Server is already running on port %d', runningOnPort));
|
||||||
|
} else {
|
||||||
|
await shim.fsDriver().writeFile(pidPath, process.pid.toString(), 'utf-8');
|
||||||
|
await ClipperServer.instance().start(); // Never exit
|
||||||
|
}
|
||||||
|
} else if (command === 'status') {
|
||||||
|
this.stdout(runningOnPort ? _('Server is running on port %d', runningOnPort) : _('Server is not running.'));
|
||||||
|
} else if (command === 'stop') {
|
||||||
|
if (!runningOnPort) {
|
||||||
|
this.stdout(_('Server is not running.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pid = await shim.fsDriver().readFile(pidPath);
|
||||||
|
if (!pid) return;
|
||||||
|
process.kill(pid, 'SIGTERM');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Command;
|
@@ -3,12 +3,9 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'set <note> <name> [value]';
|
return 'set <note> <name> [value]';
|
||||||
}
|
}
|
||||||
@@ -45,7 +42,6 @@ class Command extends BaseCommand {
|
|||||||
await Note.save(newNote);
|
await Note.save(newNote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { ReportService } = require('lib/services/report.js');
|
const { ReportService } = require('lib/services/report.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'status';
|
return 'status';
|
||||||
}
|
}
|
||||||
@@ -34,10 +32,13 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,18 +3,16 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
|
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||||
const { Synchronizer } = require('lib/synchronizer.js');
|
const { Synchronizer } = require('lib/synchronizer.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const locker = require('proper-lockfile');
|
const locker = require('proper-lockfile');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const osTmpdir = require('os-tmpdir');
|
|
||||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.syncTargetId_ = null;
|
this.syncTargetId_ = null;
|
||||||
@@ -31,9 +29,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [['--target <target>', _('Sync to provided target (defaults to sync.target config value)')]];
|
||||||
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static lockFile(filePath) {
|
static lockFile(filePath) {
|
||||||
@@ -66,23 +62,43 @@ class Command extends BaseCommand {
|
|||||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
||||||
|
|
||||||
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive
|
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) {
|
||||||
|
// OneDrive
|
||||||
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
||||||
const auth = await this.oneDriveApiUtils_.oauthDance({
|
const auth = await this.oneDriveApiUtils_.oauthDance({
|
||||||
log: (...s) => { return this.stdout(...s); }
|
log: (...s) => {
|
||||||
|
return this.stdout(...s);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.oneDriveApiUtils_ = null;
|
this.oneDriveApiUtils_ = null;
|
||||||
|
|
||||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
||||||
if (!auth) {
|
if (!auth) {
|
||||||
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (syncTargetMd.name === 'dropbox') {
|
||||||
|
// Dropbox
|
||||||
|
const api = await syncTarget.api();
|
||||||
|
const loginUrl = api.loginUrl();
|
||||||
|
this.stdout(_('To allow Joplin to synchronise with Dropbox, please follow the steps below:'));
|
||||||
|
this.stdout(_('Step 1: Open this URL in your browser to authorise the application:'));
|
||||||
|
this.stdout(loginUrl);
|
||||||
|
const authCode = await this.prompt(_('Step 2: Enter the code provided by Dropbox:'), { type: 'string' });
|
||||||
|
if (!authCode) {
|
||||||
|
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.execAuthToken(authCode);
|
||||||
|
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', response.access_token);
|
||||||
|
api.setAuthToken(response.access_token);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTarget.label()));
|
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTargetMd.label));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +117,8 @@ class Command extends BaseCommand {
|
|||||||
this.releaseLockFn_ = null;
|
this.releaseLockFn_ = null;
|
||||||
|
|
||||||
// Lock is unique per profile/database
|
// Lock is unique per profile/database
|
||||||
const lockFilePath = osTmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
||||||
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
|
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
|
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
|
||||||
@@ -131,22 +147,26 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||||
|
|
||||||
if (!syncTarget.isAuthenticated()) {
|
if (!(await syncTarget.isAuthenticated())) {
|
||||||
app().gui().showConsole();
|
app()
|
||||||
app().gui().maximizeConsole();
|
.gui()
|
||||||
|
.showConsole();
|
||||||
|
app()
|
||||||
|
.gui()
|
||||||
|
.maximizeConsole();
|
||||||
|
|
||||||
const authDone = await this.doAuth();
|
const authDone = await this.doAuth();
|
||||||
if (!authDone) return cleanUp();
|
if (!authDone) return cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sync = await syncTarget.synchronizer();
|
const sync = await syncTarget.synchronizer();
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
onProgress: (report) => {
|
onProgress: report => {
|
||||||
let lines = Synchronizer.reportToLines(report);
|
let lines = Synchronizer.reportToLines(report);
|
||||||
if (lines.length) cliUtils.redraw(lines.join(' '));
|
if (lines.length) cliUtils.redraw(lines.join(' '));
|
||||||
},
|
},
|
||||||
onMessage: (msg) => {
|
onMessage: msg => {
|
||||||
cliUtils.redrawDone();
|
cliUtils.redrawDone();
|
||||||
this.stdout(msg);
|
this.stdout(msg);
|
||||||
},
|
},
|
||||||
@@ -175,6 +195,15 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When using the tool in command line mode, the ResourceFetcher service is
|
||||||
|
// not going to be running in the background, so the resources need to be
|
||||||
|
// explicitely downloaded below.
|
||||||
|
if (!app().hasGui()) {
|
||||||
|
this.stdout(_('Downloading resources...'));
|
||||||
|
await ResourceFetcher.instance().fetchAll();
|
||||||
|
await ResourceFetcher.instance().waitForAllFinished();
|
||||||
|
}
|
||||||
|
|
||||||
await app().refreshCurrentFolder();
|
await app().refreshCurrentFolder();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cleanUp();
|
cleanUp();
|
||||||
@@ -198,7 +227,7 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
const syncTarget = reg.syncTarget(syncTargetId);
|
const syncTarget = reg.syncTarget(syncTargetId);
|
||||||
|
|
||||||
if (syncTarget.isAuthenticated()) {
|
if (await syncTarget.isAuthenticated()) {
|
||||||
const sync = await syncTarget.synchronizer();
|
const sync = await syncTarget.synchronizer();
|
||||||
if (sync) await sync.cancel();
|
if (sync) await sync.cancel();
|
||||||
} else {
|
} else {
|
||||||
@@ -212,7 +241,6 @@ class Command extends BaseCommand {
|
|||||||
cancellable() {
|
cancellable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,19 +3,25 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'tag <tag-command> [tag] [note]';
|
return 'tag <tag-command> [tag] [note]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _('<tag-command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.');
|
return _('<tag-command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags (use -l for long option).');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options() {
|
||||||
|
return [['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
|
||||||
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let tag = null;
|
let tag = null;
|
||||||
|
let options = args.options;
|
||||||
|
|
||||||
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
||||||
let notes = [];
|
let notes = [];
|
||||||
if (args.note) {
|
if (args.note) {
|
||||||
@@ -41,16 +47,38 @@ class Command extends BaseCommand {
|
|||||||
} else if (command == 'list') {
|
} else if (command == 'list') {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
let notes = await Tag.notes(tag.id);
|
let notes = await Tag.notes(tag.id);
|
||||||
notes.map((note) => { this.stdout(note.title); });
|
notes.map(note => {
|
||||||
|
let line = '';
|
||||||
|
if (options.long) {
|
||||||
|
line += BaseModel.shortId(note.id);
|
||||||
|
line += ' ';
|
||||||
|
line += time.formatMsToLocal(note.user_updated_time);
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
if (note.is_todo) {
|
||||||
|
line += '[';
|
||||||
|
if (note.todo_completed) {
|
||||||
|
line += 'X';
|
||||||
|
} else {
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
line += '] ';
|
||||||
|
} else {
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
line += note.title;
|
||||||
|
this.stdout(line);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let tags = await Tag.all();
|
let tags = await Tag.all();
|
||||||
tags.map((tag) => { this.stdout(tag.title); });
|
tags.map(tag => {
|
||||||
|
this.stdout(tag.title);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(_('Invalid command: "%s"', command));
|
throw new Error(_('Invalid command: "%s"', command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,12 +2,10 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'todo <todo-command> <note-pattern>';
|
return 'todo <todo-command> <note-pattern>';
|
||||||
}
|
}
|
||||||
@@ -39,12 +37,11 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
} else if (action == 'clear') {
|
} else if (action == 'clear') {
|
||||||
toSave.is_todo = 0;
|
toSave.is_todo = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Note.save(toSave);
|
await Note.save(toSave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,15 +1,9 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
const Note = require('lib/models/Note.js');
|
|
||||||
const { time } = require('lib/time-utils.js');
|
|
||||||
|
|
||||||
const CommandDone = require('./command-done.js');
|
const CommandDone = require('./command-done.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'undone <note>';
|
return 'undone <note>';
|
||||||
}
|
}
|
||||||
@@ -21,7 +15,6 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
await CommandDone.handleAction(this, args, false);
|
await CommandDone.handleAction(this, args, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -2,10 +2,8 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'use <notebook>';
|
return 'use <notebook>';
|
||||||
}
|
}
|
||||||
@@ -14,10 +12,6 @@ class Command extends BaseCommand {
|
|||||||
return _('Switches to [notebook] - all further operations will happen within this notebook.');
|
return _('Switches to [notebook] - all further operations will happen within this notebook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
autocomplete() {
|
|
||||||
return { data: autocompleteFolders };
|
|
||||||
}
|
|
||||||
|
|
||||||
compatibleUis() {
|
compatibleUis() {
|
||||||
return ['cli'];
|
return ['cli'];
|
||||||
}
|
}
|
||||||
@@ -27,7 +21,6 @@ class Command extends BaseCommand {
|
|||||||
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
|
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -3,7 +3,6 @@ const Setting = require('lib/models/Setting.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'version';
|
return 'version';
|
||||||
}
|
}
|
||||||
@@ -16,7 +15,6 @@ class Command extends BaseCommand {
|
|||||||
const p = require('./package.json');
|
const p = require('./package.json');
|
||||||
this.stdout(_('%s %s (%s)', p.name, p.version, Setting.value('env')));
|
this.stdout(_('%s %s (%s)', p.name, p.version, Setting.value('env')));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||||
|
|
||||||
class ConsoleWidget extends TextWidget {
|
class ConsoleWidget extends TextWidget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.lines_ = [];
|
this.lines_ = [];
|
||||||
@@ -16,7 +15,7 @@ class ConsoleWidget extends TextWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get lastLine() {
|
get lastLine() {
|
||||||
return this.lines_.length ? this.lines_[this.lines_.length-1] : '';
|
return this.lines_.length ? this.lines_[this.lines_.length - 1] : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
addLine(line) {
|
addLine(line) {
|
||||||
@@ -40,13 +39,12 @@ class ConsoleWidget extends TextWidget {
|
|||||||
if (this.lines_.length > this.maxLines_) {
|
if (this.lines_.length > this.maxLines_) {
|
||||||
this.lines_.splice(0, this.lines_.length - this.maxLines_);
|
this.lines_.splice(0, this.lines_.length - this.maxLines_);
|
||||||
}
|
}
|
||||||
this.text = this.lines_.join("\n");
|
this.text = this.lines_.join('\n');
|
||||||
this.updateText_ = false;
|
this.updateText_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.render();
|
super.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ConsoleWidget;
|
module.exports = ConsoleWidget;
|
||||||
|
@@ -5,7 +5,6 @@ const ListWidget = require('tkwidgets/ListWidget.js');
|
|||||||
const _ = require('lib/locale.js')._;
|
const _ = require('lib/locale.js')._;
|
||||||
|
|
||||||
class FolderListWidget extends ListWidget {
|
class FolderListWidget extends ListWidget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -18,33 +17,42 @@ class FolderListWidget extends ListWidget {
|
|||||||
this.notesParentType_ = 'Folder';
|
this.notesParentType_ = 'Folder';
|
||||||
this.updateIndexFromSelectedFolderId_ = false;
|
this.updateIndexFromSelectedFolderId_ = false;
|
||||||
this.updateItems_ = false;
|
this.updateItems_ = false;
|
||||||
|
this.trimItemTitle = false;
|
||||||
|
|
||||||
this.itemRenderer = (item) => {
|
this.itemRenderer = item => {
|
||||||
let output = [];
|
let output = [];
|
||||||
if (item === '-') {
|
if (item === '-') {
|
||||||
output.push('-'.repeat(this.innerWidth));
|
output.push('-'.repeat(this.innerWidth));
|
||||||
} else if (item.type_ === Folder.modelType()) {
|
} else if (item.type_ === Folder.modelType()) {
|
||||||
output.push(Folder.displayTitle(item));
|
output.push(' '.repeat(this.folderDepth(this.folders, item.id)) + Folder.displayTitle(item));
|
||||||
} else if (item.type_ === Tag.modelType()) {
|
} else if (item.type_ === Tag.modelType()) {
|
||||||
output.push('[' + Folder.displayTitle(item) + ']');
|
output.push('[' + Folder.displayTitle(item) + ']');
|
||||||
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
||||||
output.push(_('Search:'));
|
output.push(_('Search:'));
|
||||||
output.push(item.title);
|
output.push(item.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (item && item.id) output.push(item.id.substr(0, 5));
|
|
||||||
|
|
||||||
return output.join(' ');
|
return output.join(' ');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folderDepth(folders, folderId) {
|
||||||
|
let output = 0;
|
||||||
|
while (true) {
|
||||||
|
const folder = BaseModel.byId(folders, folderId);
|
||||||
|
if (!folder.parent_id) return output;
|
||||||
|
output++;
|
||||||
|
folderId = folder.parent_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get selectedFolderId() {
|
get selectedFolderId() {
|
||||||
return this.selectedFolderId_;
|
return this.selectedFolderId_;
|
||||||
}
|
}
|
||||||
|
|
||||||
set selectedFolderId(v) {
|
set selectedFolderId(v) {
|
||||||
this.selectedFolderId_ = v;
|
this.selectedFolderId_ = v;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +62,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
|
|
||||||
set selectedSearchId(v) {
|
set selectedSearchId(v) {
|
||||||
this.selectedSearchId_ = v;
|
this.selectedSearchId_ = v;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +72,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
|
|
||||||
set selectedTagId(v) {
|
set selectedTagId(v) {
|
||||||
this.selectedTagId_ = v;
|
this.selectedTagId_ = v;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +81,8 @@ class FolderListWidget extends ListWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set notesParentType(v) {
|
set notesParentType(v) {
|
||||||
//if (this.notesParentType_ === v) return;
|
|
||||||
this.notesParentType_ = v;
|
this.notesParentType_ = v;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +93,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
set searches(v) {
|
set searches(v) {
|
||||||
this.searches_ = v;
|
this.searches_ = v;
|
||||||
this.updateItems_ = true;
|
this.updateItems_ = true;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +104,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
set tags(v) {
|
set tags(v) {
|
||||||
this.tags_ = v;
|
this.tags_ = v;
|
||||||
this.updateItems_ = true;
|
this.updateItems_ = true;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,17 +115,37 @@ class FolderListWidget extends ListWidget {
|
|||||||
set folders(v) {
|
set folders(v) {
|
||||||
this.folders_ = v;
|
this.folders_ = v;
|
||||||
this.updateItems_ = true;
|
this.updateItems_ = true;
|
||||||
this.updateIndexFromSelectedItemId()
|
this.updateIndexFromSelectedItemId();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
folderHasChildren_(folders, folderId) {
|
||||||
|
for (let i = 0; i < folders.length; i++) {
|
||||||
|
let folder = folders[i];
|
||||||
|
if (folder.parent_id === folderId) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.updateItems_) {
|
if (this.updateItems_) {
|
||||||
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
|
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
|
||||||
const wasSelectedItemId = this.selectedJoplinItemId;
|
const wasSelectedItemId = this.selectedJoplinItemId;
|
||||||
const previousParentType = this.notesParentType;
|
const previousParentType = this.notesParentType;
|
||||||
|
|
||||||
let newItems = this.folders.slice();
|
let newItems = [];
|
||||||
|
const orderFolders = parentId => {
|
||||||
|
for (let i = 0; i < this.folders.length; i++) {
|
||||||
|
const f = this.folders[i];
|
||||||
|
const folderParentId = f.parent_id ? f.parent_id : '';
|
||||||
|
if (folderParentId === parentId) {
|
||||||
|
newItems.push(f);
|
||||||
|
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
orderFolders('');
|
||||||
|
|
||||||
if (this.tags.length) {
|
if (this.tags.length) {
|
||||||
if (newItems.length) newItems.push('-');
|
if (newItems.length) newItems.push('-');
|
||||||
@@ -133,7 +160,7 @@ class FolderListWidget extends ListWidget {
|
|||||||
this.items = newItems;
|
this.items = newItems;
|
||||||
|
|
||||||
this.notesParentType = previousParentType;
|
this.notesParentType = previousParentType;
|
||||||
this.updateIndexFromSelectedItemId(wasSelectedItemId)
|
this.updateIndexFromSelectedItemId(wasSelectedItemId);
|
||||||
this.updateItems_ = false;
|
this.updateItems_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +186,6 @@ class FolderListWidget extends ListWidget {
|
|||||||
const index = this.itemIndexByKey('id', itemId);
|
const index = this.itemIndexByKey('id', itemId);
|
||||||
this.currentIndex = index >= 0 ? index : 0;
|
this.currentIndex = index >= 0 ? index : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = FolderListWidget;
|
module.exports = FolderListWidget;
|
||||||
|
@@ -2,14 +2,13 @@ const Note = require('lib/models/Note.js');
|
|||||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
const ListWidget = require('tkwidgets/ListWidget.js');
|
||||||
|
|
||||||
class NoteListWidget extends ListWidget {
|
class NoteListWidget extends ListWidget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.selectedNoteId_ = 0;
|
this.selectedNoteId_ = 0;
|
||||||
|
|
||||||
this.updateIndexFromSelectedNoteId_ = false;
|
this.updateIndexFromSelectedNoteId_ = false;
|
||||||
|
|
||||||
this.itemRenderer = (note) => {
|
this.itemRenderer = note => {
|
||||||
let label = Note.displayTitle(note); // + ' ' + note.id;
|
let label = Note.displayTitle(note); // + ' ' + note.id;
|
||||||
if (note.is_todo) {
|
if (note.is_todo) {
|
||||||
label = '[' + (note.todo_completed ? 'X' : ' ') + '] ' + label;
|
label = '[' + (note.todo_completed ? 'X' : ' ') + '] ' + label;
|
||||||
@@ -32,7 +31,6 @@ class NoteListWidget extends ListWidget {
|
|||||||
|
|
||||||
super.render();
|
super.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NoteListWidget;
|
module.exports = NoteListWidget;
|
||||||
|
@@ -2,7 +2,6 @@ const Note = require('lib/models/Note.js');
|
|||||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||||
|
|
||||||
class NoteMetadataWidget extends TextWidget {
|
class NoteMetadataWidget extends TextWidget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.noteId_ = 0;
|
this.noteId_ = 0;
|
||||||
@@ -30,7 +29,6 @@ class NoteMetadataWidget extends TextWidget {
|
|||||||
this.text = this.note_ ? await Note.minimalSerializeForDisplay(this.note_) : '';
|
this.text = this.note_ ? await Note.minimalSerializeForDisplay(this.note_) : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NoteMetadataWidget;
|
module.exports = NoteMetadataWidget;
|
||||||
|
@@ -3,7 +3,6 @@ const TextWidget = require('tkwidgets/TextWidget.js');
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
class NoteWidget extends TextWidget {
|
class NoteWidget extends TextWidget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.noteId_ = 0;
|
this.noteId_ = 0;
|
||||||
@@ -44,11 +43,11 @@ class NoteWidget extends TextWidget {
|
|||||||
} else if (this.noteId_) {
|
} else if (this.noteId_) {
|
||||||
this.doAsync('loadNote', async () => {
|
this.doAsync('loadNote', async () => {
|
||||||
this.note_ = await Note.load(this.noteId_);
|
this.note_ = await Note.load(this.noteId_);
|
||||||
|
|
||||||
if (this.note_ && this.note_.encryption_applied) {
|
if (this.note_ && this.note_.encryption_applied) {
|
||||||
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
|
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
|
||||||
} else {
|
} else {
|
||||||
this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : '';
|
this.text = this.note_ ? this.note_.title + '\n\n' + this.note_.body : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.lastLoadedNoteId_ !== this.noteId_) this.scrollTop = 0;
|
if (this.lastLoadedNoteId_ !== this.noteId_) this.scrollTop = 0;
|
||||||
@@ -59,7 +58,6 @@ class NoteWidget extends TextWidget {
|
|||||||
this.scrollTop = 0;
|
this.scrollTop = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NoteWidget;
|
module.exports = NoteWidget;
|
||||||
|
@@ -5,7 +5,6 @@ const stripAnsi = require('strip-ansi');
|
|||||||
const { handleAutocompletion } = require('../autocompletion.js');
|
const { handleAutocompletion } = require('../autocompletion.js');
|
||||||
|
|
||||||
class StatusBarWidget extends BaseWidget {
|
class StatusBarWidget extends BaseWidget {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
super.render();
|
super.render();
|
||||||
|
|
||||||
const doSaveCursor = !this.promptActive;
|
const doSaveCursor = !this.promptActive;
|
||||||
|
|
||||||
if (doSaveCursor) this.term.saveCursor();
|
if (doSaveCursor) this.term.saveCursor();
|
||||||
|
|
||||||
this.innerClear();
|
this.innerClear();
|
||||||
@@ -87,14 +86,13 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
|
|
||||||
//const textStyle = this.promptActive ? (s) => s : chalk.bgBlueBright.white;
|
//const textStyle = this.promptActive ? (s) => s : chalk.bgBlueBright.white;
|
||||||
//const textStyle = (s) => s;
|
//const textStyle = (s) => s;
|
||||||
const textStyle = this.promptActive ? (s) => s : chalk.gray;
|
const textStyle = this.promptActive ? s => s : chalk.gray;
|
||||||
|
|
||||||
this.term.drawHLine(this.absoluteInnerX, this.absoluteInnerY, this.innerWidth, textStyle(' '));
|
this.term.drawHLine(this.absoluteInnerX, this.absoluteInnerY, this.innerWidth, textStyle(' '));
|
||||||
|
|
||||||
this.term.moveTo(this.absoluteInnerX, this.absoluteInnerY);
|
this.term.moveTo(this.absoluteInnerX, this.absoluteInnerY);
|
||||||
|
|
||||||
if (this.promptActive) {
|
if (this.promptActive) {
|
||||||
|
|
||||||
this.term.write(textStyle(this.promptState_.promptString));
|
this.term.write(textStyle(this.promptState_.promptString));
|
||||||
|
|
||||||
if (this.inputEventEmitter_) {
|
if (this.inputEventEmitter_) {
|
||||||
@@ -113,8 +111,8 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
history: this.history,
|
history: this.history,
|
||||||
default: this.promptState_.initialText,
|
default: this.promptState_.initialText,
|
||||||
autoComplete: handleAutocompletion,
|
autoComplete: handleAutocompletion,
|
||||||
autoCompleteHint : true,
|
autoCompleteHint: true,
|
||||||
autoCompleteMenu : true,
|
autoCompleteMenu: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
|
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
|
||||||
@@ -133,7 +131,8 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
resolveResult = input ? input.trim() : input;
|
resolveResult = input ? input.trim() : input;
|
||||||
// Add the command to history but only if it's longer than one character.
|
// Add the command to history but only if it's longer than one character.
|
||||||
// Below that it's usually an answer like "y"/"n", etc.
|
// Below that it's usually an answer like "y"/"n", etc.
|
||||||
if (!isSecurePrompt && input && input.length > 1) this.history_.push(input);
|
const isConfigPassword = input.indexOf('config ') >= 0 && input.indexOf('password') >= 0;
|
||||||
|
if (!isSecurePrompt && input && input.length > 1 && !isConfigPassword) this.history_.push(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,19 +151,15 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
// Only callback once everything has been cleaned up and reset
|
// Only callback once everything has been cleaned up and reset
|
||||||
resolveFn(resolveResult);
|
resolveFn(resolveResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
for (let i = 0; i < this.items_.length; i++) {
|
for (let i = 0; i < this.items_.length; i++) {
|
||||||
const s = this.items_[i].substr(0, this.innerWidth - 1);
|
const s = this.items_[i].substr(0, this.innerWidth - 1);
|
||||||
this.term.write(textStyle(s));
|
this.term.write(textStyle(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doSaveCursor) this.term.restoreCursor();
|
if (doSaveCursor) this.term.restoreCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = StatusBarWidget;
|
module.exports = StatusBarWidget;
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
const fs = require('fs-extra');
|
|
||||||
const { wrap } = require('lib/string-utils.js');
|
const { wrap } = require('lib/string-utils.js');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { _, setLocale, languageCode } = require('lib/locale.js');
|
|
||||||
|
|
||||||
const rootDir = dirname(dirname(__dirname));
|
|
||||||
const MAX_WIDTH = 78;
|
const MAX_WIDTH = 78;
|
||||||
const INDENT = ' ';
|
const INDENT = ' ';
|
||||||
|
|
||||||
@@ -16,14 +13,14 @@ function renderTwoColumnData(options, baseIndent, width) {
|
|||||||
let option = options[i];
|
let option = options[i];
|
||||||
const flag = option[0];
|
const flag = option[0];
|
||||||
const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2);
|
const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2);
|
||||||
|
|
||||||
let r = wrap(option[1], indent, width);
|
let r = wrap(option[1], indent, width);
|
||||||
r = r.substr(flag.length + (baseIndent + INDENT).length);
|
r = r.substr(flag.length + (baseIndent + INDENT).length);
|
||||||
r = baseIndent + INDENT + flag + r;
|
r = baseIndent + INDENT + flag + r;
|
||||||
output.push(r);
|
output.push(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCommandHelp(cmd, width = null) {
|
function renderCommandHelp(cmd, width = null) {
|
||||||
@@ -44,7 +41,7 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.name() === 'config') {
|
if (cmd.name() === 'config') {
|
||||||
const renderMetadata = (md) => {
|
const renderMetadata = md => {
|
||||||
let desc = [];
|
let desc = [];
|
||||||
|
|
||||||
if (md.label) {
|
if (md.label) {
|
||||||
@@ -53,9 +50,8 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
desc.push(label);
|
desc.push(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (md.description) {
|
const description = Setting.keyDescription(md.key, 'cli');
|
||||||
desc.push(md.description());
|
if (description) desc.push(description);
|
||||||
}
|
|
||||||
|
|
||||||
desc.push(_('Type: %s.', md.isEnum ? _('Enum') : Setting.typeToString(md.type)));
|
desc.push(_('Type: %s.', md.isEnum ? _('Enum') : Setting.typeToString(md.type)));
|
||||||
if (md.isEnum) desc.push(_('Possible values: %s.', Setting.enumOptionsDoc(md.key, '%s (%s)')));
|
if (md.isEnum) desc.push(_('Possible values: %s.', Setting.enumOptionsDoc(md.key, '%s (%s)')));
|
||||||
@@ -68,13 +64,13 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
} else if (md.type === Setting.TYPE_INT) {
|
} else if (md.type === Setting.TYPE_INT) {
|
||||||
defaultString = (md.value ? md.value : 0).toString();
|
defaultString = (md.value ? md.value : 0).toString();
|
||||||
} else if (md.type === Setting.TYPE_BOOL) {
|
} else if (md.type === Setting.TYPE_BOOL) {
|
||||||
defaultString = (md.value === true ? 'true' : 'false');
|
defaultString = md.value === true ? 'true' : 'false';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultString !== null) desc.push(_('Default: %s', defaultString));
|
if (defaultString !== null) desc.push(_('Default: %s', defaultString));
|
||||||
|
|
||||||
return [md.key, desc.join("\n")];
|
return [md.key, desc.join('\n')];
|
||||||
};
|
};
|
||||||
|
|
||||||
output.push('');
|
output.push('');
|
||||||
@@ -84,7 +80,7 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
let keysValues = [];
|
let keysValues = [];
|
||||||
const keys = Setting.keys(true, 'cli');
|
const keys = Setting.keys(true, 'cli');
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
if (keysValues.length) keysValues.push(['','']);
|
if (keysValues.length) keysValues.push(['', '']);
|
||||||
const md = Setting.settingMetadata(keys[i]);
|
const md = Setting.settingMetadata(keys[i]);
|
||||||
if (!md.label) continue;
|
if (!md.label) continue;
|
||||||
keysValues.push(renderMetadata(md));
|
keysValues.push(renderMetadata(md));
|
||||||
@@ -92,8 +88,8 @@ function renderCommandHelp(cmd, width = null) {
|
|||||||
|
|
||||||
output.push(renderTwoColumnData(keysValues, baseIndent, width));
|
output.push(renderTwoColumnData(keysValues, baseIndent, width));
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.join("\n");
|
return output.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptionColWidth(options) {
|
function getOptionColWidth(options) {
|
||||||
@@ -105,4 +101,4 @@ function getOptionColWidth(options) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { renderCommandHelp };
|
module.exports = { renderCommandHelp };
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Use njstrace to find out what Node.js might be spending time on
|
||||||
|
// var njstrace = require('njstrace').inject();
|
||||||
|
|
||||||
// Make it possible to require("/lib/...") without specifying full path
|
// Make it possible to require("/lib/...") without specifying full path
|
||||||
require('app-module-path').addPath(__dirname);
|
require('app-module-path').addPath(__dirname);
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ const Tag = require('lib/models/Tag.js');
|
|||||||
const NoteTag = require('lib/models/NoteTag.js');
|
const NoteTag = require('lib/models/NoteTag.js');
|
||||||
const MasterKey = require('lib/models/MasterKey');
|
const MasterKey = require('lib/models/MasterKey');
|
||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
const Revision = require('lib/models/Revision.js');
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||||
const { shimInit } = require('lib/shim-init-node.js');
|
const { shimInit } = require('lib/shim-init-node.js');
|
||||||
@@ -40,6 +44,7 @@ BaseItem.loadClass('Resource', Resource);
|
|||||||
BaseItem.loadClass('Tag', Tag);
|
BaseItem.loadClass('Tag', Tag);
|
||||||
BaseItem.loadClass('NoteTag', NoteTag);
|
BaseItem.loadClass('NoteTag', NoteTag);
|
||||||
BaseItem.loadClass('MasterKey', MasterKey);
|
BaseItem.loadClass('MasterKey', MasterKey);
|
||||||
|
BaseItem.loadClass('Revision', Revision);
|
||||||
|
|
||||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||||
Setting.setConstant('appType', 'cli');
|
Setting.setConstant('appType', 'cli');
|
||||||
@@ -48,72 +53,32 @@ shimInit();
|
|||||||
|
|
||||||
const application = app();
|
const application = app();
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === 'win32') {
|
||||||
var rl = require("readline").createInterface({
|
var rl = require('readline').createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
rl.on("SIGINT", function () {
|
rl.on('SIGINT', function() {
|
||||||
process.emit("SIGINT");
|
process.emit('SIGINT');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
process.stdout.on('error', function( err ) {
|
process.stdout.on('error', function(err) {
|
||||||
// https://stackoverflow.com/questions/12329816/error-write-epipe-when-piping-node-output-to-head#15884508
|
// https://stackoverflow.com/questions/12329816/error-write-epipe-when-piping-node-output-to-head#15884508
|
||||||
if (err.code == "EPIPE") {
|
if (err.code == 'EPIPE') {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
application.start(process.argv).catch(error => {
|
||||||
|
if (error.code == 'flagError') {
|
||||||
|
console.error(error.message);
|
||||||
|
console.error(_('Type `joplin help` for usage information.'));
|
||||||
|
} else {
|
||||||
|
console.error(_('Fatal error:'));
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
// async function main() {
|
|
||||||
// const WebDavApi = require('lib/WebDavApi');
|
|
||||||
// const api = new WebDavApi('http://nextcloud.local/remote.php/dav/files/admin/Joplin', { username: 'admin', password: '1234567' });
|
|
||||||
// const { FileApiDriverWebDav } = new require('lib/file-api-driver-webdav');
|
|
||||||
// const driver = new FileApiDriverWebDav(api);
|
|
||||||
|
|
||||||
// const stat = await driver.stat('');
|
|
||||||
// console.info(stat);
|
|
||||||
|
|
||||||
// // const stat = await driver.stat('testing.txt');
|
|
||||||
// // console.info(stat);
|
|
||||||
|
|
||||||
|
|
||||||
// // const content = await driver.get('testing.txta');
|
|
||||||
// // console.info(content);
|
|
||||||
|
|
||||||
// // const content = await driver.get('testing.txta', { target: 'file', path: '/var/www/joplin/CliClient/testing-file.txt' });
|
|
||||||
// // console.info(content);
|
|
||||||
|
|
||||||
// // const content = await driver.mkdir('newdir5');
|
|
||||||
// // console.info(content);
|
|
||||||
|
|
||||||
// //await driver.put('myfile4.md', 'this is my content');
|
|
||||||
|
|
||||||
// // await driver.put('testimg.jpg', null, { source: 'file', path: '/mnt/d/test.jpg' });
|
|
||||||
|
|
||||||
// // await driver.delete('myfile4.md');
|
|
||||||
|
|
||||||
// // const deltaResult = await driver.delta('', {
|
|
||||||
// // allItemIdsHandler: () => { return []; }
|
|
||||||
// // });
|
|
||||||
// // console.info(deltaResult);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// main().catch((error) => { console.error(error); });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
application.start(process.argv).catch((error) => {
|
|
||||||
console.error(_('Fatal error:'));
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { netUtils } = require('lib/net-utils.js');
|
const { netUtils } = require('lib/net-utils.js');
|
||||||
|
|
||||||
const http = require("http");
|
const http = require('http');
|
||||||
const urlParser = require("url");
|
const urlParser = require('url');
|
||||||
const FormData = require('form-data');
|
|
||||||
const enableServerDestroy = require('server-destroy');
|
const enableServerDestroy = require('server-destroy');
|
||||||
|
|
||||||
class OneDriveApiNodeUtils {
|
class OneDriveApiNodeUtils {
|
||||||
|
|
||||||
constructor(api) {
|
constructor(api) {
|
||||||
this.api_ = api;
|
this.api_ = api;
|
||||||
this.oauthServer_ = null;
|
this.oauthServer_ = null;
|
||||||
@@ -48,7 +46,7 @@ class OneDriveApiNodeUtils {
|
|||||||
|
|
||||||
let authCodeUrl = this.api().authCodeUrl('http://localhost:' + port);
|
let authCodeUrl = this.api().authCodeUrl('http://localhost:' + port);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.oauthServer_ = http.createServer();
|
this.oauthServer_ = http.createServer();
|
||||||
let errorMessage = null;
|
let errorMessage = null;
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ class OneDriveApiNodeUtils {
|
|||||||
const url = urlParser.parse(request.url, true);
|
const url = urlParser.parse(request.url, true);
|
||||||
|
|
||||||
if (url.pathname === '/auth') {
|
if (url.pathname === '/auth') {
|
||||||
response.writeHead(302, { 'Location': authCodeUrl });
|
response.writeHead(302, { Location: authCodeUrl });
|
||||||
response.end();
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,10 +62,10 @@ class OneDriveApiNodeUtils {
|
|||||||
const query = url.query;
|
const query = url.query;
|
||||||
|
|
||||||
const writeResponse = (code, message) => {
|
const writeResponse = (code, message) => {
|
||||||
response.writeHead(code, {"Content-Type": "text/html"});
|
response.writeHead(code, { 'Content-Type': 'text/html' });
|
||||||
response.write(this.makePage(message));
|
response.write(this.makePage(message));
|
||||||
response.end();
|
response.end();
|
||||||
}
|
};
|
||||||
|
|
||||||
// After the response has been received, don't destroy the server right
|
// After the response has been received, don't destroy the server right
|
||||||
// away or the browser might display a connection reset error (even
|
// away or the browser might display a connection reset error (even
|
||||||
@@ -77,21 +75,24 @@ class OneDriveApiNodeUtils {
|
|||||||
this.oauthServer_.destroy();
|
this.oauthServer_.destroy();
|
||||||
this.oauthServer_ = null;
|
this.oauthServer_ = null;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!query.code) return writeResponse(400, '"code" query parameter is missing');
|
if (!query.code) return writeResponse(400, '"code" query parameter is missing');
|
||||||
|
|
||||||
this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then(() => {
|
this.api()
|
||||||
writeResponse(200, _('The application has been authorised - you may now close this browser tab.'));
|
.execTokenRequest(query.code, 'http://localhost:' + port.toString())
|
||||||
targetConsole.log('');
|
.then(() => {
|
||||||
targetConsole.log(_('The application has been successfully authorised.'));
|
writeResponse(200, _('The application has been authorised - you may now close this browser tab.'));
|
||||||
waitAndDestroy();
|
targetConsole.log('');
|
||||||
}).catch((error) => {
|
targetConsole.log(_('The application has been successfully authorised.'));
|
||||||
writeResponse(400, error.message);
|
waitAndDestroy();
|
||||||
targetConsole.log('');
|
})
|
||||||
targetConsole.log(error.message);
|
.catch(error => {
|
||||||
waitAndDestroy();
|
writeResponse(400, error.message);
|
||||||
});
|
targetConsole.log('');
|
||||||
|
targetConsole.log(error.message);
|
||||||
|
waitAndDestroy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.oauthServer_.on('close', () => {
|
this.oauthServer_.on('close', () => {
|
||||||
@@ -116,7 +117,6 @@ class OneDriveApiNodeUtils {
|
|||||||
targetConsole.log('http://127.0.0.1:' + port + '/auth');
|
targetConsole.log('http://127.0.0.1:' + port + '/auth');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { OneDriveApiNodeUtils };
|
module.exports = { OneDriveApiNodeUtils };
|
||||||
|