Compare commits
1149 Commits
android-v0
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
322ec2efa1 | ||
|
1232661b1e | ||
|
c46d123503 | ||
|
8f4060999f | ||
|
0addd86069 | ||
|
760086307b | ||
|
fc6558a64c | ||
|
eca500880d | ||
|
90bcd7c977 | ||
|
cca0c6eaf3 | ||
|
b0736002be | ||
|
51fc2d8e51 | ||
|
d87c192ff1 | ||
|
52ccf398a6 | ||
|
344d0e2687 | ||
|
1bc4d6b423 | ||
|
baa9ca7ea3 | ||
|
e4d477fb4c | ||
|
71319eee28 | ||
|
68b31526f8 | ||
|
0b2b7324d9 | ||
|
43512cf27b | ||
|
4218b65969 | ||
|
7244e12b78 | ||
|
a796ef5c66 | ||
|
9474a05aaa | ||
|
41df355a7e | ||
|
4f3ab87914 | ||
|
5d1a08707c | ||
|
4f822df80e | ||
|
951be5cbf6 | ||
|
b6c2341542 | ||
|
a6e6b49a9d | ||
|
3a4bbd571e | ||
|
feccc6150e | ||
|
a37b599a6b | ||
|
9347683fe3 | ||
|
3551c26e28 | ||
|
cfca0107eb | ||
|
dfbe37fdaf | ||
|
81bc975193 | ||
|
7908fda451 | ||
|
cdbb7c4b0d | ||
|
414e57ec55 | ||
|
1871123066 | ||
|
87bc08bef5 | ||
|
214a39c3d3 | ||
|
ef0cc5e33e | ||
|
3a1fa583ab | ||
|
c1161ae017 | ||
|
1023ec6206 | ||
|
7841421c0d | ||
|
995d8c35dd | ||
|
b179471eff | ||
|
19a126ebfe | ||
|
7e56e5b587 | ||
|
acf0c79341 | ||
|
9fe7e23ffe | ||
|
c94cc93971 | ||
|
b26094eba8 | ||
|
89a5ccdf93 | ||
|
ce2da0e6dc | ||
|
f49d644b6a | ||
|
02ac0b8593 | ||
|
78e5eaf1e2 | ||
|
fc0d227396 | ||
|
f91c52cdf7 | ||
|
3f14878d0f | ||
|
69fd32e7c6 | ||
|
80801cedf0 | ||
|
480e4fa94b | ||
|
717c789836 | ||
|
f099376446 | ||
|
41fa9d093e | ||
|
e2f3f81eb6 | ||
|
5cab7aeb55 | ||
|
fa5f418c22 | ||
|
a25fcacace | ||
|
727ba7300e | ||
|
d25d9b3f44 | ||
|
9d762a4319 | ||
|
18d94c7585 | ||
|
af82345eb8 | ||
|
1e94a22986 | ||
|
e19a8a99ff | ||
|
f975009e24 | ||
|
90640fafc7 | ||
|
42e0e1e5a5 | ||
|
61f64fa933 | ||
|
0d0ffd6d27 | ||
|
023ccffd2e | ||
|
bc26098c7d | ||
|
7257a71a18 | ||
|
8ad8b73585 | ||
|
9a06815db9 | ||
|
66947d4954 | ||
|
3ec22185d5 | ||
|
0f05c23e26 | ||
|
74493fece0 | ||
|
557a96e814 | ||
|
4b23b419a4 | ||
|
8b7f5b1151 | ||
|
29e9ccf216 | ||
|
2c04f5c8bc | ||
|
5430a747e9 | ||
|
13bc185829 | ||
|
ed87581a8a | ||
|
2645ec96a8 | ||
|
d278d830f0 | ||
|
b4dce0ed46 | ||
|
e8416042d4 | ||
|
70adbe5e76 | ||
|
f66be08d1d | ||
|
fad96f5266 | ||
|
c33a7f5f47 | ||
|
28afbcde02 | ||
|
691292d2b3 | ||
|
30ff81064f | ||
|
f9f398ad98 | ||
|
537884bdcd | ||
|
d54400a7cb | ||
|
37e7ea0b52 | ||
|
42c78264fb | ||
|
c52da82447 | ||
|
cca43624e4 | ||
|
dac1cd7668 | ||
|
b4c00db0e3 | ||
|
3ce393a8b2 | ||
|
2b627fe4ab | ||
|
fcf8a1649d | ||
|
8d3b050831 | ||
|
43297ef0a3 | ||
|
551fabdfc9 | ||
|
d6de56b2db | ||
|
9e979804f3 | ||
|
b8e0f182cc | ||
|
9a41b9e192 | ||
|
9b8f520b9f | ||
|
5b6019805c | ||
|
a4106436c4 | ||
|
f6b4eb511e | ||
|
eb67ac17a0 | ||
|
7b760d03ef | ||
|
2805ae2acf | ||
|
5cb5ccc781 | ||
|
0dba2821b6 | ||
|
1db7825b22 | ||
|
8a92d6ad70 | ||
|
138ad9fcad | ||
|
08cb518c25 | ||
|
6d04eab200 | ||
|
8a8cb51e1b | ||
|
5c66042a2d | ||
|
ae75181b02 | ||
|
9dc3238182 | ||
|
0a68749373 | ||
|
1519116291 | ||
|
d023d841e2 | ||
|
d7a1465d8e | ||
|
15848fc696 | ||
|
837ae2c9f2 | ||
|
6789b98ead | ||
|
29f6e74ee3 | ||
|
2780c38c45 | ||
|
4531838217 | ||
|
7bccf7f65d | ||
|
c62a24a9cb | ||
|
c6830499f7 | ||
|
d9f00a2539 | ||
|
def83c9119 | ||
|
b6cb0056c7 | ||
|
1669b5258a | ||
|
5a9e0bfc26 | ||
|
8f3fdb3afe | ||
|
7ab135c099 | ||
|
1cc27f2509 | ||
|
ef700b421c | ||
|
b9af5ac052 | ||
|
173f2d421d | ||
|
9f82c069c9 | ||
|
6ade09c228 | ||
|
5393a1399c | ||
|
fd29f20b2e | ||
|
c011b53d1f | ||
|
26e3a7b68c | ||
|
e70a291698 | ||
|
511bd57726 | ||
|
c6de8598dc | ||
|
7bee25599d | ||
|
773a1ad829 | ||
|
1a1e264fa4 | ||
|
5b99ecefca | ||
|
1bfeed377a | ||
|
86eee376bb | ||
|
6a7d368184 | ||
|
1da19ae98d | ||
|
f52c117b09 | ||
|
2551f96149 | ||
|
c984c19fee | ||
|
ac8e91e82e | ||
|
af50d80541 | ||
|
e355f4e49b | ||
|
738ef2b0fa | ||
|
9746a3964b | ||
|
9efbf74b6f | ||
|
c16ea6b237 | ||
|
b06a3b588f | ||
|
6ff67e0995 | ||
|
1a5c8d126d | ||
|
f632580eed | ||
|
1d73f0cdee | ||
|
99c7111f8c | ||
|
ae9806561a | ||
|
fffdf5b5b7 | ||
|
3de19c3db7 | ||
|
56e074b4ef | ||
|
1a79253780 | ||
|
b67908df11 | ||
|
6a5089f71d | ||
|
f710463b67 | ||
|
6ae0c3aba0 | ||
|
07c6347014 | ||
|
b10999e83e | ||
|
961b5bfd25 | ||
|
d1f1d1068a | ||
|
faade0afe2 | ||
|
a442a49e2f | ||
|
7d3fbbcaba | ||
|
d9bb7c3271 | ||
|
4d1dd17fa2 | ||
|
c5c6c777be | ||
|
1fd1a73fda | ||
|
feeb498a79 | ||
|
1d7f30d441 | ||
|
53da63e371 | ||
|
424443a2d8 | ||
|
08b58f0e4c | ||
|
c2a0d8600f | ||
|
ede3c2ce2f | ||
|
0b93515711 | ||
|
2f13e689b9 | ||
|
ea135a0d28 | ||
|
f67e4a03e4 | ||
|
e9268edeff | ||
|
a8576a55d6 | ||
|
eb500cdf9e | ||
|
7b9dc66121 | ||
|
bba2c68c6f | ||
|
c70d8bea78 | ||
|
176bda66ad | ||
|
78ce10ddf0 | ||
|
29f14681a8 | ||
|
aaf617e41c | ||
|
b99146ed7f | ||
|
d136161650 | ||
|
39051a27a1 | ||
|
30bc9dd551 | ||
|
cc2f665313 | ||
|
37c989ed28 | ||
|
adc5885980 | ||
|
8de5b4219d | ||
|
e096ddebd4 | ||
|
83398dd0bc | ||
|
ddc78ebb41 | ||
|
70b69eb31e | ||
|
3fa891e136 | ||
|
6f7a9f3295 | ||
|
44bf518244 | ||
|
63cb9b4968 | ||
|
a6cecc103c | ||
|
8e8793943b | ||
|
83d9faf2fe | ||
|
f45a4fff8b | ||
|
1e02aa3120 | ||
|
77fec75f23 | ||
|
2c9feae20b | ||
|
a81788b3fa | ||
|
88cfba53a3 | ||
|
d659d975cd | ||
|
277ad90f72 | ||
|
f2e3bedde6 | ||
|
98c0f2315a | ||
|
0926755635 | ||
|
75f8234db5 | ||
|
b8e85e1587 | ||
|
b6add857f9 | ||
|
4e41731c08 | ||
|
6ac3b8a8f9 | ||
|
7ab2b11c9d | ||
|
1eb1c5914c | ||
|
5780951f7d | ||
|
67e790e393 | ||
|
0d472486ca | ||
|
2d218904d0 | ||
|
c3fbcb8feb | ||
|
64031ac6cf | ||
|
caae7f7cf8 | ||
|
d56b247adf | ||
|
11bdfbde61 | ||
|
a59bf55c16 | ||
|
043be1916c | ||
|
42e34b5c3b | ||
|
931083b2e2 | ||
|
09b9df4228 | ||
|
37663bd110 | ||
|
f01c6aa8d1 | ||
|
8838017830 | ||
|
1d6fb8058f | ||
|
bb51729bea | ||
|
507e7e6014 | ||
|
f42908b11c | ||
|
03ec406627 | ||
|
c703521b6c | ||
|
cf97bf9a77 | ||
|
304b9a582f | ||
|
4d5c4b1743 | ||
|
4abe5d07c4 | ||
|
f6633e23f5 | ||
|
a6d6201ecb | ||
|
0115e74163 | ||
|
4314c392f6 | ||
|
685f541bb4 | ||
|
f9634ea283 | ||
|
6252a4d8c8 | ||
|
d13d7bec45 | ||
|
74e284d795 | ||
|
7b82dacbf2 | ||
|
f82162f94a | ||
|
e2d5128624 | ||
|
9074412472 | ||
|
0db9c66317 | ||
|
b22211fada | ||
|
7efeaa3a22 | ||
|
79efac2f71 | ||
|
9d7b7092f5 | ||
|
71e877d369 | ||
|
7e086a7730 | ||
|
3f810c71b0 | ||
|
500fbc5294 | ||
|
6d0f60d9a1 | ||
|
e19c26fdd1 | ||
|
86b86513d9 | ||
|
6ff19063ef | ||
|
6a75451539 | ||
|
70a33f8533 | ||
|
23722719f0 | ||
|
d499251206 | ||
|
d180e7b5e1 | ||
|
9e869a5b1f | ||
|
ab959623aa | ||
|
08d2655f13 | ||
|
20632ae1c1 | ||
|
bef7c38724 | ||
|
d1abf4971d | ||
|
d13c2cf8d7 | ||
|
3146273409 | ||
|
1891eb4998 | ||
|
70b03971f6 | ||
|
38c050b47e | ||
|
0bf5c9ebdd | ||
|
6683da804b | ||
|
7750b954fc | ||
|
edbff5a26a | ||
|
18846c11ed | ||
|
cc02c1d585 | ||
|
26bf7c4d46 | ||
|
2959fa1080 | ||
|
3f4f154949 | ||
|
4c0b472f67 | ||
|
4756238821 | ||
|
f5d26e0d81 | ||
|
e9bb5bee9d | ||
|
2c608bca3c | ||
|
d9c1e30e9b | ||
|
5bc72e2b44 | ||
|
df05d04dad | ||
|
888ac8f4c2 | ||
|
55266e5694 | ||
|
39c73e1649 | ||
|
3bf9d01f0a | ||
|
89ef33f7ca | ||
|
f71fe9a1a6 | ||
|
1008b1835b | ||
|
2ffa5419e2 | ||
|
bd20ecff78 | ||
|
a073514c46 | ||
|
c6ff14226f | ||
|
ee02f8255e | ||
|
5951ed3f55 | ||
|
f6fbf3ba0f | ||
|
9bce52a92a | ||
|
e44975622a | ||
|
92b857d83b | ||
|
671e538740 | ||
|
8a282fd2e1 | ||
|
cda623a95c | ||
|
0f343bccda | ||
|
a513f6f3f0 | ||
|
3b4809714e | ||
|
238b5ab9b9 | ||
|
08d9e9b6aa | ||
|
b22900eb3a | ||
|
89fc2c4779 | ||
|
353b79f5e5 | ||
|
b929b46281 | ||
|
5c4a536dad | ||
|
91e337307c | ||
|
2855b68ed4 | ||
|
45ca6284f9 | ||
|
145ee13356 | ||
|
6f97747199 | ||
|
f3751e4ba6 | ||
|
5cd55cada6 | ||
|
bad4b2ecb8 | ||
|
7aafd63ff3 | ||
|
3227a13035 | ||
|
027f96d100 | ||
|
f242a3c215 | ||
|
ad6c347180 | ||
|
4f0431da55 | ||
|
b873fdd029 | ||
|
c1ff820913 | ||
|
7008daf92a | ||
|
ed914c6907 | ||
|
73e81a54b4 | ||
|
ab8c66a361 | ||
|
4b55fefcb1 | ||
|
0eac8b25e1 | ||
|
aec556ff7d | ||
|
110dc29bd4 | ||
|
b1efea1bd9 | ||
|
8671467ed3 | ||
|
7851b6b429 | ||
|
8eb5f8b74e | ||
|
1830ee9fd2 | ||
|
dd615d6a8f | ||
|
08a518db70 | ||
|
581372de0b | ||
|
fe2c1c197e | ||
|
9854fddeb2 | ||
|
6c3918ebd2 | ||
|
4af496632f | ||
|
485ef1f2c2 | ||
|
cd1e7a1083 | ||
|
4dce9e9e47 | ||
|
dbeff4fd7d | ||
|
aef2e4845d | ||
|
739c6be476 | ||
|
ff502670bf | ||
|
3894dd1191 | ||
|
818537933a | ||
|
ec50808ba1 | ||
|
33fd8325be | ||
|
c72e0a14c0 | ||
|
58bc708014 | ||
|
ede1ed8b22 | ||
|
22e39b4434 | ||
|
9d984596cc | ||
|
fe909f659d | ||
|
ea120eae91 | ||
|
fa819d25b0 | ||
|
65739a5077 | ||
|
228d06e27f | ||
|
d386b83c53 | ||
|
e1b1f31cf1 | ||
|
f80b403dfe | ||
|
5af55d7da3 | ||
|
5e6a389f97 | ||
|
b2056f9e4d | ||
|
dca4bb204b | ||
|
ade39837de | ||
|
b9194e94aa |
7
.gitignore
vendored
@@ -35,4 +35,9 @@ _vieux/
|
|||||||
_mydocs
|
_mydocs
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Assets/DownloadBadges*.psd
|
Assets/DownloadBadges*.psd
|
||||||
node_modules
|
node_modules
|
||||||
|
Tools/github_oauth_token.txt
|
||||||
|
_releases
|
||||||
|
ReactNativeClient/lib/csstojs/
|
||||||
|
ElectronClient/app/gui/note-viewer/fonts/
|
||||||
|
Tools/commit_hook.txt
|
18
.travis.yml
@@ -1,5 +1,16 @@
|
|||||||
|
# Only build tags (Doesn't work - doesn't build anything)
|
||||||
|
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
|
||||||
@@ -43,7 +54,8 @@ before_install:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
cd ElectronClient/app
|
cd Tools
|
||||||
rsync -aP ../../ReactNativeClient/lib/ lib/
|
|
||||||
npm install
|
npm install
|
||||||
yarn dist
|
cd ../ElectronClient/app
|
||||||
|
rsync -aP --delete ../../ReactNativeClient/lib/ lib/
|
||||||
|
npm install && yarn dist
|
||||||
|
BIN
Assets/All.psd
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 30 KiB |
BIN
Assets/Icon.psd
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.3 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 |
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
iconutil --convert icns macOs.iconset
|
|
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1472 930v318q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q63 0 117 25 15 7 18 23 3 17-9 29l-49 49q-10 10-23 10-3 0-9-2-23-6-45-6h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-254q0-13 9-22l64-64q10-10 23-10 6 0 12 3 20 8 20 29zm231-489l-814 814q-24 24-57 24t-57-24l-430-430q-24-24-24-57t24-57l110-110q24-24 57-24t57 24l263 263 647-647q24-24 57-24t57 24l110 110q24 24 24 57t-24 57z"/></svg>
|
|
Before Width: | Height: | Size: 612 B |
@@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M813 1299l614-614q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-467 467-211-211q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l358 358q19 19 45 19t45-19zm851-883v960q0 119-84.5 203.5t-203.5 84.5h-960q-119 0-203.5-84.5t-84.5-203.5v-960q0-119 84.5-203.5t203.5-84.5h960q119 0 203.5 84.5t84.5 203.5z"/></svg>
|
|
Before Width: | Height: | Size: 447 B |
@@ -1,2 +0,0 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<svg viewBox='0 0 1792 1792' xmlns='http://www.w3.org/2000/svg'><path d='M1312 256h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-832q0-66-47-113t-113-47zm288 160v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z'/></svg>
|
|
Before Width: | Height: | Size: 370 B |
76
BUILD.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# 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.
|
||||||
|
- 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
|
||||||
|
|
||||||
|
brew install yarn node
|
||||||
|
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
|
||||||
|
source ~/.bash_profile
|
||||||
|
|
||||||
|
## Linux and Windows (WSL) dependencies
|
||||||
|
|
||||||
|
- Install yarn - https://yarnpkg.com/lang/en/docs/install/
|
||||||
|
- Install node v8.x (check with `node --version`) - https://nodejs.org/en/
|
||||||
|
- If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
|
||||||
|
|
||||||
|
# Building the tools
|
||||||
|
|
||||||
|
Before building any of the applications, you need to build the tools:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd Tools
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
# Building the Electron application
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ElectronClient/app
|
||||||
|
rsync --delete -a ../../ReactNativeClient/lib/ lib/
|
||||||
|
npm install
|
||||||
|
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 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
Then, from `/ReactNativeClient`, run `npm install`, then `react-native run-ios` or `react-native run-android`.
|
||||||
|
|
||||||
|
# Building the Terminal application
|
||||||
|
|
||||||
|
```
|
||||||
|
cd CliClient
|
||||||
|
npm install
|
||||||
|
./build.sh
|
||||||
|
rsync --delete -aP ../ReactNativeClient/locales/ build/locales/
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `run.sh` to start the application for testing.
|
26
CONTRIBUTING.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# User support
|
||||||
|
|
||||||
|
For general discussion about Joplin, user support, software development questions, and to discuss new features, please go to the [Joplin Forum](https://discourse.joplin.cozic.net/). It is possible to login with your GitHub account.
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# 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. "+1" comments are not tracked.
|
||||||
|
|
||||||
|
# Adding new features
|
||||||
|
|
||||||
|
If you want to add a new feature, consider asking about it before implementing it or checking existing discussions 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
There are only two rules, but not following them means the pull request will not be accepted (it can be accepted once the issues are fixed):
|
||||||
|
|
||||||
|
- **Please use tabs, NOT spaces.**
|
||||||
|
- **Please do not add or remove optional characters, such as spaces or colons.** Please setup your editor so that it only changes what you are working on and is not making automated changes elsewhere. The reason for this is that small white space changes make diff hard to read and can cause needless conflicts.
|
3
CliClient/.gitignore
vendored
@@ -18,4 +18,5 @@ tests/cli-integration/
|
|||||||
tests/sync
|
tests/sync
|
||||||
out.txt
|
out.txt
|
||||||
linkToLocal.sh
|
linkToLocal.sh
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
tests/support/dropbox-auth.txt
|
@@ -1,6 +1,6 @@
|
|||||||
const { _ } = require('lib/locale.js');
|
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 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");
|
||||||
|
@@ -1,19 +1,23 @@
|
|||||||
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 { Tag } = require('lib/models/tag.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const { BaseModel } = require('lib/base-model.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const { Note } = require('lib/models/note.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Resource } = require('lib/models/resource.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
const Resource = require('lib/models/Resource.js');
|
||||||
const { cliUtils } = require('./cli-utils.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');
|
||||||
const TermWrapper = require('tkwidgets/framework/TermWrapper.js');
|
const TermWrapper = require('tkwidgets/framework/TermWrapper.js');
|
||||||
const Renderer = require('tkwidgets/framework/Renderer.js');
|
const Renderer = require('tkwidgets/framework/Renderer.js');
|
||||||
|
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 ListWidget = require('tkwidgets/ListWidget.js');
|
||||||
@@ -34,37 +38,55 @@ const ConsoleWidget = require('./gui/ConsoleWidget.js');
|
|||||||
|
|
||||||
class AppGui {
|
class AppGui {
|
||||||
|
|
||||||
constructor(app, store) {
|
constructor(app, store, keymap) {
|
||||||
this.app_ = app;
|
try {
|
||||||
this.store_ = store;
|
this.app_ = app;
|
||||||
|
this.store_ = store;
|
||||||
|
|
||||||
BaseWidget.setLogger(app.logger());
|
BaseWidget.setLogger(app.logger());
|
||||||
|
|
||||||
this.term_ = new TermWrapper(tk.terminal);
|
this.term_ = new TermWrapper(tk.terminal);
|
||||||
|
|
||||||
this.renderer_ = null;
|
// Some keys are directly handled by the tkwidget framework
|
||||||
this.logger_ = new Logger();
|
// so they need to be remapped in a different way.
|
||||||
this.buildUi();
|
this.tkWidgetKeys_ = {
|
||||||
|
'focus_next': 'TAB',
|
||||||
|
'focus_previous': 'SHIFT_TAB',
|
||||||
|
'move_up': 'UP',
|
||||||
|
'move_down': 'DOWN',
|
||||||
|
'page_down': 'PAGE_DOWN',
|
||||||
|
'page_up': 'PAGE_UP',
|
||||||
|
};
|
||||||
|
|
||||||
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
this.renderer_ = null;
|
||||||
|
this.logger_ = new Logger();
|
||||||
|
this.buildUi();
|
||||||
|
|
||||||
this.app_.on('modelAction', async (event) => {
|
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
||||||
await this.handleModelAction(event.action);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.shortcuts_ = this.setupShortcuts();
|
this.app_.on('modelAction', async (event) => {
|
||||||
|
await this.handleModelAction(event.action);
|
||||||
|
});
|
||||||
|
|
||||||
this.inputMode_ = AppGui.INPUT_MODE_NORMAL;
|
this.keymap_ = this.setupKeymap(keymap);
|
||||||
|
|
||||||
this.commandCancelCalled_ = false;
|
this.inputMode_ = AppGui.INPUT_MODE_NORMAL;
|
||||||
|
|
||||||
this.currentShortcutKeys_ = [];
|
this.commandCancelCalled_ = false;
|
||||||
this.lastShortcutKeyTime_ = 0;
|
|
||||||
|
|
||||||
// Recurrent sync is setup only when the GUI is started. In
|
this.currentShortcutKeys_ = [];
|
||||||
// a regular command it's not necessary since the process
|
this.lastShortcutKeyTime_ = 0;
|
||||||
// exits right away.
|
|
||||||
reg.setupRecurrentSync();
|
// Recurrent sync is setup only when the GUI is started. In
|
||||||
|
// a regular command it's not necessary since the process
|
||||||
|
// exits right away.
|
||||||
|
reg.setupRecurrentSync();
|
||||||
|
DecryptionWorker.instance().scheduleStart();
|
||||||
|
} catch (error) {
|
||||||
|
this.fullScreen(false);
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
store() {
|
store() {
|
||||||
@@ -80,8 +102,16 @@ class AppGui {
|
|||||||
await this.renderer_.renderRoot();
|
await this.renderer_.renderRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt(initialText = '', promptString = ':') {
|
termSaveState() {
|
||||||
return this.widget('statusBar').prompt(initialText, promptString);
|
return this.term().saveState();
|
||||||
|
}
|
||||||
|
|
||||||
|
termRestoreState(state) {
|
||||||
|
return this.term().restoreState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt(initialText = '', promptString = ':', options = null) {
|
||||||
|
return this.widget('statusBar').prompt(initialText, promptString, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdoutMaxWidth() {
|
stdoutMaxWidth() {
|
||||||
@@ -95,6 +125,7 @@ class AppGui {
|
|||||||
buildUi() {
|
buildUi() {
|
||||||
this.rootWidget_ = new ReduxRootWidget(this.store_);
|
this.rootWidget_ = new ReduxRootWidget(this.store_);
|
||||||
this.rootWidget_.name = 'root';
|
this.rootWidget_.name = 'root';
|
||||||
|
this.rootWidget_.autoShortcutsEnabled = false;
|
||||||
|
|
||||||
const folderList = new FolderListWidget();
|
const folderList = new FolderListWidget();
|
||||||
folderList.style = {
|
folderList.style = {
|
||||||
@@ -259,148 +290,31 @@ class AppGui {
|
|||||||
|
|
||||||
addCommandToConsole(cmd) {
|
addCommandToConsole(cmd) {
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
|
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
|
||||||
|
if (isConfigPassword) return;
|
||||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
this.stdout(chalk.cyan.bold('> ' + cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
setupShortcuts() {
|
setupKeymap(keymap) {
|
||||||
const shortcuts = {};
|
const output = [];
|
||||||
|
|
||||||
shortcuts['TAB'] = {
|
for (let i = 0; i < keymap.length; i++) {
|
||||||
friendlyName: 'Tab',
|
const item = Object.assign({}, keymap[i]);
|
||||||
description: () => _('Give focus to next pane'),
|
|
||||||
isDocOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['SHIFT_TAB'] = {
|
if (!item.command) throw new Error('Missing command for keymap item: ' + JSON.stringify(item));
|
||||||
friendlyName: 'Shift+Tab',
|
|
||||||
description: () => _('Give focus to previous pane'),
|
|
||||||
isDocOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts[':'] = {
|
if (!('type' in item)) item.type = 'exec';
|
||||||
description: () => _('Enter command line mode'),
|
|
||||||
action: async () => {
|
|
||||||
const cmd = await this.widget('statusBar').prompt();
|
|
||||||
if (!cmd) return;
|
|
||||||
this.addCommandToConsole(cmd);
|
|
||||||
await this.processCommand(cmd);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
shortcuts['ESC'] = { // Built into terminal-kit inputField
|
if (item.command in this.tkWidgetKeys_) {
|
||||||
description: () => _('Exit command line mode'),
|
item.type = 'tkwidgets';
|
||||||
isDocOnly: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
shortcuts['ENTER'] = {
|
|
||||||
description: () => _('Edit the selected note'),
|
|
||||||
action: () => {
|
|
||||||
const w = this.widget('mainWindow').focusedWidget;
|
|
||||||
if (w.name === 'folderList') {
|
|
||||||
this.widget('noteList').focus();
|
|
||||||
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
|
||||||
this.processCommand('edit $n');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['CTRL_C'] = {
|
|
||||||
description: () => _('Cancel the current command.'),
|
|
||||||
friendlyName: 'Ctrl+C',
|
|
||||||
isDocOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['CTRL_D'] = {
|
|
||||||
description: () => _('Exit the application.'),
|
|
||||||
friendlyName: 'Ctrl+D',
|
|
||||||
isDocOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['DELETE'] = {
|
|
||||||
description: () => _('Delete the currently selected note or notebook.'),
|
|
||||||
action: async () => {
|
|
||||||
if (this.widget('folderList').hasFocus) {
|
|
||||||
const item = this.widget('folderList').selectedJoplinItem;
|
|
||||||
if (item.type_ === BaseModel.TYPE_FOLDER) {
|
|
||||||
await this.processCommand('rmbook ' + item.id);
|
|
||||||
} else if (item.type_ === BaseModel.TYPE_TAG) {
|
|
||||||
this.stdout(_('To delete a tag, untag the associated notes.'));
|
|
||||||
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
|
||||||
this.store().dispatch({
|
|
||||||
type: 'SEARCH_DELETE',
|
|
||||||
id: item.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (this.widget('noteList').hasFocus) {
|
|
||||||
await this.processCommand('rmnote $n');
|
|
||||||
} else {
|
|
||||||
this.stdout(_('Please select the note or notebook to be deleted first.'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
shortcuts[' '] = {
|
item.canRunAlongOtherCommands = item.type === 'function' && ['toggle_metadata', 'toggle_console'].indexOf(item.command) >= 0;
|
||||||
friendlyName: 'SPACE',
|
|
||||||
description: () => _('Set a to-do as completed / not completed'),
|
output.push(item);
|
||||||
action: 'todo toggle $n',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcuts['tc'] = {
|
return output;
|
||||||
description: () => _('[t]oggle [c]onsole between maximized/minimized/hidden/visible.'),
|
|
||||||
action: () => {
|
|
||||||
if (!this.consoleIsShown()) {
|
|
||||||
this.showConsole();
|
|
||||||
this.minimizeConsole();
|
|
||||||
} else {
|
|
||||||
if (this.consoleIsMaximized()) {
|
|
||||||
this.hideConsole();
|
|
||||||
} else {
|
|
||||||
this.maximizeConsole();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
canRunAlongOtherCommands: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['/'] = {
|
|
||||||
description: () => _('Search'),
|
|
||||||
action: { type: 'prompt', initialText: 'search ""', cursorPosition: -2 },
|
|
||||||
};
|
|
||||||
|
|
||||||
shortcuts['tm'] = {
|
|
||||||
description: () => _('[t]oggle note [m]etadata.'),
|
|
||||||
action: () => {
|
|
||||||
this.toggleNoteMetadata();
|
|
||||||
},
|
|
||||||
canRunAlongOtherCommands: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['mn'] = {
|
|
||||||
description: () => _('[M]ake a new [n]ote'),
|
|
||||||
action: { type: 'prompt', initialText: 'mknote ""', cursorPosition: -2 },
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['mt'] = {
|
|
||||||
description: () => _('[M]ake a new [t]odo'),
|
|
||||||
action: { type: 'prompt', initialText: 'mktodo ""', cursorPosition: -2 },
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['mb'] = {
|
|
||||||
description: () => _('[M]ake a new note[b]ook'),
|
|
||||||
action: { type: 'prompt', initialText: 'mkbook ""', cursorPosition: -2 },
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['yn'] = {
|
|
||||||
description: () => _('Copy ([Y]ank) the [n]ote to a notebook.'),
|
|
||||||
action: { type: 'prompt', initialText: 'cp $n ""', cursorPosition: -2 },
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts['dn'] = {
|
|
||||||
description: () => _('Move the note to a notebook.'),
|
|
||||||
action: { type: 'prompt', initialText: 'mv $n ""', cursorPosition: -2 },
|
|
||||||
}
|
|
||||||
|
|
||||||
return shortcuts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleConsole() {
|
toggleConsole() {
|
||||||
@@ -475,8 +389,16 @@ class AppGui {
|
|||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcuts() {
|
keymap() {
|
||||||
return this.shortcuts_;
|
return this.keymap_;
|
||||||
|
}
|
||||||
|
|
||||||
|
keymapItemByKey(key) {
|
||||||
|
for (let i = 0; i < this.keymap_.length; i++) {
|
||||||
|
const item = this.keymap_[i];
|
||||||
|
if (item.keys.indexOf(key) >= 0) return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
term() {
|
term() {
|
||||||
@@ -507,40 +429,104 @@ class AppGui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async processCommand(cmd) {
|
async processFunctionCommand(cmd) {
|
||||||
|
|
||||||
|
if (cmd === 'activate') {
|
||||||
|
|
||||||
|
const w = this.widget('mainWindow').focusedWidget;
|
||||||
|
if (w.name === 'folderList') {
|
||||||
|
this.widget('noteList').focus();
|
||||||
|
} else if (w.name === 'noteList' || w.name === 'noteText') {
|
||||||
|
this.processPromptCommand('edit $n');
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cmd === 'delete') {
|
||||||
|
|
||||||
|
if (this.widget('folderList').hasFocus) {
|
||||||
|
const item = this.widget('folderList').selectedJoplinItem;
|
||||||
|
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
if (item.type_ === BaseModel.TYPE_FOLDER) {
|
||||||
|
await this.processPromptCommand('rmbook ' + item.id);
|
||||||
|
} else if (item.type_ === BaseModel.TYPE_TAG) {
|
||||||
|
this.stdout(_('To delete a tag, untag the associated notes.'));
|
||||||
|
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
||||||
|
this.store().dispatch({
|
||||||
|
type: 'SEARCH_DELETE',
|
||||||
|
id: item.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (this.widget('noteList').hasFocus) {
|
||||||
|
await this.processPromptCommand('rmnote $n');
|
||||||
|
} else {
|
||||||
|
this.stdout(_('Please select the note or notebook to be deleted first.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cmd === 'toggle_console') {
|
||||||
|
|
||||||
|
if (!this.consoleIsShown()) {
|
||||||
|
this.showConsole();
|
||||||
|
this.minimizeConsole();
|
||||||
|
} else {
|
||||||
|
if (this.consoleIsMaximized()) {
|
||||||
|
this.hideConsole();
|
||||||
|
} else {
|
||||||
|
this.maximizeConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (cmd === 'toggle_metadata') {
|
||||||
|
|
||||||
|
this.toggleNoteMetadata();
|
||||||
|
|
||||||
|
} else if (cmd === 'enter_command_line_mode') {
|
||||||
|
|
||||||
|
const cmd = await this.widget('statusBar').prompt();
|
||||||
|
if (!cmd) return;
|
||||||
|
this.addCommandToConsole(cmd);
|
||||||
|
await this.processPromptCommand(cmd);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
throw new Error('Unknown command: ' + cmd);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async processPromptCommand(cmd) {
|
||||||
if (!cmd) return;
|
if (!cmd) return;
|
||||||
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);
|
||||||
|
|
||||||
if (cmd === 'q' || cmd === 'wq' || cmd === 'qa') { // Vim bonus
|
try {
|
||||||
await this.app().exit();
|
let note = this.widget('noteList').currentItem;
|
||||||
return;
|
let folder = this.widget('folderList').currentItem;
|
||||||
}
|
let args = splitCommandString(cmd);
|
||||||
|
|
||||||
let note = this.widget('noteList').currentItem;
|
for (let i = 0; i < args.length; i++) {
|
||||||
let folder = this.widget('folderList').currentItem;
|
if (args[i] == '$n') {
|
||||||
let args = splitCommandString(cmd);
|
args[i] = note ? note.id : '';
|
||||||
|
} else if (args[i] == '$b') {
|
||||||
for (let i = 0; i < args.length; i++) {
|
args[i] = folder ? folder.id : '';
|
||||||
if (args[i] == '$n') {
|
} else if (args[i] == '$c') {
|
||||||
args[i] = note ? note.id : '';
|
const item = this.activeListItem();
|
||||||
} else if (args[i] == '$b') {
|
args[i] = item ? item.id : '';
|
||||||
args[i] = folder ? folder.id : '';
|
}
|
||||||
} else if (args[i] == '$c') {
|
|
||||||
const item = this.activeListItem();
|
|
||||||
args[i] = item ? item.id : '';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.app().execCommand(args);
|
await this.app().execCommand(args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.stdout(error.message);
|
this.stdout(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.widget('console').scrollBottom();
|
this.widget('console').scrollBottom();
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
this.widget('root').invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFolderList() {
|
async updateFolderList() {
|
||||||
@@ -655,12 +641,27 @@ class AppGui {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +677,7 @@ 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'])) {
|
||||||
@@ -765,32 +766,34 @@ class AppGui {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
const shortcutKey = this.currentShortcutKeys_.join('');
|
const shortcutKey = this.currentShortcutKeys_.join('');
|
||||||
const cmd = shortcutKey in this.shortcuts_ ? this.shortcuts_[shortcutKey] : null;
|
let keymapItem = this.keymapItemByKey(shortcutKey);
|
||||||
|
|
||||||
let processShortcutKeys = !this.app().currentCommand() && cmd;
|
// If this command is an alias to another command, resolve to the actual command
|
||||||
if (cmd && cmd.canRunAlongOtherCommands) processShortcutKeys = true;
|
|
||||||
|
let processShortcutKeys = !this.app().currentCommand() && keymapItem;
|
||||||
|
if (keymapItem && keymapItem.canRunAlongOtherCommands) processShortcutKeys = true;
|
||||||
if (statusBar.promptActive) processShortcutKeys = false;
|
if (statusBar.promptActive) processShortcutKeys = false;
|
||||||
if (cmd && cmd.isDocOnly) processShortcutKeys = false;
|
|
||||||
|
|
||||||
if (processShortcutKeys) {
|
if (processShortcutKeys) {
|
||||||
this.logger().info('Shortcut:', shortcutKey, cmd.description());
|
this.logger().debug('Shortcut:', shortcutKey, keymapItem);
|
||||||
|
|
||||||
this.currentShortcutKeys_ = [];
|
this.currentShortcutKeys_ = [];
|
||||||
if (typeof cmd.action === 'function') {
|
|
||||||
await cmd.action();
|
if (keymapItem.type === 'function') {
|
||||||
} else if (typeof cmd.action === 'object') {
|
this.processFunctionCommand(keymapItem.command);
|
||||||
if (cmd.action.type === 'prompt') {
|
} else if (keymapItem.type === 'prompt') {
|
||||||
let promptOptions = {};
|
let promptOptions = {};
|
||||||
if ('cursorPosition' in cmd.action) promptOptions.cursorPosition = cmd.action.cursorPosition;
|
if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition;
|
||||||
const commandString = await statusBar.prompt(cmd.action.initialText ? cmd.action.initialText : '', null, promptOptions);
|
const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions);
|
||||||
this.addCommandToConsole(commandString);
|
this.addCommandToConsole(commandString);
|
||||||
await this.processCommand(commandString);
|
await this.processPromptCommand(commandString);
|
||||||
} else {
|
} else if (keymapItem.type === 'exec') {
|
||||||
throw new Error('Unknown command: ' + JSON.stringify(cmd.action));
|
this.stdout(keymapItem.command);
|
||||||
}
|
await this.processPromptCommand(keymapItem.command);
|
||||||
} else { // String
|
} else if (keymapItem.type === 'tkwidgets') {
|
||||||
this.stdout(cmd.action);
|
this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]);
|
||||||
await this.processCommand(cmd.action);
|
} else {
|
||||||
|
throw new Error('Unknown command type: ' + JSON.stringify(keymapItem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,4 +819,4 @@ class AppGui {
|
|||||||
AppGui.INPUT_MODE_NORMAL = 1;
|
AppGui.INPUT_MODE_NORMAL = 1;
|
||||||
AppGui.INPUT_MODE_META = 2;
|
AppGui.INPUT_MODE_META = 2;
|
||||||
|
|
||||||
module.exports = AppGui;
|
module.exports = AppGui;
|
||||||
|
@@ -5,12 +5,13 @@ const { JoplinDatabase } = require('lib/joplin-database.js');
|
|||||||
const { Database } = require('lib/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 { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||||
const { BaseModel } = require('lib/base-model.js');
|
const ResourceService = require('lib/services/ResourceService');
|
||||||
const { Folder } = require('lib/models/folder.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { BaseItem } = require('lib/models/base-item.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const { Note } = require('lib/models/note.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const { Tag } = require('lib/models/tag.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { Setting } = require('lib/models/setting.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
@@ -21,6 +22,7 @@ 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 EventEmitter = require('events');
|
||||||
|
const Cache = require('lib/Cache');
|
||||||
|
|
||||||
class Application extends BaseApplication {
|
class Application extends BaseApplication {
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ class Application extends BaseApplication {
|
|||||||
this.allCommandsLoaded_ = false;
|
this.allCommandsLoaded_ = false;
|
||||||
this.showStackTraces_ = false;
|
this.showStackTraces_ = false;
|
||||||
this.gui_ = null;
|
this.gui_ = null;
|
||||||
|
this.cache_ = new Cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
gui() {
|
gui() {
|
||||||
@@ -144,13 +147,15 @@ class Application extends BaseApplication {
|
|||||||
message += ' (' + options.answers.join('/') + ')';
|
message += ' (' + options.answers.join('/') + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
let answer = await this.gui().prompt('', message + ' ');
|
let answer = await this.gui().prompt('', message + ' ', options);
|
||||||
|
|
||||||
if (options.type === 'boolean') {
|
if (options.type === 'boolean') {
|
||||||
if (answer === null) return false; // Pressed ESCAPE
|
if (answer === null) return false; // Pressed ESCAPE
|
||||||
if (!answer) answer = options.answers[0];
|
if (!answer) answer = options.answers[0];
|
||||||
let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
|
let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1;
|
||||||
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
||||||
|
} else {
|
||||||
|
return answer;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,22 +182,33 @@ class Application extends BaseApplication {
|
|||||||
await doExit();
|
await doExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
commands() {
|
commands(uiType = null) {
|
||||||
if (this.allCommandsLoaded_) return this.commands_;
|
if (!this.allCommandsLoaded_) {
|
||||||
|
fs.readdirSync(__dirname).forEach((path) => {
|
||||||
|
if (path.indexOf('command-') !== 0) return;
|
||||||
|
const ext = fileExtension(path)
|
||||||
|
if (ext != 'js') return;
|
||||||
|
|
||||||
fs.readdirSync(__dirname).forEach((path) => {
|
let CommandClass = require('./' + path);
|
||||||
if (path.indexOf('command-') !== 0) return;
|
let cmd = new CommandClass();
|
||||||
const ext = fileExtension(path)
|
if (!cmd.enabled()) return;
|
||||||
if (ext != 'js') return;
|
cmd = this.setupCommand(cmd);
|
||||||
|
this.commands_[cmd.name()] = cmd;
|
||||||
|
});
|
||||||
|
|
||||||
let CommandClass = require('./' + path);
|
this.allCommandsLoaded_ = true;
|
||||||
let cmd = new CommandClass();
|
}
|
||||||
if (!cmd.enabled()) return;
|
|
||||||
cmd = this.setupCommand(cmd);
|
|
||||||
this.commands_[cmd.name()] = cmd;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.allCommandsLoaded_ = true;
|
if (uiType !== null) {
|
||||||
|
let temp = [];
|
||||||
|
for (let n in this.commands_) {
|
||||||
|
if (!this.commands_.hasOwnProperty(n)) continue;
|
||||||
|
const c = this.commands_[n];
|
||||||
|
if (!c.supportsUi(uiType)) continue;
|
||||||
|
temp[n] = c;
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
return this.commands_;
|
return this.commands_;
|
||||||
}
|
}
|
||||||
@@ -210,12 +226,8 @@ class Application extends BaseApplication {
|
|||||||
async commandMetadata() {
|
async commandMetadata() {
|
||||||
if (this.commandMetadata_) return this.commandMetadata_;
|
if (this.commandMetadata_) return this.commandMetadata_;
|
||||||
|
|
||||||
const osTmpdir = require('os-tmpdir');
|
let output = await this.cache_.getItem('metadata');
|
||||||
const storage = require('node-persist');
|
if (output) {
|
||||||
await storage.init({ dir: osTmpdir() + '/commandMetadata', ttl: 1000 * 60 * 60 * 24 });
|
|
||||||
|
|
||||||
let output = await storage.getItem('metadata');
|
|
||||||
if (Setting.value('env') != 'dev' && output) {
|
|
||||||
this.commandMetadata_ = output;
|
this.commandMetadata_ = output;
|
||||||
return Object.assign({}, this.commandMetadata_);
|
return Object.assign({}, this.commandMetadata_);
|
||||||
}
|
}
|
||||||
@@ -229,7 +241,7 @@ class Application extends BaseApplication {
|
|||||||
output[n] = cmd.metadata();
|
output[n] = cmd.metadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
await storage.setItem('metadata', output);
|
await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
this.commandMetadata_ = output;
|
this.commandMetadata_ = output;
|
||||||
return Object.assign({}, this.commandMetadata_);
|
return Object.assign({}, this.commandMetadata_);
|
||||||
@@ -246,9 +258,13 @@ class Application extends BaseApplication {
|
|||||||
try {
|
try {
|
||||||
CommandClass = require(__dirname + '/command-' + name + '.js');
|
CommandClass = require(__dirname + '/command-' + name + '.js');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let e = new Error('No such command: ' + name);
|
if (error.message && error.message.indexOf('Cannot find module') >= 0) {
|
||||||
e.type = 'notFound';
|
let e = new Error(_('No such command: %s', name));
|
||||||
throw e;
|
e.type = 'notFound';
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd = new CommandClass();
|
let cmd = new CommandClass();
|
||||||
@@ -260,7 +276,7 @@ class Application extends BaseApplication {
|
|||||||
dummyGui() {
|
dummyGui() {
|
||||||
return {
|
return {
|
||||||
isDummy: () => { return true; },
|
isDummy: () => { return true; },
|
||||||
prompt: (initialText = '', promptString = '') => { return cliUtils.prompt(initialText, promptString); },
|
prompt: (initialText = '', promptString = '', options = null) => { return cliUtils.prompt(initialText, promptString, options); },
|
||||||
showConsole: () => {},
|
showConsole: () => {},
|
||||||
maximizeConsole: () => {},
|
maximizeConsole: () => {},
|
||||||
stdout: (text) => { console.info(text); },
|
stdout: (text) => { console.info(text); },
|
||||||
@@ -268,13 +284,16 @@ class Application extends BaseApplication {
|
|||||||
exit: () => {},
|
exit: () => {},
|
||||||
showModalOverlay: (text) => {},
|
showModalOverlay: (text) => {},
|
||||||
hideModalOverlay: () => {},
|
hideModalOverlay: () => {},
|
||||||
stdoutMaxWidth: () => { return 78; }
|
stdoutMaxWidth: () => { return 100; },
|
||||||
|
forceRender: () => {},
|
||||||
|
termSaveState: () => {},
|
||||||
|
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);
|
||||||
|
|
||||||
@@ -294,6 +313,63 @@ class Application extends BaseApplication {
|
|||||||
return this.activeCommand_;
|
return this.activeCommand_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadKeymaps() {
|
||||||
|
const defaultKeyMap = [
|
||||||
|
{ "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
|
||||||
|
{ "keys": ["TAB"], "type": "function", "command": "focus_next" },
|
||||||
|
{ "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
|
||||||
|
{ "keys": ["UP"], "type": "function", "command": "move_up" },
|
||||||
|
{ "keys": ["DOWN"], "type": "function", "command": "move_down" },
|
||||||
|
{ "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
|
||||||
|
{ "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
|
||||||
|
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
|
||||||
|
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
|
||||||
|
{ "keys": [" "], "command": "todo toggle $n" },
|
||||||
|
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
|
||||||
|
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
|
||||||
|
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
|
||||||
|
{ "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
|
||||||
|
{ "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
|
||||||
|
{ "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
|
||||||
|
{ "keys": ["yn"], "type": "prompt", "command": "cp $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
|
||||||
|
// the default ones.
|
||||||
|
const itemsByCommand = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < defaultKeyMap.length; i++) {
|
||||||
|
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = Setting.value('profileDir') + '/keymap.json';
|
||||||
|
if (await fs.pathExists(filePath)) {
|
||||||
|
try {
|
||||||
|
let configString = await fs.readFile(filePath, 'utf-8');
|
||||||
|
configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments
|
||||||
|
const keymap = JSON.parse(configString);
|
||||||
|
for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) {
|
||||||
|
const item = keymap[keymapIndex];
|
||||||
|
itemsByCommand[item.command] = item;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let msg = error.message ? error.message : '';
|
||||||
|
msg = 'Could not load keymap ' + filePath + '\n' + msg;
|
||||||
|
error.message = msg;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = [];
|
||||||
|
for (let n in itemsByCommand) {
|
||||||
|
if (!itemsByCommand.hasOwnProperty(n)) continue;
|
||||||
|
output.push(itemsByCommand[n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
async start(argv) {
|
async start(argv) {
|
||||||
argv = await super.start(argv);
|
argv = await super.start(argv);
|
||||||
|
|
||||||
@@ -306,20 +382,25 @@ class Application extends BaseApplication {
|
|||||||
if (argv.length) {
|
if (argv.length) {
|
||||||
this.gui_ = this.dummyGui();
|
this.gui_ = this.dummyGui();
|
||||||
|
|
||||||
|
this.currentFolder_ = await Folder.load(Setting.value('activeFolderId'));
|
||||||
|
|
||||||
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
|
} else { // Otherwise open the GUI
|
||||||
this.initRedux();
|
this.initRedux();
|
||||||
|
|
||||||
|
const keymap = await this.loadKeymaps();
|
||||||
|
|
||||||
const AppGui = require('./app-gui.js');
|
const AppGui = require('./app-gui.js');
|
||||||
this.gui_ = new AppGui(this, this.store());
|
this.gui_ = new AppGui(this, this.store(), keymap);
|
||||||
this.gui_.setLogger(this.logger_);
|
this.gui_.setLogger(this.logger_);
|
||||||
await this.gui_.start();
|
await this.gui_.start();
|
||||||
|
|
||||||
@@ -332,9 +413,11 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
const tags = await Tag.allWithNotes();
|
const tags = await Tag.allWithNotes();
|
||||||
|
|
||||||
|
ResourceService.runInBackground();
|
||||||
|
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'TAG_UPDATE_ALL',
|
type: 'TAG_UPDATE_ALL',
|
||||||
tags: tags,
|
items: tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.store().dispatch({
|
this.store().dispatch({
|
||||||
|
199
CliClient/app/autocompletion.js
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
var { app } = require('./app.js');
|
||||||
|
var Note = require('lib/models/Note.js');
|
||||||
|
var Folder = require('lib/models/Folder.js');
|
||||||
|
var Tag = require('lib/models/Tag.js');
|
||||||
|
var { cliUtils } = require('./cli-utils.js');
|
||||||
|
var yargParser = require('yargs-parser');
|
||||||
|
var fs = require('fs-extra');
|
||||||
|
|
||||||
|
async function handleAutocompletionPromise(line) {
|
||||||
|
// Auto-complete the command name
|
||||||
|
const names = await app().commandNames();
|
||||||
|
let words = getArguments(line);
|
||||||
|
//If there is only one word and it is not already a command name then you
|
||||||
|
//should look for commmands it could be
|
||||||
|
if (words.length == 1) {
|
||||||
|
if (names.indexOf(words[0]) === -1) {
|
||||||
|
let x = names.filter((n) => n.indexOf(words[0]) === 0);
|
||||||
|
if (x.length === 1) {
|
||||||
|
return x[0] + ' ';
|
||||||
|
}
|
||||||
|
return x.length > 0 ? x.map((a) => a + ' ') : line;
|
||||||
|
} else {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//There is more than one word and it is a command
|
||||||
|
const metadata = (await app().commandMetadata())[words[0]];
|
||||||
|
//If for some reason this command does not have any associated metadata
|
||||||
|
//just don't autocomplete. However, this should not happen.
|
||||||
|
if (metadata === undefined) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
//complete an option
|
||||||
|
let next = words.length > 1 ? words[words.length - 1] : '';
|
||||||
|
let l = [];
|
||||||
|
if (next[0] === '-') {
|
||||||
|
for (let i = 0; i<metadata.options.length; i++) {
|
||||||
|
const options = metadata.options[i][0].split(' ');
|
||||||
|
//if there are multiple options then they will be separated by comma and
|
||||||
|
//space. The comma should be removed
|
||||||
|
if (options[0][options[0].length - 1] === ',') {
|
||||||
|
options[0] = options[0].slice(0, -1);
|
||||||
|
}
|
||||||
|
if (words.includes(options[0]) || words.includes(options[1])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//First two elements are the flag and the third is the description
|
||||||
|
//Only autocomplete long
|
||||||
|
if (options.length > 1 && options[1].indexOf(next) === 0) {
|
||||||
|
l.push(options[1]);
|
||||||
|
} else if (options[0].indexOf(next) === 0) {
|
||||||
|
l.push(options[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (l.length === 0) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
let ret = l.map(a=>toCommandLine(a));
|
||||||
|
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
//Complete an argument
|
||||||
|
//Determine the number of positional arguments by counting the number of
|
||||||
|
//words that don't start with a - less one for the command name
|
||||||
|
const positionalArgs = words.filter((a)=>a.indexOf('-') !== 0).length - 1;
|
||||||
|
|
||||||
|
let cmdUsage = yargParser(metadata.usage)['_'];
|
||||||
|
cmdUsage.splice(0, 1);
|
||||||
|
|
||||||
|
if (cmdUsage.length >= positionalArgs) {
|
||||||
|
|
||||||
|
let argName = cmdUsage[positionalArgs - 1];
|
||||||
|
argName = cliUtils.parseCommandArg(argName).name;
|
||||||
|
|
||||||
|
const currentFolder = app().currentFolder();
|
||||||
|
|
||||||
|
if (argName == 'note' || argName == 'note-pattern') {
|
||||||
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
||||||
|
l.push(...notes.map((n) => n.title));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argName == 'notebook') {
|
||||||
|
const folders = await Folder.search({ titlePattern: next + '*' });
|
||||||
|
l.push(...folders.map((n) => n.title));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argName == 'item') {
|
||||||
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
|
||||||
|
const folders = await Folder.search({ titlePattern: next + '*' });
|
||||||
|
l.push(...notes.map((n) => n.title), folders.map((n) => n.title));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argName == 'tag') {
|
||||||
|
let tags = await Tag.search({ titlePattern: next + '*' });
|
||||||
|
l.push(...tags.map((n) => n.title));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argName == 'file') {
|
||||||
|
let files = await fs.readdir('.');
|
||||||
|
l.push(...files);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argName == 'tag-command') {
|
||||||
|
let c = filterList(['add', 'remove', 'list'], next);
|
||||||
|
l.push(...c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argName == 'todo-command') {
|
||||||
|
let c = filterList(['toggle', 'clear'], next);
|
||||||
|
l.push(...c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (l.length === 1) {
|
||||||
|
return toCommandLine([...words.slice(0, -1), l[0]]);
|
||||||
|
} else if (l.length > 1) {
|
||||||
|
let ret = l.map(a=>toCommandLine(a));
|
||||||
|
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
|
||||||
|
}
|
||||||
|
function handleAutocompletion(str, callback) {
|
||||||
|
handleAutocompletionPromise(str).then(function(res) {
|
||||||
|
callback(undefined, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function toCommandLine(args) {
|
||||||
|
if (Array.isArray(args)) {
|
||||||
|
return args.map(function(a) {
|
||||||
|
if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
||||||
|
return "'" + a + "'";
|
||||||
|
} else if (a.indexOf("'") !== -1) {
|
||||||
|
return '"' + a + '"';
|
||||||
|
} else {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}).join(' ');
|
||||||
|
} else {
|
||||||
|
if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
||||||
|
return "'" + args + "' ";
|
||||||
|
} else if (args.indexOf("'") !== -1) {
|
||||||
|
return '"' + args + '" ';
|
||||||
|
} else {
|
||||||
|
return args + ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getArguments(line) {
|
||||||
|
let inSingleQuotes = false;
|
||||||
|
let inDoubleQuotes = false;
|
||||||
|
let currentWord = '';
|
||||||
|
let parsed = [];
|
||||||
|
for(let i = 0; i<line.length; i++) {
|
||||||
|
if(line[i] === '"') {
|
||||||
|
if(inDoubleQuotes) {
|
||||||
|
inDoubleQuotes = false;
|
||||||
|
//maybe push word to parsed?
|
||||||
|
//currentWord += '"';
|
||||||
|
} else {
|
||||||
|
inDoubleQuotes = true;
|
||||||
|
//currentWord += '"';
|
||||||
|
}
|
||||||
|
} else if(line[i] === "'") {
|
||||||
|
if(inSingleQuotes) {
|
||||||
|
inSingleQuotes = false;
|
||||||
|
//maybe push word to parsed?
|
||||||
|
//currentWord += "'";
|
||||||
|
} else {
|
||||||
|
inSingleQuotes = true;
|
||||||
|
//currentWord += "'";
|
||||||
|
}
|
||||||
|
} else if (/\s/.test(line[i]) &&
|
||||||
|
!(inDoubleQuotes || inSingleQuotes)) {
|
||||||
|
if (currentWord !== '') {
|
||||||
|
parsed.push(currentWord);
|
||||||
|
currentWord = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentWord += line[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(inSingleQuotes || inDoubleQuotes) && /\s/.test(line[line.length - 1])) {
|
||||||
|
parsed.push('');
|
||||||
|
} else {
|
||||||
|
parsed.push(currentWord);
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
function filterList(list, next) {
|
||||||
|
let output = [];
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (list[i].indexOf(next) !== 0) continue;
|
||||||
|
output.push(list[i]);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { handleAutocompletion };
|
@@ -12,6 +12,10 @@ class BaseCommand {
|
|||||||
throw new Error('Usage not defined');
|
throw new Error('Usage not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encryptionCheck(item) {
|
||||||
|
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
||||||
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
throw new Error('Description not defined');
|
throw new Error('Description not defined');
|
||||||
}
|
}
|
||||||
@@ -28,10 +32,6 @@ class BaseCommand {
|
|||||||
return this.compatibleUis().indexOf(ui) >= 0;
|
return this.compatibleUis().indexOf(ui) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
aliases() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@@ -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://joplin.cozic.net');
|
||||||
|
|
||||||
output.push('');
|
output.push('');
|
||||||
|
|
||||||
|
@@ -1,165 +0,0 @@
|
|||||||
"use strict"
|
|
||||||
|
|
||||||
require('app-module-path').addPath(__dirname);
|
|
||||||
|
|
||||||
const processArgs = process.argv.splice(2, process.argv.length);
|
|
||||||
|
|
||||||
const silentLog = processArgs.indexOf('--silent') >= 0;
|
|
||||||
|
|
||||||
const { basename, dirname, filename, fileExtension } = require('lib/path-utils.js');
|
|
||||||
const fs = require('fs-extra');
|
|
||||||
const gettextParser = require('gettext-parser');
|
|
||||||
|
|
||||||
const rootDir = dirname(dirname(__dirname));
|
|
||||||
const cliDir = rootDir + '/CliClient';
|
|
||||||
const cliLocalesDir = cliDir + '/locales';
|
|
||||||
const rnDir = rootDir + '/ReactNativeClient';
|
|
||||||
const electronDir = rootDir + '/ElectronClient/app';
|
|
||||||
|
|
||||||
function execCommand(command) {
|
|
||||||
if (!silentLog) console.info('Running: ' + command);
|
|
||||||
|
|
||||||
const exec = require('child_process').exec
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let childProcess = exec(command, (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
if (error.signal == 'SIGTERM') {
|
|
||||||
resolve('Process was killed');
|
|
||||||
} else {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve(stdout.trim());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePoFile(filePath) {
|
|
||||||
const content = fs.readFileSync(filePath);
|
|
||||||
return gettextParser.po.parse(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeTranslation(translation) {
|
|
||||||
let output = {};
|
|
||||||
const translations = translation.translations[''];
|
|
||||||
for (let n in translations) {
|
|
||||||
if (!translations.hasOwnProperty(n)) continue;
|
|
||||||
if (n == '') continue;
|
|
||||||
const t = translations[n];
|
|
||||||
if (t.comments && t.comments.flag && t.comments.flag.indexOf('fuzzy') >= 0) {
|
|
||||||
output[n] = t['msgid'];
|
|
||||||
} else {
|
|
||||||
output[n] = t['msgstr'][0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JSON.stringify(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveToFile(filePath, data) {
|
|
||||||
fs.writeFileSync(filePath, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLocale(inputFile, outputFile) {
|
|
||||||
const r = parsePoFile(inputFile);
|
|
||||||
const translation = serializeTranslation(r);
|
|
||||||
saveToFile(outputFile, translation);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removePoHeaderDate(filePath) {
|
|
||||||
await execCommand('sed -i -e\'/POT-Creation-Date:/d\' "' + filePath + '"');
|
|
||||||
await execCommand('sed -i -e\'/PO-Revision-Date:/d\' "' + filePath + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPotFile(potFilePath, sources) {
|
|
||||||
let baseArgs = [];
|
|
||||||
baseArgs.push('--from-code=utf-8');
|
|
||||||
baseArgs.push('--output="' + potFilePath + '"');
|
|
||||||
baseArgs.push('--language=JavaScript');
|
|
||||||
baseArgs.push('--copyright-holder="Laurent Cozic"');
|
|
||||||
baseArgs.push('--package-name=Joplin-CLI');
|
|
||||||
baseArgs.push('--package-version=1.0.0');
|
|
||||||
baseArgs.push('--no-location');
|
|
||||||
|
|
||||||
for (let i = 0; i < sources.length; i++) {
|
|
||||||
let args = baseArgs.slice();
|
|
||||||
if (i > 0) args.push('--join-existing');
|
|
||||||
args.push(sources[i]);
|
|
||||||
const result = await execCommand('xgettext ' + args.join(' '));
|
|
||||||
if (result) console.error(result);
|
|
||||||
await removePoHeaderDate(potFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mergePotToPo(potFilePath, poFilePath) {
|
|
||||||
const command = 'msgmerge -U "' + poFilePath + '" "' + potFilePath + '"';
|
|
||||||
const result = await execCommand(command);
|
|
||||||
if (result) console.error(result);
|
|
||||||
await removePoHeaderDate(poFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildIndex(locales) {
|
|
||||||
let output = [];
|
|
||||||
output.push('var locales = {};');
|
|
||||||
for (let i = 0; i < locales.length; i++) {
|
|
||||||
const locale = locales[i];
|
|
||||||
output.push("locales['" + locale + "'] = require('./" + locale + ".json');");
|
|
||||||
}
|
|
||||||
output.push('module.exports = { locales: locales };');
|
|
||||||
return output.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function availableLocales(defaultLocale) {
|
|
||||||
const output = [defaultLocale];
|
|
||||||
fs.readdirSync(cliLocalesDir).forEach((path) => {
|
|
||||||
if (fileExtension(path) !== 'po') return;
|
|
||||||
const locale = filename(path);
|
|
||||||
if (locale === defaultLocale) return;
|
|
||||||
output.push(locale);
|
|
||||||
});
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let potFilePath = cliLocalesDir + '/joplin.pot';
|
|
||||||
let jsonLocalesDir = cliDir + '/build/locales';
|
|
||||||
const defaultLocale = 'en_GB';
|
|
||||||
|
|
||||||
await createPotFile(potFilePath, [
|
|
||||||
cliDir + '/app/*.js',
|
|
||||||
cliDir + '/app/gui/*.js',
|
|
||||||
electronDir + '/*.js',
|
|
||||||
electronDir + '/gui/*.js',
|
|
||||||
rnDir + '/lib/*.js',
|
|
||||||
rnDir + '/lib/models/*.js',
|
|
||||||
rnDir + '/lib/services/*.js',
|
|
||||||
rnDir + '/lib/components/*.js',
|
|
||||||
rnDir + '/lib/components/screens/*.js',
|
|
||||||
]);
|
|
||||||
|
|
||||||
await execCommand('cp "' + potFilePath + '" ' + '"' + cliLocalesDir + '/' + defaultLocale + '.po"');
|
|
||||||
|
|
||||||
fs.mkdirpSync(jsonLocalesDir, 0o755);
|
|
||||||
|
|
||||||
let locales = availableLocales(defaultLocale);
|
|
||||||
for (let i = 0; i < locales.length; i++) {
|
|
||||||
const locale = locales[i];
|
|
||||||
const poFilePäth = cliLocalesDir + '/' + locale + '.po';
|
|
||||||
const jsonFilePath = jsonLocalesDir + '/' + locale + '.json';
|
|
||||||
if (locale != defaultLocale) await mergePotToPo(potFilePath, poFilePäth);
|
|
||||||
buildLocale(poFilePäth, jsonFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToFile(jsonLocalesDir + '/index.js', buildIndex(locales));
|
|
||||||
|
|
||||||
const rnJsonLocaleDir = rnDir + '/locales';
|
|
||||||
await execCommand('rsync -a "' + jsonLocalesDir + '/" "' + rnJsonLocaleDir + '"');
|
|
||||||
|
|
||||||
const electronJsonLocaleDir = electronDir + '/locales';
|
|
||||||
await execCommand('rsync -a "' + jsonLocalesDir + '/" "' + electronJsonLocaleDir + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
@@ -5,10 +5,10 @@ const { Logger } = require('lib/logger.js');
|
|||||||
const { dirname } = require('lib/path-utils.js');
|
const { dirname } = require('lib/path-utils.js');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||||
const { BaseModel } = require('lib/base-model.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 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
|
||||||
|
|
||||||
|
@@ -178,38 +178,39 @@ cliUtils.promptConfirm = function(message, answers = null) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cliUtils.promptInput = function(message) {
|
|
||||||
const readline = require('readline');
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
rl.question(message + ' ', (answer) => {
|
|
||||||
rl.close();
|
|
||||||
resolve(answer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
// with readline.question?).
|
// with readline.question?).
|
||||||
cliUtils.prompt = function(initialText = '', promptString = ':') {
|
cliUtils.prompt = function(initialText = '', promptString = ':', options = null) {
|
||||||
|
if (!options) options = {};
|
||||||
|
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
|
const Writable = require('stream').Writable;
|
||||||
|
|
||||||
|
const mutableStdout = new Writable({
|
||||||
|
write: function(chunk, encoding, callback) {
|
||||||
|
if (!this.muted)
|
||||||
|
process.stdout.write(chunk, encoding);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: mutableStdout,
|
||||||
|
terminal: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
mutableStdout.muted = false;
|
||||||
|
|
||||||
rl.question(promptString, (answer) => {
|
rl.question(promptString, (answer) => {
|
||||||
rl.close();
|
rl.close();
|
||||||
|
if (!!options.secure) this.stdout_('');
|
||||||
resolve(answer);
|
resolve(answer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mutableStdout.muted = !!options.secure;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { shim } = require('lib/shim.js');
|
const { shim } = require('lib/shim.js');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ class Command extends BaseCommand {
|
|||||||
let title = args['note'];
|
let title = args['note'];
|
||||||
|
|
||||||
let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
||||||
|
this.encryptionCheck(note);
|
||||||
if (!note) throw new Error(_('Cannot find "%s".', title));
|
if (!note) throw new Error(_('Cannot find "%s".', title));
|
||||||
|
|
||||||
const localFilePath = args['file'];
|
const localFilePath = args['file'];
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -21,10 +21,6 @@ class Command extends BaseCommand {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['note'];
|
let title = args['note'];
|
||||||
|
|
||||||
@@ -33,6 +29,9 @@ 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().gui().maximizeConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { _, setLocale } = require('lib/locale.js');
|
const { _, setLocale } = require('lib/locale.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');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -23,7 +23,11 @@ class Command extends BaseCommand {
|
|||||||
const verbose = args.options.verbose;
|
const verbose = args.options.verbose;
|
||||||
|
|
||||||
const renderKeyValue = (name) => {
|
const renderKeyValue = (name) => {
|
||||||
const value = Setting.value(name);
|
const md = Setting.settingMetadata(name);
|
||||||
|
let value = Setting.value(name);
|
||||||
|
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
|
||||||
|
if (md.secure) 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 {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 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 {
|
||||||
@@ -16,8 +16,9 @@ class Command extends BaseCommand {
|
|||||||
return _('Marks a to-do as done.');
|
return _('Marks a to-do as done.');
|
||||||
}
|
}
|
||||||
|
|
||||||
static async handleAction(args, isCompleted) {
|
static async handleAction(commandInstance, args, isCompleted) {
|
||||||
const note = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
|
const note = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
|
||||||
|
commandInstance.encryptionCheck(note);
|
||||||
if (!note) throw new Error(_('Cannot find "%s".', args.note));
|
if (!note) throw new Error(_('Cannot find "%s".', args.note));
|
||||||
if (!note.is_todo) throw new Error(_('Note is not a to-do: "%s"', args.note));
|
if (!note.is_todo) throw new Error(_('Note is not a to-do: "%s"', args.note));
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
await Command.handleAction(args, true);
|
await Command.handleAction(this, args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { 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 {
|
||||||
|
|
||||||
|
218
CliClient/app/command-e2ee.js
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
const EncryptionService = require('lib/services/EncryptionService');
|
||||||
|
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||||
|
const MasterKey = require('lib/models/MasterKey');
|
||||||
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
|
||||||
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
return 'e2ee <command> [path]';
|
||||||
|
}
|
||||||
|
|
||||||
|
description() {
|
||||||
|
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.');
|
||||||
|
}
|
||||||
|
|
||||||
|
options() {
|
||||||
|
return [
|
||||||
|
// 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).'],
|
||||||
|
['-v, --verbose', 'More verbose output for the `target-status` command'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async action(args) {
|
||||||
|
// change-password
|
||||||
|
|
||||||
|
const options = args.options;
|
||||||
|
|
||||||
|
if (args.command === 'enable') {
|
||||||
|
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
|
if (!password) {
|
||||||
|
this.stdout(_('Operation cancelled'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.command === 'disable') {
|
||||||
|
await EncryptionService.instance().disableEncryption();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.command === 'decrypt') {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if (args.path) {
|
||||||
|
const plainText = await EncryptionService.instance().decryptString(args.path);
|
||||||
|
this.stdout(plainText);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (process.stdin.isTTY) {
|
||||||
|
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||||
|
await DecryptionWorker.instance().start();
|
||||||
|
this.stdout(_('Completed decryption.'));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// var repl = require("repl");
|
||||||
|
// var r = repl.start("node> ");
|
||||||
|
|
||||||
|
const text = await new Promise((accept, reject) => {
|
||||||
|
var buffer = '';
|
||||||
|
process.stdin.setEncoding('utf8');
|
||||||
|
process.stdin.on('data', function(chunk) {
|
||||||
|
buffer += chunk;
|
||||||
|
// process.stdout.write(chunk);
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function() {
|
||||||
|
accept(buffer.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (text.length > 0) {
|
||||||
|
var cipherText = text;
|
||||||
|
try {
|
||||||
|
var item = await BaseItem.unserialize(text);
|
||||||
|
cipherText = item.encryption_cipher_text;
|
||||||
|
} catch (error) {
|
||||||
|
// we already got the pure cipher text
|
||||||
|
}
|
||||||
|
const plainText = await EncryptionService.instance().decryptString(cipherText);
|
||||||
|
this.stdout(plainText);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'masterKeyNotLoaded') {
|
||||||
|
const masterKeyId = error.masterKeyId;
|
||||||
|
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||||
|
if (!password) {
|
||||||
|
this.stdout(_('Operation cancelled'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||||
|
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.command === 'status') {
|
||||||
|
this.stdout(_('Encryption is: %s', Setting.value('encryption.enabled') ? _('Enabled') : _('Disabled')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.command === 'target-status') {
|
||||||
|
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;
|
||||||
|
if (!targetPath) throw new Error('Please specify the sync target path.');
|
||||||
|
|
||||||
|
const dirPaths = function(targetPath) {
|
||||||
|
let paths = [];
|
||||||
|
fs.readdirSync(targetPath).forEach((path) => {
|
||||||
|
paths.push(path);
|
||||||
|
});
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemCount = 0;
|
||||||
|
let resourceCount = 0;
|
||||||
|
let encryptedItemCount = 0;
|
||||||
|
let encryptedResourceCount = 0;
|
||||||
|
let otherItemCount = 0;
|
||||||
|
|
||||||
|
let encryptedPaths = [];
|
||||||
|
let decryptedPaths = [];
|
||||||
|
|
||||||
|
let paths = dirPaths(targetPath);
|
||||||
|
|
||||||
|
for (let i = 0; i < paths.length; i++) {
|
||||||
|
const path = paths[i];
|
||||||
|
const fullPath = targetPath + '/' + path;
|
||||||
|
const stat = await fs.stat(fullPath);
|
||||||
|
|
||||||
|
// this.stdout(fullPath);
|
||||||
|
|
||||||
|
if (path === '.resource') {
|
||||||
|
let resourcePaths = dirPaths(fullPath);
|
||||||
|
for (let j = 0; j < resourcePaths.length; j++) {
|
||||||
|
const resourcePath = resourcePaths[j];
|
||||||
|
resourceCount++;
|
||||||
|
const fullResourcePath = fullPath + '/' + resourcePath;
|
||||||
|
const isEncrypted = await EncryptionService.instance().fileIsEncrypted(fullResourcePath);
|
||||||
|
if (isEncrypted) {
|
||||||
|
encryptedResourceCount++;
|
||||||
|
encryptedPaths.push(fullResourcePath);
|
||||||
|
} else {
|
||||||
|
decryptedPaths.push(fullResourcePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (stat.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
const content = await fs.readFile(fullPath, 'utf8');
|
||||||
|
const item = await BaseItem.unserialize(content);
|
||||||
|
const ItemClass = BaseItem.itemClass(item);
|
||||||
|
|
||||||
|
if (!ItemClass.encryptionSupported()) {
|
||||||
|
otherItemCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemCount++;
|
||||||
|
|
||||||
|
const isEncrypted = await EncryptionService.instance().itemIsEncrypted(item);
|
||||||
|
|
||||||
|
if (isEncrypted) {
|
||||||
|
encryptedItemCount++;
|
||||||
|
encryptedPaths.push(fullPath);
|
||||||
|
} else {
|
||||||
|
decryptedPaths.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stdout('Encrypted items: ' + encryptedItemCount + '/' + itemCount);
|
||||||
|
this.stdout('Encrypted resources: ' + encryptedResourceCount + '/' + resourceCount);
|
||||||
|
this.stdout('Other items (never encrypted): ' + otherItemCount);
|
||||||
|
|
||||||
|
if (options.verbose) {
|
||||||
|
this.stdout('');
|
||||||
|
this.stdout('# Encrypted paths');
|
||||||
|
this.stdout('');
|
||||||
|
for (let i = 0; i < encryptedPaths.length; i++) {
|
||||||
|
const path = encryptedPaths[i];
|
||||||
|
this.stdout(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stdout('');
|
||||||
|
this.stdout('# Decrypted paths');
|
||||||
|
this.stdout('');
|
||||||
|
for (let i = 0; i < decryptedPaths.length; i++) {
|
||||||
|
const path = decryptedPaths[i];
|
||||||
|
this.stdout(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Command;
|
@@ -3,10 +3,10 @@ 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 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/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
@@ -44,6 +44,8 @@ class Command extends BaseCommand {
|
|||||||
if (!app().currentFolder()) throw new Error(_('No active notebook.'));
|
if (!app().currentFolder()) throw new Error(_('No active notebook.'));
|
||||||
let note = await app().loadItem(BaseModel.TYPE_NOTE, title);
|
let note = await app().loadItem(BaseModel.TYPE_NOTE, title);
|
||||||
|
|
||||||
|
this.encryptionCheck(note);
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
const ok = await this.prompt(_('Note does not exist: "%s". Create it?', title));
|
const ok = await this.prompt(_('Note does not exist: "%s". Create it?', title));
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
@@ -76,12 +78,14 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
|
app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
|
||||||
await app().gui().forceRender();
|
await app().gui().forceRender();
|
||||||
const termState = app().gui().term().saveState();
|
const termState = app().gui().termSaveState();
|
||||||
|
|
||||||
const spawnSync = require('child_process').spawnSync;
|
const spawnSync = require('child_process').spawnSync;
|
||||||
spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
|
||||||
|
|
||||||
app().gui().term().restoreState(termState);
|
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
|
||||||
|
|
||||||
|
app().gui().termRestoreState(termState);
|
||||||
app().gui().hideModalOverlay();
|
app().gui().hideModalOverlay();
|
||||||
app().gui().forceRender();
|
app().gui().forceRender();
|
||||||
|
|
||||||
|
@@ -12,6 +12,10 @@ class Command extends BaseCommand {
|
|||||||
return _('Exits the application.');
|
return _('Exits the application.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compatibleUis() {
|
||||||
|
return ['gui'];
|
||||||
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
await app().exit();
|
await app().exit();
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Database } = require('lib/database.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');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
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/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Note } = require('lib/models/note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { reg } = require('lib/registry.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');
|
||||||
@@ -10,15 +10,21 @@ 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() {
|
||||||
|
const service = new InteropService();
|
||||||
|
const formats = service.modules()
|
||||||
|
.filter(m => m.type === 'exporter')
|
||||||
|
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
['--format <format>', _('Destination format: %s', formats.join(', '))],
|
||||||
['--note <note>', _('Exports only the given note.')],
|
['--note <note>', _('Exports only the given note.')],
|
||||||
['--notebook <notebook>', _('Exports only the given notebook.')],
|
['--notebook <notebook>', _('Exports only the given notebook.')],
|
||||||
];
|
];
|
||||||
@@ -26,13 +32,9 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
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) {
|
||||||
|
|
||||||
@@ -48,10 +50,10 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ 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 { Database } = require('lib/database.js');
|
||||||
const { Setting } = require('lib/models/setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { wrap } = require('lib/string-utils.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');
|
||||||
@@ -18,7 +18,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allCommands() {
|
allCommands() {
|
||||||
const commands = app().commands();
|
const commands = app().commands(app().uiType());
|
||||||
let output = [];
|
let output = [];
|
||||||
for (let n in commands) {
|
for (let n in commands) {
|
||||||
if (!commands.hasOwnProperty(n)) continue;
|
if (!commands.hasOwnProperty(n)) continue;
|
||||||
@@ -36,21 +36,22 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
const stdoutWidth = app().commandStdoutMaxWidth();
|
const stdoutWidth = app().commandStdoutMaxWidth();
|
||||||
|
|
||||||
if (args.command === 'shortcuts') {
|
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
||||||
|
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplin.cozic.net/terminal/#shortcuts'));
|
||||||
|
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 shortcuts = app().gui().shortcuts();
|
const keymap = app().gui().keymap();
|
||||||
|
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let n in shortcuts) {
|
for (let i = 0; i < keymap.length; i++) {
|
||||||
if (!shortcuts.hasOwnProperty(n)) continue;
|
const item = keymap[i];
|
||||||
const shortcut = shortcuts[n];
|
const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k);
|
||||||
if (!shortcut.description) continue;
|
rows.push([keys.join(', '), item.command]);
|
||||||
n = shortcut.friendlyName ? shortcut.friendlyName : n;
|
|
||||||
rows.push([n, shortcut.description()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cliUtils.printArray(this.stdout.bind(this), rows);
|
cliUtils.printArray(this.stdout.bind(this), rows);
|
||||||
@@ -65,20 +66,20 @@ class Command extends BaseCommand {
|
|||||||
} 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.'));
|
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
|
||||||
this.stdout('');
|
this.stdout('');
|
||||||
this.stdout(_('The possible commands are:'));
|
this.stdout(_('The possible commands are:'));
|
||||||
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 complete list of available keyboard shortcuts, type `help shortcuts`'));
|
this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`'));
|
||||||
}
|
}
|
||||||
|
|
||||||
app().gui().showConsole();
|
app().gui().showConsole();
|
||||||
|
@@ -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;
|
|
75
CliClient/app/command-import.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const { BaseCommand } = require('./base-command.js');
|
||||||
|
const InteropService = require('lib/services/InteropService.js');
|
||||||
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
const Note = require('lib/models/Note.js');
|
||||||
|
const { filename, basename, fileExtension } = require('lib/path-utils.js');
|
||||||
|
const { importEnex } = require('lib/import-enex');
|
||||||
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
const { reg } = require('lib/registry.js');
|
||||||
|
const { app } = require('./app.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
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;
|
@@ -1,10 +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 { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Folder } = require('lib/models/folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const { Setting } = require('lib/models/setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { Note } = require('lib/models/note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
@@ -22,7 +22,7 @@ class Command extends BaseCommand {
|
|||||||
enabled() {
|
enabled() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
options() {
|
||||||
return [
|
return [
|
||||||
['-n, --limit <num>', _('Displays only the first top <num> notes.')],
|
['-n, --limit <num>', _('Displays only the first top <num> notes.')],
|
||||||
@@ -93,7 +93,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;
|
||||||
@@ -123,4 +123,4 @@ class Command extends BaseCommand {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = Command;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
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 { Folder } = require('lib/models/folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
@@ -14,10 +14,6 @@ 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);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
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 { Note } = require('lib/models/note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
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 { Note } = require('lib/models/note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ class Command extends BaseCommand {
|
|||||||
const name = args['name'];
|
const name = args['name'];
|
||||||
|
|
||||||
const item = await app().loadItem('folderOrNote', pattern);
|
const item = await app().loadItem('folderOrNote', pattern);
|
||||||
|
this.encryptionCheck(item);
|
||||||
if (!item) throw new Error(_('Cannot find "%s".', pattern));
|
if (!item) throw new Error(_('Cannot find "%s".', pattern));
|
||||||
|
|
||||||
const newItem = {
|
const newItem = {
|
||||||
|
@@ -1,10 +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/base-item.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 Note = require('lib/models/Note.js');
|
||||||
const { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
@@ -29,7 +29,7 @@ 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 "%s"?', folder.title), { 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);
|
||||||
|
@@ -1,10 +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/base-item.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 Note = require('lib/models/Note.js');
|
||||||
const { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const { uuid } = require('lib/uuid.js');
|
const { uuid } = require('lib/uuid.js');
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Folder } = require('lib/models/folder.js');
|
const { Database } = require('lib/database.js');
|
||||||
const { Note } = require('lib/models/note.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const { BaseItem } = require('lib/models/base-item.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -12,16 +13,16 @@ class Command extends BaseCommand {
|
|||||||
return 'set <note> <name> [value]';
|
return 'set <note> <name> [value]';
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return _('Sets the property <name> of the given <note> to the given [value].');
|
const fields = Note.fields();
|
||||||
}
|
const s = [];
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const f = fields[i];
|
||||||
|
if (f.name === 'id') continue;
|
||||||
|
s.push(f.name + ' (' + Database.enumName('fieldType', f.type) + ')');
|
||||||
|
}
|
||||||
|
|
||||||
hidden() {
|
return _('Sets the property <name> of the given <note> to the given [value]. Possible properties are:\n\n%s', s.join(', '));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
@@ -34,6 +35,8 @@ class Command extends BaseCommand {
|
|||||||
if (!notes.length) throw new Error(_('Cannot find "%s".', title));
|
if (!notes.length) throw new Error(_('Cannot find "%s".', title));
|
||||||
|
|
||||||
for (let i = 0; i < notes.length; i++) {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
|
this.encryptionCheck(notes[i]);
|
||||||
|
|
||||||
let newNote = {
|
let newNote = {
|
||||||
id: notes[i].id,
|
id: notes[i].id,
|
||||||
type_: notes[i].type_,
|
type_: notes[i].type_,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { Database } = require('lib/database.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');
|
||||||
|
|
||||||
|
@@ -2,15 +2,15 @@ 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 { 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/base-item.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
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');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@ 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)')],
|
||||||
['--random-failures', 'For debugging purposes. Do not use.'],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +61,44 @@ class Command extends BaseCommand {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAuth(syncTargetId) {
|
async doAuth() {
|
||||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||||
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
||||||
const auth = await this.oneDriveApiUtils_.oauthDance({
|
|
||||||
log: (...s) => { return this.stdout(...s); }
|
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive
|
||||||
});
|
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
|
||||||
this.oneDriveApiUtils_ = null;
|
const auth = await this.oneDriveApiUtils_.oauthDance({
|
||||||
return auth;
|
log: (...s) => { return this.stdout(...s); }
|
||||||
|
});
|
||||||
|
this.oneDriveApiUtils_ = null;
|
||||||
|
|
||||||
|
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
||||||
|
if (!auth) {
|
||||||
|
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTargetMd.label));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelAuth() {
|
cancelAuth() {
|
||||||
@@ -87,7 +116,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(Setting.value('profileDir'));
|
// TODO: use SQLite database to do lock?
|
||||||
|
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 {
|
||||||
@@ -117,16 +147,12 @@ 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().gui().showConsole();
|
||||||
app().gui().maximizeConsole();
|
app().gui().maximizeConsole();
|
||||||
|
|
||||||
const auth = await this.doAuth(this.syncTargetId_);
|
const authDone = await this.doAuth();
|
||||||
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
|
if (!authDone) return cleanUp();
|
||||||
if (!auth) {
|
|
||||||
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
|
|
||||||
return cleanUp();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sync = await syncTarget.synchronizer();
|
const sync = await syncTarget.synchronizer();
|
||||||
@@ -140,7 +166,6 @@ class Command extends BaseCommand {
|
|||||||
cliUtils.redrawDone();
|
cliUtils.redrawDone();
|
||||||
this.stdout(msg);
|
this.stdout(msg);
|
||||||
},
|
},
|
||||||
randomFailures: args.options['random-failures'] === true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTargetId_), this.syncTargetId_));
|
this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTargetId_), this.syncTargetId_));
|
||||||
@@ -189,7 +214,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 {
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
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 { Tag } = require('lib/models/tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -11,11 +12,19 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,7 +50,28 @@ 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); });
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 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 {
|
||||||
@@ -25,6 +25,8 @@ class Command extends BaseCommand {
|
|||||||
for (let i = 0; i < notes.length; i++) {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
const note = notes[i];
|
const note = notes[i];
|
||||||
|
|
||||||
|
this.encryptionCheck(note);
|
||||||
|
|
||||||
let toSave = {
|
let toSave = {
|
||||||
id: note.id,
|
id: note.id,
|
||||||
};
|
};
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.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 Note = require('lib/models/Note.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
|
|
||||||
const CommandDone = require('./command-done.js');
|
const CommandDone = require('./command-done.js');
|
||||||
@@ -19,7 +19,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
await CommandDone.handleAction(args, false);
|
await CommandDone.handleAction(this, args, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
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 { BaseModel } = require('lib/base-model.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const { Folder } = require('lib/models/folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
@@ -18,8 +18,8 @@ class Command extends BaseCommand {
|
|||||||
return { data: autocompleteFolders };
|
return { data: autocompleteFolders };
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
compatibleUis() {
|
||||||
return false;
|
return ['cli'];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.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');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const { Logger } = require('lib/logger.js');
|
const { Logger } = require('lib/logger.js');
|
||||||
const { Resource } = require('lib/models/resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const { dirname } = require('lib/path-utils.js');
|
const { dirname } = require('lib/path-utils.js');
|
||||||
const { FsDriverNode } = require('./fs-driver-node.js');
|
const { FsDriverNode } = require('./fs-driver-node.js');
|
||||||
const lodash = require('lodash');
|
const lodash = require('lodash');
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const Folder = require('lib/models/folder.js').Folder;
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Tag = require('lib/models/tag.js').Tag;
|
const Tag = require('lib/models/Tag.js');
|
||||||
const BaseModel = require('lib/base-model.js').BaseModel;
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const ListWidget = require('tkwidgets/ListWidget.js');
|
const ListWidget = require('tkwidgets/ListWidget.js');
|
||||||
const _ = require('lib/locale.js')._;
|
const _ = require('lib/locale.js')._;
|
||||||
|
|
||||||
@@ -18,19 +18,20 @@ 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(item.title);
|
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('[' + item.title + ']');
|
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));
|
// if (item && item.id) output.push(item.id.substr(0, 5));
|
||||||
|
|
||||||
@@ -38,6 +39,17 @@ class FolderListWidget extends ListWidget {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
throw new Error('unreachable');
|
||||||
|
}
|
||||||
|
|
||||||
get selectedFolderId() {
|
get selectedFolderId() {
|
||||||
return this.selectedFolderId_;
|
return this.selectedFolderId_;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const Note = require('lib/models/note.js').Note;
|
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 {
|
||||||
@@ -10,7 +10,7 @@ class NoteListWidget extends ListWidget {
|
|||||||
this.updateIndexFromSelectedNoteId_ = false;
|
this.updateIndexFromSelectedNoteId_ = false;
|
||||||
|
|
||||||
this.itemRenderer = (note) => {
|
this.itemRenderer = (note) => {
|
||||||
let label = note.title; // + ' ' + 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;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const Note = require('lib/models/note.js').Note;
|
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 {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
const Note = require('lib/models/note.js').Note;
|
const Note = require('lib/models/Note.js');
|
||||||
const TextWidget = require('tkwidgets/TextWidget.js');
|
const TextWidget = require('tkwidgets/TextWidget.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
class NoteWidget extends TextWidget {
|
class NoteWidget extends TextWidget {
|
||||||
|
|
||||||
@@ -32,11 +33,24 @@ class NoteWidget extends TextWidget {
|
|||||||
this.reloadNote();
|
this.reloadNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
welcomeText() {
|
||||||
|
return _('Welcome to Joplin!\n\nType `:help shortcuts` for the list of keyboard shortcuts, or just `:help` for usage information.\n\nFor example, to create a notebook press `mb`; to create a note press `mn`.');
|
||||||
|
}
|
||||||
|
|
||||||
reloadNote() {
|
reloadNote() {
|
||||||
if (this.noteId_) {
|
if (!this.noteId_ && !this.notes.length) {
|
||||||
|
this.text = this.welcomeText();
|
||||||
|
this.scrollTop = 0;
|
||||||
|
} 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_);
|
||||||
this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : '';
|
|
||||||
|
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.');
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
this.lastLoadedNoteId_ = this.noteId_;
|
this.lastLoadedNoteId_ = this.noteId_;
|
||||||
});
|
});
|
||||||
|
@@ -2,6 +2,7 @@ const BaseWidget = require('tkwidgets/BaseWidget.js');
|
|||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const termutils = require('tkwidgets/framework/termutils.js');
|
const termutils = require('tkwidgets/framework/termutils.js');
|
||||||
const stripAnsi = require('strip-ansi');
|
const stripAnsi = require('strip-ansi');
|
||||||
|
const { handleAutocompletion } = require('../autocompletion.js');
|
||||||
|
|
||||||
class StatusBarWidget extends BaseWidget {
|
class StatusBarWidget extends BaseWidget {
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if ('cursorPosition' in options) this.promptState_.cursorPosition = options.cursorPosition;
|
if ('cursorPosition' in options) this.promptState_.cursorPosition = options.cursorPosition;
|
||||||
|
if ('secure' in options) this.promptState_.secure = options.secure;
|
||||||
|
|
||||||
this.promptState_.promise = new Promise((resolve, reject) => {
|
this.promptState_.promise = new Promise((resolve, reject) => {
|
||||||
this.promptState_.resolve = resolve;
|
this.promptState_.resolve = resolve;
|
||||||
@@ -104,13 +106,19 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
|
|
||||||
this.term.showCursor(true);
|
this.term.showCursor(true);
|
||||||
|
|
||||||
|
const isSecurePrompt = !!this.promptState_.secure;
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
history: this.history,
|
history: this.history,
|
||||||
default: this.promptState_.initialText,
|
default: this.promptState_.initialText,
|
||||||
|
autoComplete: handleAutocompletion,
|
||||||
|
autoCompleteHint : true,
|
||||||
|
autoCompleteMenu : true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
|
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
|
||||||
|
if (isSecurePrompt) options.echoChar = true;
|
||||||
|
|
||||||
this.inputEventEmitter_ = this.term.inputField(options, (error, input) => {
|
this.inputEventEmitter_ = this.term.inputField(options, (error, input) => {
|
||||||
let resolveResult = null;
|
let resolveResult = null;
|
||||||
@@ -125,7 +133,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 (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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,4 +168,4 @@ class StatusBarWidget extends BaseWidget {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = StatusBarWidget;
|
module.exports = StatusBarWidget;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const fs = require('fs-extra');
|
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 { fileExtension, basename, dirname } = require('lib/path-utils.js');
|
||||||
const { _, setLocale, languageCode } = require('lib/locale.js');
|
const { _, setLocale, languageCode } = require('lib/locale.js');
|
||||||
|
|
||||||
@@ -53,9 +53,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)')));
|
||||||
|
@@ -1,26 +1,36 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
// Loading time: 20170803: 1.5s with no commands
|
// Make it possible to require("/lib/...") without specifying full path
|
||||||
|
|
||||||
require('app-module-path').addPath(__dirname);
|
require('app-module-path').addPath(__dirname);
|
||||||
|
|
||||||
|
const compareVersion = require('compare-version');
|
||||||
|
const nodeVersion = process && process.versions && process.versions.node ? process.versions.node : '0.0.0';
|
||||||
|
if (compareVersion(nodeVersion, '8.0.0') < 0) {
|
||||||
|
console.error('Joplin requires Node 8+. Detected version ' + nodeVersion);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { BaseModel } = require('lib/base-model.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const { Folder } = require('lib/models/folder.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const { Resource } = require('lib/models/resource.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const { BaseItem } = require('lib/models/base-item.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 NoteTag = require('lib/models/NoteTag.js');
|
||||||
const { NoteTag } = require('lib/models/note-tag.js');
|
const MasterKey = require('lib/models/MasterKey');
|
||||||
const { Setting } = require('lib/models/setting.js');
|
const Setting = require('lib/models/Setting.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');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
|
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
|
||||||
|
const EncryptionService = require('lib/services/EncryptionService');
|
||||||
|
|
||||||
const fsDriver = new FsDriverNode();
|
const fsDriver = new FsDriverNode();
|
||||||
Logger.fsDriver_ = fsDriver;
|
Logger.fsDriver_ = fsDriver;
|
||||||
Resource.fsDriver_ = fsDriver;
|
Resource.fsDriver_ = fsDriver;
|
||||||
|
EncryptionService.fsDriver_ = fsDriver;
|
||||||
|
FileApiDriverLocal.fsDriver_ = fsDriver;
|
||||||
|
|
||||||
// That's not good, but it's to avoid circular dependency issues
|
// That's not good, but it's to avoid circular dependency issues
|
||||||
// in the BaseItem class.
|
// in the BaseItem class.
|
||||||
@@ -29,6 +39,7 @@ BaseItem.loadClass('Folder', Folder);
|
|||||||
BaseItem.loadClass('Resource', Resource);
|
BaseItem.loadClass('Resource', Resource);
|
||||||
BaseItem.loadClass('Tag', Tag);
|
BaseItem.loadClass('Tag', Tag);
|
||||||
BaseItem.loadClass('NoteTag', NoteTag);
|
BaseItem.loadClass('NoteTag', NoteTag);
|
||||||
|
BaseItem.loadClass('MasterKey', MasterKey);
|
||||||
|
|
||||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||||
Setting.setConstant('appType', 'cli');
|
Setting.setConstant('appType', 'cli');
|
||||||
@@ -56,6 +67,13 @@ process.stdout.on('error', function( err ) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
application.start(process.argv).catch((error) => {
|
application.start(process.argv).catch((error) => {
|
||||||
console.error(_('Fatal error:'));
|
if (error.code == 'flagError') {
|
||||||
console.error(error);
|
console.error(error.message);
|
||||||
|
console.error(_('Type `joplin help` for usage information.'));
|
||||||
|
} else {
|
||||||
|
console.error(_('Fatal error:'));
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
@@ -1,3 +0,0 @@
|
|||||||
#/bin/bash
|
|
||||||
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
NODE_PATH="$CLIENT_DIR/build" node "$CLIENT_DIR/build/build-translation.js" --silent
|
|
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
"$ROOT_DIR/build.sh" && NODE_PATH="$ROOT_DIR/build" node "$ROOT_DIR/build/build-website.js"
|
|
@@ -4,57 +4,6 @@ ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||||||
BUILD_DIR="$ROOT_DIR/build"
|
BUILD_DIR="$ROOT_DIR/build"
|
||||||
|
|
||||||
rsync -a --exclude "node_modules/" "$ROOT_DIR/app/" "$BUILD_DIR/"
|
rsync -a --exclude "node_modules/" "$ROOT_DIR/app/" "$BUILD_DIR/"
|
||||||
rsync -a "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
|
rsync -a --delete "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
|
||||||
cp "$ROOT_DIR/package.json" "$BUILD_DIR"
|
cp "$ROOT_DIR/package.json" "$BUILD_DIR"
|
||||||
chmod 755 "$BUILD_DIR/main.js"
|
chmod 755 "$BUILD_DIR/main.js"
|
||||||
|
|
||||||
cd "$BUILD_DIR"
|
|
||||||
node build-translation.js --silent
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
|
|
||||||
# # require('cache-require-paths');
|
|
||||||
|
|
||||||
# mkdir -p "$ROOT_DIR/build"
|
|
||||||
# rm -f "$ROOT_DIR/app/lib"
|
|
||||||
# ln -s "$ROOT_DIR/../ReactNativeClient/lib" "$ROOT_DIR/app"
|
|
||||||
|
|
||||||
# npm run build || exit 1
|
|
||||||
|
|
||||||
# # Files under app/gui are in ES6 already but I cannot get Babel
|
|
||||||
# # to ignore them, so copy them back to the build directory.
|
|
||||||
# rsync -a "$ROOT_DIR/app/gui/" "$ROOT_DIR/build/gui/"
|
|
||||||
|
|
||||||
# cp "$ROOT_DIR/package.json" "$ROOT_DIR/build"
|
|
||||||
|
|
||||||
# chmod 755 "$ROOT_DIR/build/main.js"
|
|
||||||
|
|
||||||
# # if [[ ! -f "$ROOT_DIR/package.json.md5" ]]; then
|
|
||||||
# # "$ROOT_DIR/update-package-md5.sh"
|
|
||||||
# # fi
|
|
||||||
|
|
||||||
# # Add modules on top of main.js:
|
|
||||||
# # - cache-require-paths to cache require() calls
|
|
||||||
# # - app-module-path so that lib/something paths can be resolved.
|
|
||||||
|
|
||||||
# #PACKAGE_MD5=$(cat "$ROOT_DIR/package.json.md5")
|
|
||||||
# MAIN_PATH="$ROOT_DIR/build/main.js"
|
|
||||||
# #LINE_TO_ADD="var osTmpdir = require('os-tmpdir'); process.env.CACHE_REQUIRE_PATHS_FILE = osTmpdir() + '/joplin-module-path-cache-$PACKAGE_MD5'; require('cache-require-paths'); require('app-module-path').addPath(__dirname);"
|
|
||||||
# LINE_TO_ADD="require('app-module-path').addPath(__dirname);"
|
|
||||||
# RESULT="$(grep "$LINE_TO_ADD" "$MAIN_PATH")"
|
|
||||||
# if [[ -z "$RESULT" ]]; then
|
|
||||||
# echo "Adding extra modules..."
|
|
||||||
# sed -i "2i $LINE_TO_ADD" "$MAIN_PATH"
|
|
||||||
# else
|
|
||||||
# echo "Extra modules already added."
|
|
||||||
# fi
|
|
||||||
|
|
||||||
# NODE_PATH="$ROOT_DIR/build" node "$ROOT_DIR/build/build-translation.js" --silent
|
|
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
|
|
||||||
"$CLIENT_DIR/publish.sh"
|
|
||||||
npm install -g joplin
|
|
1677
CliClient/locales/ca.po
Normal file
1648
CliClient/locales/cs_CZ.po
Normal file
1664
CliClient/locales/da_DK.po
Normal file
1808
CliClient/locales/de_DE.po
Normal file
@@ -1,989 +0,0 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR Laurent Cozic
|
|
||||||
# This file is distributed under the same license as the Joplin-CLI package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: Joplin-CLI 1.0.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"Last-Translator: \n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"Language: es_CR\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Generator: Poedit 2.0.4\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
|
|
||||||
msgid "Give focus to next pane"
|
|
||||||
msgstr "Enfocar panel siguiente"
|
|
||||||
|
|
||||||
msgid "Give focus to previous pane"
|
|
||||||
msgstr "Enfocar panel anterior"
|
|
||||||
|
|
||||||
msgid "Enter command line mode"
|
|
||||||
msgstr "Entrar en modo de línea de comandos"
|
|
||||||
|
|
||||||
msgid "Exit command line mode"
|
|
||||||
msgstr "Salir del modo de línea de comandos"
|
|
||||||
|
|
||||||
msgid "Edit the selected note"
|
|
||||||
msgstr "Editar la nota siguiente"
|
|
||||||
|
|
||||||
msgid "Cancel the current command."
|
|
||||||
msgstr "Cancelar el comando actual."
|
|
||||||
|
|
||||||
msgid "Exit the application."
|
|
||||||
msgstr "Salir de la aplicación."
|
|
||||||
|
|
||||||
msgid "Delete the currently selected note or notebook."
|
|
||||||
msgstr "Eliminar la nota o el cuaderno activo."
|
|
||||||
|
|
||||||
msgid "To delete a tag, untag the associated notes."
|
|
||||||
msgstr "Para eliminar una etiqueta, desetiquete las notas asociadas."
|
|
||||||
|
|
||||||
msgid "Please select the note or notebook to be deleted first."
|
|
||||||
msgstr "Por favor seleccione la nota o el cuaderno a eliminar primero."
|
|
||||||
|
|
||||||
msgid "Set a to-do as completed / not completed"
|
|
||||||
msgstr "Cambiar un quehacer a completado / no completado"
|
|
||||||
|
|
||||||
msgid "[t]oggle [c]onsole between maximized/minimized/hidden/visible."
|
|
||||||
msgstr "Al[t]ernar [c]onsla entre maximizado/minimizado/oculto/visible."
|
|
||||||
|
|
||||||
msgid "Search"
|
|
||||||
msgstr "Buscar"
|
|
||||||
|
|
||||||
msgid "[t]oggle note [m]etadata."
|
|
||||||
msgstr "Al[t]ernar [m]etadatos de la nota."
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "[M]ake a new [n]ote"
|
|
||||||
msgstr "Crear [M] una nueva [n]ota"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "[M]ake a new [t]odo"
|
|
||||||
msgstr "Crear [M] un nuevo quehacer [T]"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "[M]ake a new note[b]ook"
|
|
||||||
msgstr "Crear[M] un nuevo cuaderno [B]"
|
|
||||||
|
|
||||||
msgid "Copy ([Y]ank) the [n]ote to a notebook."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Move the note to a notebook."
|
|
||||||
msgstr "Mover nota a un cuaderno."
|
|
||||||
|
|
||||||
msgid "Press Ctrl+D or type \"exit\" to exit the application"
|
|
||||||
msgstr "Presione Ctrl+D o escriba \"exit\" para salir de la aplicación"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "More than one item match \"%s\". Please narrow down your query."
|
|
||||||
msgstr ""
|
|
||||||
"Más de un elemento coinciden con \"%s\". Por favor haga una búsqueda más "
|
|
||||||
"específica."
|
|
||||||
|
|
||||||
msgid "No notebook selected."
|
|
||||||
msgstr "No hay ningún cuaderno seleccionado."
|
|
||||||
|
|
||||||
msgid "No notebook has been specified."
|
|
||||||
msgstr "Ningún cuaderno ha sido especificado."
|
|
||||||
|
|
||||||
msgid "Y"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "N"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "y"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Cancelling background synchronisation... Please wait."
|
|
||||||
msgstr "Cancelando sincronización en segundo plano... Por favor espere."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "The command \"%s\" is only available in GUI mode"
|
|
||||||
msgstr "El comando \"%s\" solo está disponible en el modo gráfico (GUI)"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Missing required argument: %s"
|
|
||||||
msgstr "Falta argumento necesario: %s"
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "%s: %s"
|
|
||||||
msgstr "%s: %s"
|
|
||||||
|
|
||||||
msgid "Your choice: "
|
|
||||||
msgstr "Su elección: "
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Invalid answer: %s"
|
|
||||||
msgstr "Respuesta inválida: %s"
|
|
||||||
|
|
||||||
msgid "Attaches the given file to the note."
|
|
||||||
msgstr "Adjunta el archivo a la nota."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Cannot find \"%s\"."
|
|
||||||
msgstr "No se puede encontrar \"%s\"."
|
|
||||||
|
|
||||||
msgid "Displays the given note."
|
|
||||||
msgstr "Muestra la nota."
|
|
||||||
|
|
||||||
msgid "Displays the complete information about note."
|
|
||||||
msgstr "Muestra la información completa acerca de la nota."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"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."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Also displays unset and hidden config variables."
|
|
||||||
msgstr "También muestra variables configuradas no establecidas y ocultas."
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "%s = %s (%s)"
|
|
||||||
msgstr "%s = %s (%s)"
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "%s = %s"
|
|
||||||
msgstr "%s = %s"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Duplicates the notes matching <note> to [notebook]. If no notebook is "
|
|
||||||
"specified the note is duplicated in the current notebook."
|
|
||||||
msgstr ""
|
|
||||||
"Duplica las notas coincidentes con <note> a [noteboom]. Si ningún cuaderno "
|
|
||||||
"es especificado la nota es duplicada en el uaderno actual."
|
|
||||||
|
|
||||||
msgid "Marks a to-do as done."
|
|
||||||
msgstr "Marca el quehacer como hecho."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Note is not a to-do: \"%s\""
|
|
||||||
msgstr "La nota no es un quehacer: \"%s\""
|
|
||||||
|
|
||||||
msgid "Edit note."
|
|
||||||
msgstr "Editar nota."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"No text editor is defined. Please set it using `config editor <editor-path>`"
|
|
||||||
msgstr ""
|
|
||||||
"No hay texto definido. Por favor establézcalo con `config editor <editor-"
|
|
||||||
"path>`"
|
|
||||||
|
|
||||||
msgid "No active notebook."
|
|
||||||
msgstr "No hay cuaderno activo."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Note does not exist: \"%s\". Create it?"
|
|
||||||
msgstr "La nota no existe: \"%s\". ¿Crearlo?"
|
|
||||||
|
|
||||||
msgid "Starting to edit note. Close the editor to get back to the prompt."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Note has been saved."
|
|
||||||
msgstr "La nota ha sido guardada."
|
|
||||||
|
|
||||||
msgid "Exits the application."
|
|
||||||
msgstr "Cierra la aplicación."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Exports Joplin data to the given directory. By default, it will export the "
|
|
||||||
"complete database including notebooks, notes, tags and resources."
|
|
||||||
msgstr ""
|
|
||||||
"Exporta la información de Joplin al directorio. Por defecto, exportará la "
|
|
||||||
"base de datos completa incluyendo cuadernos, notas, etiquetas y recursos."
|
|
||||||
|
|
||||||
msgid "Exports only the given note."
|
|
||||||
msgstr "Exporta solo la nota especificado."
|
|
||||||
|
|
||||||
msgid "Exports only the given notebook."
|
|
||||||
msgstr "Exporta solo el cuaderno especificado."
|
|
||||||
|
|
||||||
msgid "Displays a geolocation URL for the note."
|
|
||||||
msgstr "Muestra la URL de geolocalización de la nota."
|
|
||||||
|
|
||||||
msgid "Displays usage information."
|
|
||||||
msgstr "Muestra información de uso."
|
|
||||||
|
|
||||||
msgid "Shortcuts are not available in CLI mode."
|
|
||||||
msgstr "Los atajos no están disponibles en mod de línea de comandos (CLI)."
|
|
||||||
|
|
||||||
msgid "Type `help [command]` for more information about a command."
|
|
||||||
msgstr "Escriba `help [comando]` para más información acerca del comando."
|
|
||||||
|
|
||||||
msgid "The possible commands are:"
|
|
||||||
msgstr "Los comandos posibles son:"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"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."
|
|
||||||
msgstr ""
|
|
||||||
"En cualquier comando, una nota o cuaderno puede ser referido por su título o "
|
|
||||||
"ID, o usando los atajos `$n` o `$b` para la nota o cuaderno seleccionado, "
|
|
||||||
"respectivamente. `$c` puede ser utilizado para referirse al elemento "
|
|
||||||
"seleccionado."
|
|
||||||
|
|
||||||
msgid "To move from one pane to another, press Tab or Shift+Tab."
|
|
||||||
msgstr "Para moverse de un panel a otro, presione Tab o Shift+Tab."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Use the arrows and page up/down to scroll the lists and text areas "
|
|
||||||
"(including this console)."
|
|
||||||
msgstr ""
|
|
||||||
"Use las flechas y RePág/AvPág para deslizar las listas y áreas de texto "
|
|
||||||
"(incluyendo esta consola)."
|
|
||||||
|
|
||||||
msgid "To maximise/minimise the console, press \"TC\"."
|
|
||||||
msgstr "Para minimizar/mazimizar la consola, presione \"TC\"."
|
|
||||||
|
|
||||||
msgid "To enter command line mode, press \":\""
|
|
||||||
msgstr "Para entrar en modo de línea de comandos (CLI), presione \":\""
|
|
||||||
|
|
||||||
msgid "To exit command line mode, press ESCAPE"
|
|
||||||
msgstr "Para salir del modo de línea de comandos (CLI), presione Esc"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"For the complete list of available keyboard shortcuts, type `help shortcuts`"
|
|
||||||
msgstr ""
|
|
||||||
"Para la lista completa de atajos de teclado disponibles, escriba `help "
|
|
||||||
"shortcuts`"
|
|
||||||
|
|
||||||
msgid "Imports an Evernote notebook file (.enex file)."
|
|
||||||
msgstr "Importa un archivo de cuaderno de Evernote (.enex)."
|
|
||||||
|
|
||||||
msgid "Do not ask for confirmation."
|
|
||||||
msgstr "No preguntar por confirmación."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "File \"%s\" will be imported into existing notebook \"%s\". Continue?"
|
|
||||||
msgstr "El archivo \"%s\" será importado al cuaderno \"%s\". ¿Continuar?"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid ""
|
|
||||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into "
|
|
||||||
"it. Continue?"
|
|
||||||
msgstr ""
|
|
||||||
"El nuevo cuaderno \"%s\" será cread y el archivo \"%s\" será importado en "
|
|
||||||
"él. ¿Continuar?"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Found: %d."
|
|
||||||
msgstr "Encontrado: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Created: %d."
|
|
||||||
msgstr "Creado: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Updated: %d."
|
|
||||||
msgstr "Actualizado: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Skipped: %d."
|
|
||||||
msgstr "Saltado: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Resources: %d."
|
|
||||||
msgstr "Recursos: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Tagged: %d."
|
|
||||||
msgstr "Etiquetado: %d."
|
|
||||||
|
|
||||||
msgid "Importing notes..."
|
|
||||||
msgstr "Importando notas..."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "The notes have been imported: %s"
|
|
||||||
msgstr "Las notas han sido importadas: %s"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Displays the notes in the current notebook. Use `ls /` to display the list "
|
|
||||||
"of notebooks."
|
|
||||||
msgstr ""
|
|
||||||
"Muestra las notas en el cuaderno actual. Use `ls /` para mostrar la lista de "
|
|
||||||
"cuadernos."
|
|
||||||
|
|
||||||
msgid "Displays only the first top <num> notes."
|
|
||||||
msgstr "Muestra las primeras <num> notas."
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Sorts the item by <field> (eg. title, updated_time, created_time)."
|
|
||||||
msgstr ""
|
|
||||||
"Ordena el elemento por <field> (ej. title, updated_time, created_time)."
|
|
||||||
|
|
||||||
msgid "Reverses the sorting order."
|
|
||||||
msgstr "Invierte el orden."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"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."
|
|
||||||
msgstr ""
|
|
||||||
"Muestra solo los elementos del tipo específico. Puede ser `n` para notas, "
|
|
||||||
"`t` para quehaceres, o `nt` para notas y quehaceres (ej. `-tt` mostraría "
|
|
||||||
"solo los quehaceres, mientras que `-ttd` mostraría las notas y los "
|
|
||||||
"quehaceres."
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Either \"text\" or \"json\""
|
|
||||||
msgstr "Cualquiera entre \"texto\" o \"json\""
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, "
|
|
||||||
"TODO_CHECKED (for to-dos), TITLE"
|
|
||||||
msgstr ""
|
|
||||||
"Usar formato de lista largo. El formato es ID, NOTE_COUNT (para cuaderno), "
|
|
||||||
"DATE, TODO_CHECKED (para quehaceres), TITLE"
|
|
||||||
|
|
||||||
msgid "Please select a notebook first."
|
|
||||||
msgstr "Por favor seleccione un cuaderno primero."
|
|
||||||
|
|
||||||
msgid "Creates a new notebook."
|
|
||||||
msgstr "Crea un nuevo cuaderno."
|
|
||||||
|
|
||||||
msgid "Creates a new note."
|
|
||||||
msgstr "Crea una nueva nota."
|
|
||||||
|
|
||||||
msgid "Notes can only be created within a notebook."
|
|
||||||
msgstr "Las notas solo pueden ser creadas dentro de un cuaderno."
|
|
||||||
|
|
||||||
msgid "Creates a new to-do."
|
|
||||||
msgstr "Crea un nuevo quehacer."
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Moves the notes matching <note> to [notebook]."
|
|
||||||
msgstr "Mueve las notas coincidentes con <note> a [cuaderno]."
|
|
||||||
|
|
||||||
msgid "Renames the given <item> (note or notebook) to <name>."
|
|
||||||
msgstr "Renombra el <item> (nota o cuaderno) a <name>."
|
|
||||||
|
|
||||||
msgid "Deletes the given notebook."
|
|
||||||
msgstr "Elimina el cuaderno."
|
|
||||||
|
|
||||||
msgid "Deletes the notebook without asking for confirmation."
|
|
||||||
msgstr "Elimina el cuaderno sin preguntar por confirmación."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Delete notebook \"%s\"?"
|
|
||||||
msgstr "¿Eliminar el cuaderno \"%s\"?"
|
|
||||||
|
|
||||||
msgid "Deletes the notes matching <note-pattern>."
|
|
||||||
msgstr "Elimina las notas coincidentes con <note-pattern>."
|
|
||||||
|
|
||||||
msgid "Deletes the notes without asking for confirmation."
|
|
||||||
msgstr "Elimina las notas sin preguntar por confirmación."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "%d notes match this pattern. Delete them?"
|
|
||||||
msgstr "%d notas coinciden con este patrón. ¿Eliminarlas?"
|
|
||||||
|
|
||||||
msgid "Delete note?"
|
|
||||||
msgstr "¿Elimnar la nota?"
|
|
||||||
|
|
||||||
msgid "Searches for the given <pattern> in all the notes."
|
|
||||||
msgstr "Busca el <pattern> en todas las notas."
|
|
||||||
|
|
||||||
msgid "Sets the property <name> of the given <note> to the given [value]."
|
|
||||||
msgstr "Establece la propiedad <name> de la <note> al [value]."
|
|
||||||
|
|
||||||
msgid "Displays summary about the notes and notebooks."
|
|
||||||
msgstr "Muestra un resumen acerca de las notas y los cuadernos."
|
|
||||||
|
|
||||||
msgid "Synchronises with remote storage."
|
|
||||||
msgstr "Sincroniza con el almacenamiento remoto."
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Sync to provided target (defaults to sync.target config value)"
|
|
||||||
msgstr "Sincroniza con el objetivo dado"
|
|
||||||
|
|
||||||
msgid "Synchronisation is already in progress."
|
|
||||||
msgstr "La sincronización ya está en progreso."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid ""
|
|
||||||
"Lock file is already being hold. If you know that no synchronisation is "
|
|
||||||
"taking place, you may delete the lock file at \"%s\" and resume the "
|
|
||||||
"operation."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Authentication was not completed (did not receive an authentication token)."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Synchronisation target: %s (%s)"
|
|
||||||
msgstr "Objetivo de sincronización: %s (%s)"
|
|
||||||
|
|
||||||
msgid "Cannot initialize synchroniser."
|
|
||||||
msgstr "No se puede iniciar la sincronización."
|
|
||||||
|
|
||||||
msgid "Starting synchronisation..."
|
|
||||||
msgstr "Iniciando la sincronización..."
|
|
||||||
|
|
||||||
msgid "Cancelling... Please wait."
|
|
||||||
msgstr "Cancelando... por favor espere."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<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."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Invalid command: \"%s\""
|
|
||||||
msgstr "Comando inválido: \"%s\""
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"<todo-command> can either be \"toggle\" or \"clear\". Use \"toggle\" to "
|
|
||||||
"toggle the given to-do between completed and uncompleted state (If the "
|
|
||||||
"target is a regular note it will be converted to a to-do). Use \"clear\" to "
|
|
||||||
"convert the to-do back to a regular note."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Marks a to-do as non-completed."
|
|
||||||
msgstr "Marca los quehaceres como incompletos."
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
"Switches to [notebook] - all further operations will happen within this "
|
|
||||||
"notebook."
|
|
||||||
msgstr ""
|
|
||||||
"Cambia al [cuaderno] - todas las operaciones próximas sucerderán dentro de "
|
|
||||||
"este cuaderno."
|
|
||||||
|
|
||||||
msgid "Displays version information"
|
|
||||||
msgstr "Muestra la información de la versión"
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "%s %s (%s)"
|
|
||||||
msgstr "%s %s (%s)"
|
|
||||||
|
|
||||||
msgid "Enum"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Type: %s."
|
|
||||||
msgstr "Tipo: %s."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Possible values: %s."
|
|
||||||
msgstr "Valores posibles: %s."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Default: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Possible keys/values:"
|
|
||||||
msgstr "Valores posibles:"
|
|
||||||
|
|
||||||
msgid "Fatal error:"
|
|
||||||
msgstr "Error fatal:"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The application has been authorised - you may now close this browser tab."
|
|
||||||
msgstr "La aplicación ha sido autorizada. Ya puede cerrar esta pestaña."
|
|
||||||
|
|
||||||
msgid "The application has been successfully authorised."
|
|
||||||
msgstr "La aplicación fue autorizada exitosamente."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Please open the following URL in your browser to authenticate the "
|
|
||||||
"application. The application will create a directory in \"Apps/Joplin\" and "
|
|
||||||
"will only read and write files in this directory. It will have no access to "
|
|
||||||
"any files outside this directory nor to any other personal data. No data "
|
|
||||||
"will be shared with any third party."
|
|
||||||
msgstr ""
|
|
||||||
"Por favor abra el siguiente URL en su explorador para autenticar la "
|
|
||||||
"aplciación. La aplicación creará un directorio en \"Apps/Joplin\" y solo "
|
|
||||||
"leerá y escribirá los archivos en este directorio. No tendrá acceso a ningún "
|
|
||||||
"archivo fuera de este directorio ni a información personal. Ninguna "
|
|
||||||
"información será compartida con terceros."
|
|
||||||
|
|
||||||
msgid "Search:"
|
|
||||||
msgstr "Buscar:"
|
|
||||||
|
|
||||||
msgid "File"
|
|
||||||
msgstr "Archivo"
|
|
||||||
|
|
||||||
msgid "New note"
|
|
||||||
msgstr "Nueva nota"
|
|
||||||
|
|
||||||
msgid "New to-do"
|
|
||||||
msgstr "Nuevo quehacer"
|
|
||||||
|
|
||||||
msgid "New notebook"
|
|
||||||
msgstr "Nuevo cuaderno"
|
|
||||||
|
|
||||||
msgid "Import Evernote notes"
|
|
||||||
msgstr "Importar notas de Evernote"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Evernote Export Files"
|
|
||||||
msgstr "Archivos exportados desde Evernote"
|
|
||||||
|
|
||||||
msgid "Quit"
|
|
||||||
msgstr "Abortar"
|
|
||||||
|
|
||||||
msgid "Edit"
|
|
||||||
msgstr "Editar"
|
|
||||||
|
|
||||||
msgid "Copy"
|
|
||||||
msgstr "Copiar"
|
|
||||||
|
|
||||||
msgid "Cut"
|
|
||||||
msgstr "Cortar"
|
|
||||||
|
|
||||||
msgid "Paste"
|
|
||||||
msgstr "Pegar"
|
|
||||||
|
|
||||||
msgid "Search in all the notes"
|
|
||||||
msgstr "Buscar en todas la notas"
|
|
||||||
|
|
||||||
msgid "Tools"
|
|
||||||
msgstr "Herramientas"
|
|
||||||
|
|
||||||
msgid "Options"
|
|
||||||
msgstr "Opciones"
|
|
||||||
|
|
||||||
msgid "Help"
|
|
||||||
msgstr "Ayuda"
|
|
||||||
|
|
||||||
msgid "Website and documentation"
|
|
||||||
msgstr "Sitio web y documentación"
|
|
||||||
|
|
||||||
msgid "About Joplin"
|
|
||||||
msgstr "Acerca de Joplin"
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "%s %s (%s, %s)"
|
|
||||||
msgstr "%s %s (%s, %s)"
|
|
||||||
|
|
||||||
msgid "OK"
|
|
||||||
msgstr "Aceptar"
|
|
||||||
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr "Cancelar"
|
|
||||||
|
|
||||||
msgid "Back"
|
|
||||||
msgstr "Atrás"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid ""
|
|
||||||
"New notebook \"%s\" will be created and file \"%s\" will be imported into it"
|
|
||||||
msgstr ""
|
|
||||||
"El nuevo cuaderno \"%s\" será cread y el archivo \"%s\" será importado en él"
|
|
||||||
|
|
||||||
msgid "Please create a notebook first."
|
|
||||||
msgstr "Por favor cree un cuaderno primero."
|
|
||||||
|
|
||||||
msgid "Note title:"
|
|
||||||
msgstr "Título de la nota:"
|
|
||||||
|
|
||||||
msgid "Please create a notebook first"
|
|
||||||
msgstr "Por favor cree un cuaderno primero"
|
|
||||||
|
|
||||||
msgid "To-do title:"
|
|
||||||
msgstr "Nombre del quehacer:"
|
|
||||||
|
|
||||||
msgid "Notebook title:"
|
|
||||||
msgstr "Título del cuaderno:"
|
|
||||||
|
|
||||||
msgid "Add or remove tags:"
|
|
||||||
msgstr "Añadir o eliminar etiquetas:"
|
|
||||||
|
|
||||||
msgid "Separate each tag by a comma."
|
|
||||||
msgstr "Separar cada etiqueta con una coma."
|
|
||||||
|
|
||||||
msgid "Rename notebook:"
|
|
||||||
msgstr "Renombrar cuaderno:"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Set or clear alarm:"
|
|
||||||
msgstr "Establecer o eliminar alarma:"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Layout"
|
|
||||||
msgstr "Plantilla"
|
|
||||||
|
|
||||||
msgid "Add or remove tags"
|
|
||||||
msgstr "Añadir o eliminar etiquetas"
|
|
||||||
|
|
||||||
msgid "Switch between note and to-do type"
|
|
||||||
msgstr "Cambie entre nota y quehacer"
|
|
||||||
|
|
||||||
msgid "Delete"
|
|
||||||
msgstr "Eliminar"
|
|
||||||
|
|
||||||
msgid "Delete notes?"
|
|
||||||
msgstr "¿Eliminar notas?"
|
|
||||||
|
|
||||||
msgid "No notes in here. Create one by clicking on \"New note\"."
|
|
||||||
msgstr "No hay notas aquí. Cree una presionando \"Nota nueva\"."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Unsupported link or message: %s"
|
|
||||||
msgstr "Enlace o mensaje no soportado: %s"
|
|
||||||
|
|
||||||
msgid "Attach file"
|
|
||||||
msgstr "Adjuntar archivo"
|
|
||||||
|
|
||||||
msgid "Set alarm"
|
|
||||||
msgstr "Establecer alarma"
|
|
||||||
|
|
||||||
msgid "Refresh"
|
|
||||||
msgstr "Actualizar"
|
|
||||||
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Clear"
|
|
||||||
msgstr "Limpiar"
|
|
||||||
|
|
||||||
msgid "OneDrive Login"
|
|
||||||
msgstr "Inicio de sesión de OneDrive"
|
|
||||||
|
|
||||||
msgid "Import"
|
|
||||||
msgstr "Importar"
|
|
||||||
|
|
||||||
msgid "Delete notebook?"
|
|
||||||
msgstr "¿Eliminar cuaderno?"
|
|
||||||
|
|
||||||
msgid "Remove this tag from all the notes?"
|
|
||||||
msgstr "¿Eliminar esta etiqueta de todas las notas?"
|
|
||||||
|
|
||||||
msgid "Remove this search from the sidebar?"
|
|
||||||
msgstr "¿Eliminar esta búsqueda de la barra lateral?"
|
|
||||||
|
|
||||||
msgid "Rename"
|
|
||||||
msgstr "Renombrar"
|
|
||||||
|
|
||||||
msgid "Synchronise"
|
|
||||||
msgstr "Sincronizar"
|
|
||||||
|
|
||||||
msgid "Notebooks"
|
|
||||||
msgstr "Cuadernos"
|
|
||||||
|
|
||||||
msgid "Tags"
|
|
||||||
msgstr "Etiquetas"
|
|
||||||
|
|
||||||
msgid "Searches"
|
|
||||||
msgstr "Búsquedas"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Usage: %s"
|
|
||||||
msgstr "Uso: %s"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Unknown flag: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "File system"
|
|
||||||
msgstr "Sistema de archivos"
|
|
||||||
|
|
||||||
msgid "OneDrive"
|
|
||||||
msgstr "OneDrive"
|
|
||||||
|
|
||||||
msgid "OneDrive Dev (For testing only)"
|
|
||||||
msgstr "Desarrollo de OneDrive (solo para pruebas)"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Unknown log level: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Unknown level ID: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Cannot refresh token: authentication data is missing. Starting the "
|
|
||||||
"synchronisation again may fix the problem."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "Cannot access %s"
|
|
||||||
msgstr "No se puede acceder a %s"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Created local items: %d."
|
|
||||||
msgstr "Elementos locales creados: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Updated local items: %d."
|
|
||||||
msgstr "Elementos locales actualizados: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Created remote items: %d."
|
|
||||||
msgstr "Elementos remotos creados: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Updated remote items: %d."
|
|
||||||
msgstr "Elementos remotos actualizados: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Deleted local items: %d."
|
|
||||||
msgstr "Elementos locales eliminados: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Deleted remote items: %d."
|
|
||||||
msgstr "Elementos remotos eliminados: %d."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "State: \"%s\"."
|
|
||||||
msgstr "Estado: \"%s\"."
|
|
||||||
|
|
||||||
msgid "Cancelling..."
|
|
||||||
msgstr "Cancelando..."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Completed: %s"
|
|
||||||
msgstr "Completado: %s"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Synchronisation is already in progress. State: %s"
|
|
||||||
msgstr "La sincronización ya está en progreso. Estado: %s"
|
|
||||||
|
|
||||||
msgid "Conflicts"
|
|
||||||
msgstr "Conflictos"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "A notebook with this title already exists: \"%s\""
|
|
||||||
msgstr "Ya existe un cuaderno con este título: \"%s\""
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
|
|
||||||
msgstr ""
|
|
||||||
"Los cuadernos no pueden ser nombrados \"%s\", los cuales son títulos "
|
|
||||||
"reservados."
|
|
||||||
|
|
||||||
msgid "Untitled"
|
|
||||||
msgstr "Sin título"
|
|
||||||
|
|
||||||
msgid "This note does not have geolocation information."
|
|
||||||
msgstr "Esta nota no tiene datos de localización geográfica."
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "Cannot copy note to \"%s\" notebook"
|
|
||||||
msgstr "No se puede copiar la nota al cuaderno \"%s\""
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "Cannot move note to \"%s\" notebook"
|
|
||||||
msgstr "No se puede mover la nota al cuaderno \"%s\""
|
|
||||||
|
|
||||||
msgid "File system synchronisation target directory"
|
|
||||||
msgstr "Directorio objetivo del sistema de sincronización de archivos"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The path to synchronise with when file system synchronisation is enabled. "
|
|
||||||
"See `sync.target`."
|
|
||||||
msgstr ""
|
|
||||||
"La ruta para sincronizar cuando el sistema de sincronización de archivos de "
|
|
||||||
"es activado. Ver `sync.target`."
|
|
||||||
|
|
||||||
msgid "Text editor"
|
|
||||||
msgstr "Editor de texto"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The editor that will be used to open a note. If none is provided it will try "
|
|
||||||
"to auto-detect the default editor."
|
|
||||||
msgstr ""
|
|
||||||
"El editor que va a ser utilizado para abrir notas. Si no hay ninguno "
|
|
||||||
"seleccionado va a intentar detectar el editor por defecto."
|
|
||||||
|
|
||||||
msgid "Language"
|
|
||||||
msgstr "Idioma"
|
|
||||||
|
|
||||||
msgid "Date format"
|
|
||||||
msgstr "Formato de fecha"
|
|
||||||
|
|
||||||
msgid "Time format"
|
|
||||||
msgstr "Formato de hora"
|
|
||||||
|
|
||||||
msgid "Theme"
|
|
||||||
msgstr "Tema"
|
|
||||||
|
|
||||||
msgid "Light"
|
|
||||||
msgstr "Claro"
|
|
||||||
|
|
||||||
msgid "Dark"
|
|
||||||
msgstr "Oscuro"
|
|
||||||
|
|
||||||
msgid "Show uncompleted todos on top of the lists"
|
|
||||||
msgstr "Mostrar quehaceres incompletos arriba de las listas"
|
|
||||||
|
|
||||||
msgid "Save geo-location with notes"
|
|
||||||
msgstr "Guardar localización geográfica con notas"
|
|
||||||
|
|
||||||
msgid "Synchronisation interval"
|
|
||||||
msgstr "Intervalo de sincronización"
|
|
||||||
|
|
||||||
msgid "Disabled"
|
|
||||||
msgstr "Desactivado"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "%d minutes"
|
|
||||||
msgstr "%d minutos"
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "%d hour"
|
|
||||||
msgstr "%d hora"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "%d hours"
|
|
||||||
msgstr "%d horas"
|
|
||||||
|
|
||||||
msgid "Automatically update the application"
|
|
||||||
msgstr "Actualizar la aplicación automáticamente"
|
|
||||||
|
|
||||||
msgid "Show advanced options"
|
|
||||||
msgstr "Mostrar opciones avanzadas"
|
|
||||||
|
|
||||||
msgid "Synchronisation target"
|
|
||||||
msgstr "Objetivo de sincronización"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"The target to synchonise to. If synchronising with the file system, set "
|
|
||||||
"`sync.2.path` to specify the target directory."
|
|
||||||
msgstr ""
|
|
||||||
"El objetivo a sincronizar. Si está sincronizando con el sistema de archivos, "
|
|
||||||
"establezca `sync.2.path` para especificar el directorio objetivo."
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
|
||||||
msgstr "Valor inválido: \"%s\". Posibles valores: %s."
|
|
||||||
|
|
||||||
msgid "Sync status (synced items / total items)"
|
|
||||||
msgstr ""
|
|
||||||
"Estado de sincronización (elementos sincronizados / total de elementos)"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "%s: %d/%d"
|
|
||||||
msgstr "%s: %d/%d"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Total: %d/%d"
|
|
||||||
msgstr "Total: %d/%d"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Conflicted: %d"
|
|
||||||
msgstr "Conflicto: %d"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "To delete: %d"
|
|
||||||
msgstr "A eliminar: %d"
|
|
||||||
|
|
||||||
msgid "Folders"
|
|
||||||
msgstr "Carpetas"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "%s: %d notes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Coming alarms"
|
|
||||||
msgstr "Alarmas pendientes"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "On %s: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "There are currently no notes. Create one by clicking on the (+) button."
|
|
||||||
msgstr "Actualmente no hay notas. Cree una presionando el botón (+)."
|
|
||||||
|
|
||||||
msgid "Delete these notes?"
|
|
||||||
msgstr "¿Eliminar estas notas?"
|
|
||||||
|
|
||||||
msgid "Log"
|
|
||||||
msgstr "Historial"
|
|
||||||
|
|
||||||
msgid "Status"
|
|
||||||
msgstr "Estado"
|
|
||||||
|
|
||||||
msgid "Export Debug Report"
|
|
||||||
msgstr "Exportar reporte de fallos"
|
|
||||||
|
|
||||||
msgid "Configuration"
|
|
||||||
msgstr "Configuración"
|
|
||||||
|
|
||||||
msgid "Move to notebook..."
|
|
||||||
msgstr "Mover a cuaderno..."
|
|
||||||
|
|
||||||
#, fuzzy, javascript-format
|
|
||||||
msgid "Move %d notes to notebook \"%s\"?"
|
|
||||||
msgstr "¿Mover notas %d al cuaderno \"%s\"?"
|
|
||||||
|
|
||||||
msgid "Select date"
|
|
||||||
msgstr "Seleccionar fecha"
|
|
||||||
|
|
||||||
msgid "Confirm"
|
|
||||||
msgstr "Aceptar"
|
|
||||||
|
|
||||||
msgid "Cancel synchronisation"
|
|
||||||
msgstr "Cancelar sincronización"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "The notebook could not be saved: %s"
|
|
||||||
msgstr "El cuaderno no pudo ser guardado: %s"
|
|
||||||
|
|
||||||
msgid "Edit notebook"
|
|
||||||
msgstr "Editar cuaderno"
|
|
||||||
|
|
||||||
msgid "This note has been modified:"
|
|
||||||
msgstr "Esta nota ha sido modificada:"
|
|
||||||
|
|
||||||
msgid "Save changes"
|
|
||||||
msgstr "Guardar cambios"
|
|
||||||
|
|
||||||
msgid "Discard changes"
|
|
||||||
msgstr "Descartar cambios"
|
|
||||||
|
|
||||||
#, javascript-format
|
|
||||||
msgid "Unsupported image type: %s"
|
|
||||||
msgstr "Tipo de imagen no soportado: %s"
|
|
||||||
|
|
||||||
msgid "Attach photo"
|
|
||||||
msgstr "Adjuntar foto"
|
|
||||||
|
|
||||||
msgid "Attach any file"
|
|
||||||
msgstr "Adjuntar un archivo"
|
|
||||||
|
|
||||||
msgid "Convert to note"
|
|
||||||
msgstr "Convertir a nota"
|
|
||||||
|
|
||||||
msgid "Convert to todo"
|
|
||||||
msgstr "Convertir a quehacer"
|
|
||||||
|
|
||||||
msgid "Hide metadata"
|
|
||||||
msgstr "Ocultar metadatos"
|
|
||||||
|
|
||||||
msgid "Show metadata"
|
|
||||||
msgstr "Mostrar metadatos"
|
|
||||||
|
|
||||||
msgid "View on map"
|
|
||||||
msgstr "Ver en el mapa"
|
|
||||||
|
|
||||||
msgid "Delete notebook"
|
|
||||||
msgstr "Eliminar un cuaderno"
|
|
||||||
|
|
||||||
msgid "Login with OneDrive"
|
|
||||||
msgstr "Iniciar sesión con OneDrive"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
|
||||||
"menu to access your existing notebooks."
|
|
||||||
msgstr ""
|
|
||||||
"Presione el botón (+) para crear una nueva nota o un nuevo cuaderno. "
|
|
||||||
"Presione el menú lateral para acceder a uno de sus cuadernos."
|
|
||||||
|
|
||||||
msgid "You currently have no notebook. Create one by clicking on (+) button."
|
|
||||||
msgstr "Actualmente no tiene cuadernos. Cree uno presionando el botón (+)."
|
|
||||||
|
|
||||||
msgid "Welcome"
|
|
||||||
msgstr "Bienvenido"
|
|