You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Compare commits
1497 Commits
android-v0
...
cli-v1.0.1
Author | SHA1 | Date | |
---|---|---|---|
|
9de7c15e93 | ||
|
61736546b4 | ||
|
82b6dd23a7 | ||
|
64427f0160 | ||
|
cbf47cb9ee | ||
|
dba3e4202d | ||
|
173cd6de4d | ||
|
3d333bd8f2 | ||
|
a82f8c7dd0 | ||
|
cde079e44e | ||
|
f8d20b61ea | ||
|
d279435502 | ||
|
eb2065128e | ||
|
10e81aa476 | ||
|
3e808f05fd | ||
|
e57bfad9b1 | ||
|
5f344f07d4 | ||
|
e1b7b64e1b | ||
|
ed3970be81 | ||
|
c27861d40f | ||
|
565dfba8c9 | ||
|
553a26eb63 | ||
|
9c85bc2cd1 | ||
|
e96bc9c48a | ||
|
0d036d8183 | ||
|
e5f2a7f2f5 | ||
|
016ce3dd61 | ||
|
afb375955e | ||
|
b702b0b40c | ||
|
91ecab51c5 | ||
|
7628506926 | ||
|
4e7f7c0c9c | ||
|
863f5bcf18 | ||
|
dccd489fcc | ||
|
333c3f6369 | ||
|
808413d0bf | ||
|
440be3d920 | ||
|
9b27a4f601 | ||
|
6e36ca32b4 | ||
|
85a9c303f2 | ||
|
7e9972d99f | ||
|
771975cd35 | ||
|
356f8e580b | ||
|
68268cb35d | ||
|
8e58ed12af | ||
|
beb428b246 | ||
|
4d81caff0b | ||
|
78372c9bac | ||
|
a4db1bc671 | ||
|
8ea1c373ed | ||
|
8ef27dfcdc | ||
|
23e43c7bc1 | ||
|
edb8f4c79f | ||
|
e115fa4bb3 | ||
|
52a2daddbf | ||
|
c400142996 | ||
|
219171a18c | ||
|
d7d573d9dd | ||
|
c4b17f8919 | ||
|
da2f4b96c7 | ||
|
08af9de190 | ||
|
9e2982992a | ||
|
c03ac5c5f1 | ||
|
5934f2f08e | ||
|
f136f40fdc | ||
|
d213e4ab57 | ||
|
782aae4ddf | ||
|
4f47bd7bcd | ||
|
4f76946140 | ||
|
aa60923cbd | ||
|
7670ce32b1 | ||
|
0b98632336 | ||
|
49c998de83 | ||
|
da69d6b2c9 | ||
|
a757aefce0 | ||
|
b2129cb8c4 | ||
|
27f14c175f | ||
|
aad49c520b | ||
|
af794a16d6 | ||
|
ca7266cd69 | ||
|
fb758afc81 | ||
|
b806f0da49 | ||
|
4e3b1f3e13 | ||
|
475467c41c | ||
|
28e5039873 | ||
|
1efc6e6151 | ||
|
e280a02643 | ||
|
788dc42684 | ||
|
01f2759a62 | ||
|
9419e3af9c | ||
|
6d220005cc | ||
|
6d68e61bbd | ||
|
29582623b0 | ||
|
4571e7853a | ||
|
d6e59c5238 | ||
|
6335cbedb8 | ||
|
f3344ce05d | ||
|
155d38d24a | ||
|
412f6d8316 | ||
|
d0f3ed80e0 | ||
|
b86f3b74bd | ||
|
60054d1d8b | ||
|
c40c6428d7 | ||
|
e708ecccee | ||
|
04f991d3bf | ||
|
d5d7368ba0 | ||
|
abff929d4e | ||
|
49edc82594 | ||
|
7dd7d0ec17 | ||
|
61aaf64f95 | ||
|
da35785951 | ||
|
e394034678 | ||
|
edf002ab32 | ||
|
50f2076981 | ||
|
a9ae78bcde | ||
|
0d7f9a2ab3 | ||
|
a5ee120281 | ||
|
12ebf44e22 | ||
|
220f5d0967 | ||
|
4ef05272c4 | ||
|
c3262aa5f8 | ||
|
42119c8f42 | ||
|
27cce03968 | ||
|
776aba1e49 | ||
|
9356841cfc | ||
|
7fc233e808 | ||
|
7b2eac3abd | ||
|
93323deea5 | ||
|
e8fa399e9e | ||
|
a974eb5d9f | ||
|
6a3f04274d | ||
|
f8e1395087 | ||
|
82b5af51e5 | ||
|
cf40c14a86 | ||
|
be2b2b7836 | ||
|
0cebae8032 | ||
|
6ebc77cbba | ||
|
542a5e88b7 | ||
|
72b36522e8 | ||
|
252d937405 | ||
|
a73b0309b9 | ||
|
c22283e799 | ||
|
1e51ab4a59 | ||
|
0c2f2667d3 | ||
|
496c9ddb91 | ||
|
5ad0b2eed9 | ||
|
577d62e783 | ||
|
dcb73c9916 | ||
|
6b2910c3c7 | ||
|
db04906416 | ||
|
54fceeb07d | ||
|
fa32678645 | ||
|
ee1df1a396 | ||
|
729be8767c | ||
|
8471f0d86d | ||
|
390b818d71 | ||
|
1a1c190ea3 | ||
|
40d82b80f1 | ||
|
7647ecbbc7 | ||
|
b2a5cf9dd0 | ||
|
cbf3ab2ec2 | ||
|
c4a37ff0ba | ||
|
bdc7ea4346 | ||
|
d4c4b9b10a | ||
|
4b9105edff | ||
|
c0980a5a9e | ||
|
272055fc1d | ||
|
cbb1851b12 | ||
|
45d758d52e | ||
|
49936ef095 | ||
|
986d4be601 | ||
|
b6ad9719ad | ||
|
96a1546da1 | ||
|
6884dd2b9e | ||
|
9c027e59c4 | ||
|
9e16ff3644 | ||
|
4000cb5d1c | ||
|
1030b412ff | ||
|
54f0fbcf6b | ||
|
1602182085 | ||
|
20bb1238c5 | ||
|
68fbe8125e | ||
|
23e6e6e69d | ||
|
ade5af2559 | ||
|
0a993dc012 | ||
|
4baa46507f | ||
|
0d8878abd3 | ||
|
f962084591 | ||
|
921b45286b | ||
|
301bfed05e | ||
|
62e7d6fa86 | ||
|
7e34cd4452 | ||
|
b35cb9a7ab | ||
|
18b836525c | ||
|
e34e49b88d | ||
|
5bf879c2d9 | ||
|
61d6309c0e | ||
|
17c9c0f9ef | ||
|
9bd62fd3d4 | ||
|
de73d4baa7 | ||
|
379ff5163b | ||
|
d9538ccb08 | ||
|
9289dbdf77 | ||
|
5719ae495a | ||
|
a89e3b7924 | ||
|
e576d09712 | ||
|
43600a7824 | ||
|
921f01d9dc | ||
|
dec5668582 | ||
|
e30bc12354 | ||
|
fca4fa666d | ||
|
59478160c8 | ||
|
8110fe89ef | ||
|
c7ed1b5eae | ||
|
25951e7097 | ||
|
687b9d1bef | ||
|
630e77b9eb | ||
|
68ff2e17b3 | ||
|
945d83608a | ||
|
833d473268 | ||
|
2256b0c5ec | ||
|
677aa7d59b | ||
|
4363005e92 | ||
|
2e3ef618db | ||
|
1adbbd14c6 | ||
|
4dfd7db729 | ||
|
e70562a102 | ||
|
a0e5947ba4 | ||
|
e841ea8a91 | ||
|
770a435029 | ||
|
49b56e84a7 | ||
|
ff1a6fdbbd | ||
|
2168090b96 | ||
|
33f7b680bc | ||
|
0957298cb8 | ||
|
08f2f982cf | ||
|
3376fbfa55 | ||
|
4a31e5fe73 | ||
|
baacec5ba6 | ||
|
95188b71b8 | ||
|
cf57be6e98 | ||
|
b691092d7a | ||
|
03e60fc028 | ||
|
2e25ec318f | ||
|
7236e5e9ae | ||
|
6f7dd51a98 | ||
|
db1dab9293 | ||
|
06f1b9e4d7 | ||
|
8f958ac931 | ||
|
eae63bfb79 | ||
|
8adfc81c30 | ||
|
0c516443e3 | ||
|
ad9bc0bf63 | ||
|
b0596670a6 | ||
|
998011ff43 | ||
|
081e1c5b62 | ||
|
edfd2c4d54 | ||
|
9d65a3a34c | ||
|
1a86cbdb9d | ||
|
849cb4456c | ||
|
1736717f2e | ||
|
50b75e1e63 | ||
|
179005dd6c | ||
|
6b3fe6b2cb | ||
|
c34872bb26 | ||
|
4845a21287 | ||
|
ddd513fe09 | ||
|
4ce118d459 | ||
|
99da184ba5 | ||
|
e2e4e62c4f | ||
|
229dd7a6dd | ||
|
1e0c4cc5cd | ||
|
b40ccc7a15 | ||
|
7d6b7e588c | ||
|
22cacd2c5b | ||
|
6a22e7836a | ||
|
32a67b9b33 | ||
|
b5dff09c28 | ||
|
c56d8153e8 | ||
|
eb5950d126 | ||
|
4241436e40 | ||
|
e93af7aed5 | ||
|
d2416f850e | ||
|
7af22eb006 | ||
|
3f1be5e7e7 | ||
|
a4e649c82d | ||
|
cde1a8f0a8 | ||
|
cda6eb7c2f | ||
|
3c3e6aeca0 | ||
|
99156311db | ||
|
91b2e5e703 | ||
|
573fd816d0 | ||
|
e6aa002758 | ||
|
361d46ac5d | ||
|
9bc7c2fd65 | ||
|
ce49f5f8b7 | ||
|
81e4cd319d | ||
|
71f905535f | ||
|
d3bff0a9e3 | ||
|
88e6315d09 | ||
|
3d933c5244 | ||
|
73af19314d | ||
|
1d71712c8a | ||
|
1333c35389 | ||
|
e0f5f47a15 | ||
|
34323042d5 | ||
|
aa86fa9986 | ||
|
d2d659d5a9 | ||
|
1595248b52 | ||
|
fc94c616b5 | ||
|
f6f0bcf1c3 | ||
|
6eeeda5dab | ||
|
58993d2ead | ||
|
7c0b608769 | ||
|
259be84a3e | ||
|
57c880cf85 | ||
|
0469fe76d7 | ||
|
a3e74320fa | ||
|
4e0f4397b2 | ||
|
b26aab3863 | ||
|
75ec97fe61 | ||
|
9a356453fc | ||
|
f0020b3393 | ||
|
ea9f1dc91d | ||
|
2ef77dcf1f | ||
|
29e7ec4cc9 | ||
|
5710e3fad0 | ||
|
a03aa62d58 | ||
|
2203a39917 | ||
|
bc58668483 | ||
|
0b4650f355 | ||
|
aecdec48ad | ||
|
e49198a0d4 | ||
|
6aa4553dd3 | ||
|
860e8a8f5a | ||
|
434037d793 | ||
|
214eae27da | ||
|
0567188fa8 | ||
|
4326902683 | ||
|
da3589149d | ||
|
69b4b4d1f4 | ||
|
dd4b46a88b | ||
|
6f2253b2f4 | ||
|
4c00d9512e | ||
|
6f511cb1e6 | ||
|
029e84f538 | ||
|
42b1db1d08 | ||
|
9d4b34cad7 | ||
|
cd9aff0f59 | ||
|
032816fffc | ||
|
1408f06c8d | ||
|
49f8d0c6d8 | ||
|
0d6443c30a | ||
|
e62d91dda8 | ||
|
0e122c9dc5 | ||
|
c2bd453e8c | ||
|
949ea7afb7 | ||
|
9f575101d2 | ||
|
2b4470054e | ||
|
b220613e54 | ||
|
11328babe8 | ||
|
735bc92bc4 | ||
|
6894b9b1b7 | ||
|
4de7815f31 | ||
|
4ce7b48468 | ||
|
77e4cb87ad | ||
|
dd6b43035e | ||
|
e76094c546 | ||
|
9c00dc4cab | ||
|
fc416de348 | ||
|
a2156be4ec | ||
|
cf427eba0f | ||
|
0050c90678 | ||
|
5eeff02dbe | ||
|
eb283efc20 | ||
|
87121c9c21 | ||
|
a2dbbbf832 | ||
|
fd251cd9a9 | ||
|
8ced2d288e | ||
|
242926d381 | ||
|
8c9a148e71 | ||
|
9e165fc7dc | ||
|
f46e4e0cec | ||
|
efcf5ecef4 | ||
|
b6ba843d09 | ||
|
915112e274 | ||
|
cc8f8fcd2c | ||
|
bda3ea9a35 | ||
|
a7aed1f93a | ||
|
a33f602f3b | ||
|
4d08b49578 | ||
|
21e049ab45 | ||
|
1d4234caea | ||
|
d1269de3a7 | ||
|
8c19fcf8fc | ||
|
beaba2be55 | ||
|
32c9ad1d59 | ||
|
a194513252 | ||
|
cd93a1d1e1 | ||
|
2867728996 | ||
|
394cc78851 | ||
|
76f0a26322 | ||
|
92d7a577a0 | ||
|
9c1219b188 | ||
|
f62bbfe286 | ||
|
fef176eb96 | ||
|
ed541dac3b | ||
|
4a175b2158 | ||
|
4076899e11 | ||
|
998bdf3b56 | ||
|
76b211eb6d | ||
|
f781cb3922 | ||
|
ced3e5d623 | ||
|
2a4812cb87 | ||
|
1f384c7ae4 | ||
|
01a3285636 | ||
|
53166cb3f5 | ||
|
893462ae87 | ||
|
949dbf45f1 | ||
|
d7dc625042 | ||
|
cc91c77f9e | ||
|
4847fd76de | ||
|
25b711a8da | ||
|
b5e50fa62e | ||
|
28e40a5c86 | ||
|
a8a7b7c07b | ||
|
299008688d | ||
|
42a674008f | ||
|
8fdc0bf17c | ||
|
4e3896c108 | ||
|
96cd56548e | ||
|
739fb2c3d2 | ||
|
0c98573700 | ||
|
8dc0b34fdc | ||
|
384ca09842 | ||
|
97d86825c2 | ||
|
f5a824b1e6 | ||
|
4fc11e77e8 | ||
|
8d16ad7035 | ||
|
3b1d84b00b | ||
|
3f540da31b | ||
|
3a20f1c245 | ||
|
e803f0c545 | ||
|
c9495c23a6 | ||
|
26aae9eea5 | ||
|
7d92136467 | ||
|
a7896b43d7 | ||
|
2e12b2655b | ||
|
a1f0bd1e6c | ||
|
4472590133 | ||
|
64f1214ad9 | ||
|
bd465a72cf | ||
|
1d1c2a6925 | ||
|
d68ba32533 | ||
|
d1a316032d | ||
|
b465042a56 | ||
|
8ff2418b02 | ||
|
f6640bcc32 | ||
|
fa3c0fd18a | ||
|
2ac03c18c4 | ||
|
51ee6128f3 | ||
|
53478056de | ||
|
83c791564a | ||
|
65d0032995 | ||
|
37c4f99341 | ||
|
adbc873b2a | ||
|
3567a57d6a | ||
|
b4e9fb157f | ||
|
1be3646a04 | ||
|
46b82f877b | ||
|
ef56eb4a52 | ||
|
6989f9fd16 | ||
|
7c3e8547de | ||
|
8268c3edba | ||
|
a8cc8763b0 | ||
|
09b4acf087 | ||
|
3b719ce53b | ||
|
83281197f1 | ||
|
ffda04f9b4 | ||
|
606893286a | ||
|
075b71746a | ||
|
01f1f3e957 | ||
|
88a9d5e802 | ||
|
7eebfae1c3 | ||
|
340fe76b8f | ||
|
e83678df3a | ||
|
0bbbb49a31 | ||
|
0e61115857 | ||
|
8d3ac6f6fe | ||
|
86e644be9a | ||
|
30201249b5 | ||
|
41155f5ef4 | ||
|
f308fe71f9 | ||
|
5a00214fd2 | ||
|
1b3e0f65e1 | ||
|
7cfc537870 | ||
|
53513db5b5 | ||
|
59402cf198 | ||
|
12efc02d91 | ||
|
f38b907680 | ||
|
8fcb46ca4a | ||
|
71ec9a193f | ||
|
393a545548 | ||
|
f88449fbb0 | ||
|
50ad4d05f2 | ||
|
8d0e562c8a | ||
|
c98e67c003 | ||
|
5565538b80 | ||
|
958979e1d7 | ||
|
685845e097 | ||
|
3813f9e417 | ||
|
40cf3fb4d0 | ||
|
3f88b16603 | ||
|
32c02275a2 | ||
|
c0d679b6c2 | ||
|
eb789b9b9a | ||
|
b1898141c3 | ||
|
3231bfaff0 | ||
|
6bb09c9c30 | ||
|
35d3fe03ab | ||
|
f05929cd17 | ||
|
982c9828da | ||
|
d6eacb2b33 | ||
|
0abe213fc2 | ||
|
a6716d55c5 | ||
|
fa0572de77 | ||
|
6dca4a0d6b | ||
|
eacfe1a9ac | ||
|
c223cdf10a | ||
|
38c42b7a15 | ||
|
56432dc773 | ||
|
d3b4379161 | ||
|
8a6fcdbcae | ||
|
061ce646d2 | ||
|
5ec7c16e3e | ||
|
5d629508c1 | ||
|
0a6f8b0cfe | ||
|
460f826672 | ||
|
cb16a10121 | ||
|
3b6131f1ca | ||
|
57225a36b9 | ||
|
3e313399c2 | ||
|
7947e14792 | ||
|
71098102c5 | ||
|
8e601e80df | ||
|
3b14cfcc54 | ||
|
61a0e43092 | ||
|
d08aaffe41 | ||
|
7d0def30f0 | ||
|
bb45d72a56 | ||
|
3943192c5d | ||
|
18d76807f6 | ||
|
01a30a7ccf | ||
|
3fb35d043b | ||
|
9b51bd484d | ||
|
879b556845 | ||
|
0df2a501dd | ||
|
6f64fdffcc | ||
|
19252af345 | ||
|
897f53b13e | ||
|
45cd8b7e3c | ||
|
922bbdd1b6 | ||
|
c24135577c | ||
|
3240ff40bc | ||
|
58b68cab0c | ||
|
0a0afd7245 | ||
|
de01606bff | ||
|
046474b484 | ||
|
277b2b9298 | ||
|
0b7296ae95 | ||
|
ce87dd55f0 | ||
|
07b724d65b | ||
|
bc1984298f | ||
|
9ed0bdfed2 | ||
|
57628e8986 | ||
|
fc8f53fd0e | ||
|
efd7cc6a0c | ||
|
7bfc3e1256 | ||
|
7f6ca1e527 | ||
|
71d9b1d441 | ||
|
a3d64d0a90 | ||
|
e7ec2ce6cf | ||
|
61dbdd5f7c | ||
|
e6888c451d | ||
|
899219abd2 | ||
|
7a4c7a13eb | ||
|
e8797f49b9 | ||
|
e17f3051f0 | ||
|
06091933e1 | ||
|
b30c65dd89 | ||
|
0eb18d206d | ||
|
3a9948e528 | ||
|
2bcddd38b2 | ||
|
5ff8808f69 | ||
|
28b1d8a324 | ||
|
5c1dd79435 | ||
|
706d59a6cc | ||
|
251f1bba55 | ||
|
cb1fd85ca4 | ||
|
11ddc55911 | ||
|
ee106105d8 | ||
|
19f5a144e5 | ||
|
18717bac79 | ||
|
28fa83c406 | ||
|
258e514a91 | ||
|
f92546d6eb | ||
|
693456164b | ||
|
7cd3e6b1f7 | ||
|
764e63d869 | ||
|
2c6f47f277 | ||
|
e41896d6f3 | ||
|
990591cc80 | ||
|
7b85c33213 | ||
|
4b4d0e8b25 | ||
|
4fb6af3c62 | ||
|
d7ffe7e294 | ||
|
3ff139d445 | ||
|
40443e0134 | ||
|
1f927c1285 | ||
|
5e82e62335 | ||
|
de954827df | ||
|
2cb24bf198 | ||
|
739a6a4a9c | ||
|
dfcf1193dc | ||
|
c72f92e22f | ||
|
f6d01ce7e1 | ||
|
fed9700587 | ||
|
12a3a9a89e | ||
|
590c62c371 | ||
|
df41f64b3c | ||
|
1849355245 | ||
|
fa1b471ea4 | ||
|
0a67f8c947 | ||
|
621d0260f4 | ||
|
f93fca7c5b | ||
|
f4d830c2ef | ||
|
1aa2844efa | ||
|
f22b2adaad | ||
|
b547f9aa13 | ||
|
e4166e9da7 | ||
|
1634fdb421 | ||
|
7f51035f91 | ||
|
70e71cbc2a | ||
|
ffd03bf34c | ||
|
f59a3dee78 | ||
|
3ba3037242 | ||
|
dbb269fef6 | ||
|
e209189faa | ||
|
2d7065cde2 | ||
|
59f5972c93 | ||
|
8bac5275c3 | ||
|
58d748e235 | ||
|
e69ac3e62a | ||
|
7fc8ac4c0f | ||
|
069dce69cd | ||
|
3bdf621026 | ||
|
2f62897fb6 | ||
|
dbdd602f50 | ||
|
d66fa87b2b | ||
|
124a959c8d | ||
|
127dce1cd6 | ||
|
44986a35a4 | ||
|
ea516301fd | ||
|
90b684457a | ||
|
8517e2aa42 | ||
|
b880be8b7c | ||
|
57fd1a7588 | ||
|
5ed458f634 | ||
|
ac12143d00 | ||
|
b6c36d1961 | ||
|
3c2de70baa | ||
|
f6c5620682 | ||
|
79b6f64bd0 | ||
|
ed89f55bff | ||
|
8841a92142 | ||
|
0bd19c97eb | ||
|
2fd026d107 | ||
|
5e7eb37ca7 | ||
|
6b10d5d821 | ||
|
0f4dbfbcbf | ||
|
99493174ec | ||
|
333253fd4f | ||
|
01470e8d3b | ||
|
bda2fe6717 | ||
|
d1f4c5be18 | ||
|
377adea51d | ||
|
cda3d20834 | ||
|
d11870b1eb | ||
|
53bda3eea7 | ||
|
30165e8d6a | ||
|
2202eb6570 | ||
|
720927f488 | ||
|
2858c0fce0 | ||
|
36c3521f40 | ||
|
98a3b99d17 | ||
|
95a06c4531 | ||
|
6ea77b36ce | ||
|
0cd7ebf9d3 | ||
|
a816498fc6 | ||
|
549c1a6767 | ||
|
f87d1f11b0 | ||
|
fb913bc33c | ||
|
53d7a51cb0 | ||
|
12da48c756 | ||
|
a0a6bdb684 | ||
|
eb4aa2c026 | ||
|
a9e789f845 | ||
|
89b76918bd | ||
|
e98575643c | ||
|
7c9e7743f1 | ||
|
435aa4845b | ||
|
9841488ce4 | ||
|
9c907989a5 | ||
|
f684d8e59a | ||
|
a1ad6c9712 | ||
|
b6ca3090df | ||
|
ff2d793fbb | ||
|
fcfb7f1111 | ||
|
6125cde223 | ||
|
c83391e624 | ||
|
a3a818ea74 | ||
|
54a4965503 | ||
|
2233d88c01 | ||
|
9680ab74a3 | ||
|
ef711af5b5 | ||
|
8a619e4b8b | ||
|
bc09d2c640 | ||
|
f82dfde6f4 | ||
|
312c7f2d27 | ||
|
953cc327c6 | ||
|
14cff96713 | ||
|
34b9af2ce0 | ||
|
6a6ee280c3 | ||
|
861387707a | ||
|
830e665366 | ||
|
f14ae68ea0 | ||
|
c7084bf27e | ||
|
fc8ffcbe46 | ||
|
77f089654e | ||
|
e7a12bb0dd | ||
|
22fe3a4e44 | ||
|
afb8b92528 | ||
|
5178f99100 | ||
|
72af564382 | ||
|
0a2b83998c | ||
|
73e79213dc | ||
|
e31ffc9474 | ||
|
fdb8706a5f | ||
|
4c0262bd82 | ||
|
3b2dcb37a6 | ||
|
46a3b020a6 | ||
|
8373392e99 | ||
|
695c2623c2 | ||
|
979e7f2486 | ||
|
e7a9f630ec | ||
|
4e8372174b | ||
|
1b8912d7e9 | ||
|
8c3669588b | ||
|
1b784fe3b0 | ||
|
5ab1d8dfd6 | ||
|
cda8b95bfa | ||
|
9664842b1a | ||
|
09836e1d34 | ||
|
8974e20c7f | ||
|
761a49803e | ||
|
a40028f0c0 | ||
|
d4fca7e313 | ||
|
6748d4d825 | ||
|
0a5ad1d628 | ||
|
4080958e10 | ||
|
95c4a717e3 | ||
|
c5b9353105 | ||
|
17595f7ceb | ||
|
dcf78e8a06 | ||
|
de0c54c3c3 | ||
|
38970e9a52 | ||
|
563f43168b | ||
|
6e235605ed | ||
|
0749e0b675 | ||
|
756f3e627c | ||
|
4b39ed42b1 | ||
|
abe85ca4bd | ||
|
a559565ace | ||
|
d35e3163ca | ||
|
f22ad85681 | ||
|
727bdaeea4 | ||
|
42f7764eed | ||
|
1fbc1073ca | ||
|
66b683e5e7 | ||
|
7d1f61e47b | ||
|
643e5a6a2a | ||
|
a1e7e29279 | ||
|
abf6c3f3f1 | ||
|
32c81ad8c2 | ||
|
0f461c4caa | ||
|
57ed718993 | ||
|
ef1ae63233 | ||
|
81ac200cc0 | ||
|
3a2d62f6c7 | ||
|
7f80f67fd6 | ||
|
cebd8de77a | ||
|
417218fc34 | ||
|
29586437c2 | ||
|
f51d0ad914 | ||
|
35294b5f97 | ||
|
758562cff9 | ||
|
da0678c6fe | ||
|
afe4fd70cc | ||
|
4cef383fe7 | ||
|
b58c30889e | ||
|
1561c0e4d7 | ||
|
32b11c15a4 | ||
|
5e06efc1b9 | ||
|
43bd88703c | ||
|
cdd70230af | ||
|
eaf3eef2d3 | ||
|
81ec8eaf83 | ||
|
23f7e350c6 | ||
|
cea368cd3f | ||
|
50c8f2ae61 | ||
|
ed0ecababb | ||
|
72aa4c40a5 | ||
|
4f6784e2e5 | ||
|
01f015a54f | ||
|
806acad22a | ||
|
1d322d8a39 | ||
|
aef94e6950 | ||
|
456fcec334 | ||
|
3b6937c2f0 | ||
|
7cdd1d41c1 | ||
|
1fc535a740 | ||
|
033b37077a | ||
|
07f6a4a08b | ||
|
8c1b592a51 | ||
|
9460f7a17a | ||
|
106260ed69 | ||
|
123162e946 | ||
|
54e81966e5 | ||
|
9bf6ab60bb | ||
|
4f0ff3cdfc | ||
|
47cfaaa5ab | ||
|
1f49788f21 | ||
|
7e4cf9aeda | ||
|
4b6964b683 | ||
|
3caf398021 | ||
|
8840631266 | ||
|
c4411bb895 | ||
|
f63668350b | ||
|
3fc54d7ffd | ||
|
2c6c20f44f | ||
|
08ee939951 | ||
|
463b1441d3 | ||
|
6754d4ee89 | ||
|
d5d0732bf3 | ||
|
d27cbaa663 | ||
|
70adf10f2e | ||
|
e75417d26e | ||
|
2ded983828 | ||
|
0c708f766b | ||
|
a801f8d8ed | ||
|
26fc26c9fe | ||
|
df4c07d204 | ||
|
cf565d1563 | ||
|
6b425cf543 | ||
|
6188e7a0fa | ||
|
310afb0ad6 | ||
|
7d7e1e1637 | ||
|
424c8a2723 | ||
|
187fb1b85d | ||
|
595fd7a9aa | ||
|
0027cb9036 | ||
|
db6878b978 | ||
|
1c78722573 | ||
|
fea83e28c4 | ||
|
84adf64271 | ||
|
74e2b0d15d | ||
|
df302206dd | ||
|
6d8941c005 | ||
|
971b20062f | ||
|
936f334b61 | ||
|
7e3a290939 | ||
|
c6466a780e | ||
|
43774ad3fb | ||
|
b3ba5b7747 | ||
|
599f4ccef4 | ||
|
a67600d264 | ||
|
ebf4c89ef0 | ||
|
aa7da784fc | ||
|
617ed42d8c | ||
|
5848e7d90d | ||
|
01d032261c | ||
|
54d06646aa | ||
|
81da46035a | ||
|
74d0f75802 | ||
|
f25a352dcb | ||
|
21ef8da45f | ||
|
1f3a1c49df | ||
|
a8b58aaec3 | ||
|
44f9b35d93 | ||
|
711af9beed | ||
|
971339ca9a | ||
|
f5a72ffbaf | ||
|
cf4331c5af | ||
|
07b85388fc | ||
|
553b086ba2 | ||
|
ff89537899 | ||
|
f20792889a | ||
|
ee22a7ff73 | ||
|
4fc4353859 | ||
|
f4f9e25e6b | ||
|
e54f9934b5 | ||
|
f599ae065a | ||
|
5bd9bf6a4e | ||
|
cb9e8d4f76 | ||
|
f64596672e | ||
|
961150b2d3 | ||
|
0cd8e1cbc0 | ||
|
b503aff5e9 | ||
|
54bde47c67 | ||
|
d345f8dc13 | ||
|
979b0c0e78 | ||
|
13525f3327 | ||
|
5d9c2c0904 | ||
|
c748281d86 | ||
|
fa619eba7c | ||
|
17a75f7cf5 | ||
|
a74cfbfb25 | ||
|
af01fed950 | ||
|
218b446915 | ||
|
a68df18cd5 | ||
|
f79326b2d5 | ||
|
b08dcdfd90 | ||
|
ced14e578f | ||
|
0528c6e970 | ||
|
df9c1e0aeb | ||
|
b6619b41df | ||
|
ab9675544c | ||
|
0d9f703c75 | ||
|
f6ee5dd0e7 | ||
|
423d880b92 | ||
|
41017b9ab8 | ||
|
e3314c859f | ||
|
8f794fdbc6 | ||
|
80b0773618 | ||
|
ac848241b9 | ||
|
b4432e2efc | ||
|
52f60a2cf6 | ||
|
03c8438050 | ||
|
0e1c36ccf1 | ||
|
1b2b68c485 | ||
|
5c36f3e78a | ||
|
d4ec8ae823 | ||
|
449a70d840 | ||
|
8375030135 | ||
|
e9f938b0fb | ||
|
b0e57a5990 | ||
|
f47610e6fd | ||
|
c131cb9bb8 | ||
|
023f775bd2 | ||
|
1127eb6e09 | ||
|
0d7437c7d2 | ||
|
6dbc691973 | ||
|
fe53200a3a | ||
|
c7f61271a0 | ||
|
b826e2d97b | ||
|
d8ad42b04a | ||
|
b3ca30b8b6 | ||
|
bdd9da3d22 | ||
|
eb43ddc701 | ||
|
f9c65a148f | ||
|
bd0b9dff51 | ||
|
3822309657 | ||
|
ac2ec65c81 | ||
|
c0943f1776 | ||
|
5a2ab5fae7 | ||
|
281e36fde7 | ||
|
1af1c445c6 | ||
|
e6d2e028ad | ||
|
8d25b8075d | ||
|
58201fd6c3 | ||
|
2b624a9aed | ||
|
792fd7c50d | ||
|
90d37a15bd | ||
|
ef57ee803f | ||
|
e2bfb74895 | ||
|
89b486a3ee | ||
|
d6c6ef20d4 | ||
|
6cb6e9541f | ||
|
a8acecb703 | ||
|
0938297250 | ||
|
a2da2f681c | ||
|
32477e901d | ||
|
1841c4dc11 | ||
|
f81dce3321 | ||
|
e15f84716a | ||
|
d11ecd8fac | ||
|
012e70d668 | ||
|
264ee4f319 | ||
|
4640d6d6e3 | ||
|
9db9d98419 | ||
|
f79d7b9626 | ||
|
a8da469523 | ||
|
3c5eb99c59 | ||
|
8dc14516d6 | ||
|
84efc6a04e | ||
|
86e3038cfe | ||
|
6898b9ca4c | ||
|
9eb62920f7 | ||
|
7cf267254f | ||
|
5a0e3cbbf2 | ||
|
4b376ec5c2 | ||
|
92b71d3eb2 | ||
|
c32d7de7c4 | ||
|
c83a61d45d | ||
|
429f2d5aab | ||
|
ed70cf571c | ||
|
fd77671575 | ||
|
9d915a916e | ||
|
acb90935c7 | ||
|
6301ba0a12 | ||
|
44e1245416 | ||
|
6527d9db83 | ||
|
2bcaf62a2f | ||
|
2db3998f11 | ||
|
04724c58d1 | ||
|
7ed9c2770c | ||
|
48883bfa13 | ||
|
b6d9e695d1 | ||
|
43bab3c1bd | ||
|
dd67602b87 | ||
|
c96a416c2c | ||
|
c4ca9cde32 | ||
|
6c5d208893 | ||
|
795fd8b58c | ||
|
c226940792 | ||
|
bdd0a6106f | ||
|
d1ea7ad3ea | ||
|
a2b1181f7c | ||
|
8cce2f17d5 | ||
|
658b911513 | ||
|
3c95979d94 | ||
|
2e32211a28 | ||
|
ba2874173d | ||
|
ba9598682c | ||
|
30bfd82683 | ||
|
10c6774c28 | ||
|
c4ad9019aa | ||
|
7c99ab9947 | ||
|
feb7778fe4 | ||
|
b45185780f | ||
|
4e032c0c55 | ||
|
2e2b35dfeb | ||
|
526ef7e1d2 | ||
|
a37005446a | ||
|
e012b927dc | ||
|
359b8d5545 | ||
|
23c592b322 | ||
|
9aeddf86f4 | ||
|
0e1887988e | ||
|
394f2df664 | ||
|
2a04378a0d | ||
|
bac68f2c42 | ||
|
0f0ff86ffa | ||
|
8b38752cbf | ||
|
3c24589450 | ||
|
65065a62d8 | ||
|
482e9340bc | ||
|
69d490996e | ||
|
3494937e34 | ||
|
41ba1043be | ||
|
cc57de60c0 | ||
|
60a2b9e5c6 | ||
|
8e1fb666a5 | ||
|
f4ad777bbf | ||
|
2eacf6146a | ||
|
fe2ba34cb4 | ||
|
84daa0db61 | ||
|
b9118a90be | ||
|
ef2ffd4e52 | ||
|
5e3063abe0 | ||
|
f460b2497a | ||
|
c080d7054f | ||
|
61dd4cefbc | ||
|
63d99b2d70 | ||
|
55332d7671 | ||
|
16635defcd | ||
|
595cf3fcad | ||
|
c9b9f82130 | ||
|
f5bca733d7 | ||
|
494e235e18 | ||
|
85219a6004 | ||
|
e4a7851e57 | ||
|
b7529b40b5 | ||
|
74827e5324 | ||
|
2e16cc5433 | ||
|
7f41bc5703 | ||
|
a2380fb752 | ||
|
f6a902809d | ||
|
33a853397d | ||
|
4f02481899 | ||
|
b18076565f | ||
|
853ddc5840 | ||
|
7930ab66c6 | ||
|
c7716c0d59 | ||
|
49cbb254d0 | ||
|
cf9246796d | ||
|
e1dee546dc | ||
|
da6fdad2de | ||
|
567596643c | ||
|
cb617e1b14 | ||
|
facf8afa8b | ||
|
f0dd61a711 | ||
|
e958211a13 | ||
|
0ed170b5bc | ||
|
473d3453a2 | ||
|
fa9d7b0408 | ||
|
d4a28f48c9 | ||
|
ead6fff861 | ||
|
c7d06b35cd | ||
|
fa939e5c76 | ||
|
1bf2601f4f | ||
|
feb0c02c9a | ||
|
40a34a7c05 | ||
|
c62dcd96b0 | ||
|
1364d6786d | ||
|
9f2666aef9 | ||
|
a6a351e68d | ||
|
1db38a9699 | ||
|
c57db1834f | ||
|
3aeb49b469 | ||
|
80b467eead | ||
|
61572f287a | ||
|
f136664c11 | ||
|
0e545baf10 | ||
|
e65e647359 | ||
|
238268884e | ||
|
4c210d0956 | ||
|
5f32c6466a | ||
|
71bd39a8a3 | ||
|
ffb660f0f4 | ||
|
dde23632c1 | ||
|
9d26f13db0 | ||
|
2a4c9c4427 | ||
|
3bfde26b74 | ||
|
a419bc7253 | ||
|
89e0dad88b | ||
|
ff1ee1249b | ||
|
ba9cfd8041 | ||
|
80a51e02a4 | ||
|
a2e2a9a2f5 | ||
|
49e4c37cac | ||
|
11d323d8b7 | ||
|
784ba45f1f | ||
|
e534414874 | ||
|
01f4faf8f1 | ||
|
b33d30ca47 | ||
|
1ba3fae101 | ||
|
9550347e04 | ||
|
398946d39a | ||
|
05faf55e8d | ||
|
4cf5525e20 | ||
|
62e91c44d7 | ||
|
e4ec4ae92b | ||
|
c1f5dfd9cc | ||
|
0c0efeac1f | ||
|
5e0f2642e3 | ||
|
93966b0fa1 | ||
|
e90abf3517 | ||
|
d3fa0dce96 | ||
|
58a7c2fa94 | ||
|
962a8700c2 | ||
|
b5c704e2bb | ||
|
e7b52b19d7 | ||
|
903c2e6d92 | ||
|
abcb1ac760 | ||
|
b6bf76cc4c | ||
|
2bf87655da | ||
|
d4b19f19a1 | ||
|
d8ccc38d5b | ||
|
c8c9f80cc5 | ||
|
577bef5704 | ||
|
4e3b8a06ea | ||
|
363632ffa7 | ||
|
994c99f47f | ||
|
96571baadc | ||
|
4ce2b2c948 | ||
|
5d69f7a0a7 | ||
|
69ddcc6e30 | ||
|
bcb1f36ad8 | ||
|
34c65a686c | ||
|
0b32741a12 | ||
|
dbb321a3cc | ||
|
a6e4f47adf | ||
|
fb6dee32ac | ||
|
984dd6f2c0 | ||
|
02bde2c6e9 | ||
|
782d24cc04 | ||
|
4d0af575e5 | ||
|
be8bda8e73 | ||
|
1242de532e | ||
|
7d7ec7f15e | ||
|
ca112ec5d3 | ||
|
5deb8cf76d | ||
|
a2c9737c17 | ||
|
d3fca3d6cc | ||
|
16554b22c7 | ||
|
d5574098f0 | ||
|
f5a683f25c | ||
|
5f04adb392 | ||
|
edd0f7e255 | ||
|
67145d9104 | ||
|
003e2afff7 | ||
|
6e9d70c5cb | ||
|
4821b4cdf2 | ||
|
734d4db431 | ||
|
317aaed0ac | ||
|
9778098d6c | ||
|
5b1755f988 | ||
|
2a772895dd | ||
|
5fbb01cf2f | ||
|
f9e0870b4e | ||
|
a58f1e9b4b | ||
|
6fc0d89b30 | ||
|
2dcadab7d2 | ||
|
bb3307e156 | ||
|
ecd07f1209 | ||
|
266cb1174f | ||
|
bfb9b77b6e | ||
|
01b1361dcb | ||
|
3a921720d6 | ||
|
cdfd3d9c31 | ||
|
9961fb64bb | ||
|
3137c355cf | ||
|
16abaf60d2 | ||
|
9004b710ea | ||
|
6ebac21c2b | ||
|
99f79faf83 | ||
|
613fa20806 | ||
|
1b5f812278 | ||
|
3a9643c1ea | ||
|
aee7f5a8ac | ||
|
d3cd378922 | ||
|
4f5e7367d0 | ||
|
2280fb5c43 | ||
|
96fb7c2087 | ||
|
6e994fd8b9 | ||
|
a7cde1e269 | ||
|
f8310ba0d5 | ||
|
b239c3faba | ||
|
3c2281dbf9 | ||
|
ac07bf784d | ||
|
067455542f | ||
|
5bfeaa357b | ||
|
fe27a64331 | ||
|
ed638612aa | ||
|
1d7ec83510 | ||
|
75c710232d | ||
|
5af52afadb | ||
|
0f4324c2f8 | ||
|
b48e1dac94 | ||
|
f0ca8e1e31 | ||
|
74b83eb71e | ||
|
28dce0fbb5 | ||
|
c12d402c7e | ||
|
014f5b123c | ||
|
58601dfc04 | ||
|
9fe7f0adae | ||
|
ea1374371f | ||
|
bce4294529 | ||
|
de409b632a | ||
|
a677b2e844 | ||
|
c63bb19cb6 | ||
|
72fd77812e | ||
|
40f3e72bd1 | ||
|
d6d86f2aff | ||
|
c71809438b | ||
|
3e6e1a0a36 | ||
|
f590ce4a34 | ||
|
67608e29c8 | ||
|
d5c2982093 | ||
|
90fad2a3ab | ||
|
bc7c82e3da | ||
|
cb824f7dd7 | ||
|
32c47a96f1 | ||
|
4e3f8893f7 | ||
|
ca3946689a | ||
|
e2ad2dfcaa | ||
|
d6f7893c56 | ||
|
8c65a7cc31 | ||
|
aabb9be7de | ||
|
544f93bf22 | ||
|
f81dbf4a4c | ||
|
fbec8263a3 | ||
|
68d77a69e6 | ||
|
f2ef2446c6 | ||
|
875cb5387a | ||
|
ae9ecdad40 | ||
|
86a0e34975 | ||
|
1141074745 | ||
|
efc46d9989 | ||
|
2b45f745b6 | ||
|
37fb81e9b2 | ||
|
255a4fac93 | ||
|
3e3fb88de8 | ||
|
e4cf03ae46 | ||
|
554a3eb10d | ||
|
61881b528a | ||
|
c2507cbc4e | ||
|
ed0f6d165c | ||
|
8e22d38eb3 | ||
|
2599c425c3 | ||
|
0e15821a81 | ||
|
c1bb51c12b | ||
|
1532b6d159 | ||
|
945018b698 | ||
|
df7b981e5e | ||
|
4fe495675b | ||
|
7828eef2ad | ||
|
694f81b75f | ||
|
8364b6e08d | ||
|
3f4328ce9d | ||
|
9e0bf1acb2 | ||
|
c9e130a771 | ||
|
26331f61e1 | ||
|
694672859a | ||
|
858ead40b9 | ||
|
b07fe5cc34 | ||
|
0317171097 | ||
|
9741a3a53d | ||
|
7937fab5ff | ||
|
f595be07d4 | ||
|
eef106c99b | ||
|
dbe1833f92 | ||
|
520dc0ae21 | ||
|
c9be287f4a | ||
|
711f5dcaba | ||
|
ebc0aa9809 | ||
|
dcaaf50a5a | ||
|
3370b57134 | ||
|
55c5ddedf4 | ||
|
5e8b09f5af | ||
|
1acffce62d | ||
|
8555ecce87 | ||
|
4df5f668dc | ||
|
cceebeebef | ||
|
c4f19465a6 | ||
|
e868102c98 | ||
|
0d4a1837f5 | ||
|
d6a4436313 | ||
|
03b5c6aa5e | ||
|
250cd47e02 | ||
|
943fef32e7 | ||
|
408634671c | ||
|
570b5856ba | ||
|
d114d14e87 | ||
|
32791f502e | ||
|
083ab0c788 | ||
|
003c4c4e26 | ||
|
f08f89ebd4 | ||
|
3c973144c4 | ||
|
82e99ca658 | ||
|
b04d750cec | ||
|
c804e9f541 | ||
|
7753f3f842 | ||
|
c985b7c682 | ||
|
4509919c22 | ||
|
89b164c7ca | ||
|
e52d17b39a | ||
|
5014914dc9 | ||
|
122ab83a84 | ||
|
7a985c2c8a | ||
|
b11ad30a31 | ||
|
5914fc97df | ||
|
e41ae1832d | ||
|
89b50909ed | ||
|
edccd7412f | ||
|
c76beae057 | ||
|
23c5934a7d | ||
|
a078947d6d | ||
|
0faaf660b4 | ||
|
5ba98b4200 | ||
|
c36513b99d | ||
|
97814531fa | ||
|
fd3e335a02 | ||
|
e676fa2b57 | ||
|
122cbbf673 | ||
|
271793b324 | ||
|
134b31933b | ||
|
0ec5518a62 | ||
|
76931370d7 | ||
|
8cf0e4517a | ||
|
e75c62bf0f | ||
|
058285e0b9 | ||
|
795568d8c2 | ||
|
df4933fddd | ||
|
4046a51472 | ||
|
45845f645d | ||
|
d7fd8944f7 | ||
|
3cee671f25 | ||
|
8f2e5faff3 | ||
|
39ddd934f6 | ||
|
9f8a46b9d9 | ||
|
c6698eaea6 | ||
|
8a96cf3434 | ||
|
74d255c056 | ||
|
71aa841265 | ||
|
14a93a9f26 | ||
|
e1fd9c6922 | ||
|
b9db747b5c | ||
|
4a56c76901 | ||
|
6bb3184a72 | ||
|
7fb8fbd450 | ||
|
9d5bba472e | ||
|
e6d821a45f | ||
|
72f0027e21 | ||
|
29a13a9943 | ||
|
3691ae4d13 | ||
|
4dda397c29 | ||
|
b4b058998d | ||
|
10919e415e | ||
|
4966d74864 | ||
|
c70ecb30a5 | ||
|
acc0d17e0f | ||
|
b509b878bf | ||
|
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 | ||
|
37e7ea0b52 | ||
|
44bf518244 | ||
|
63cb9b4968 | ||
|
a6cecc103c |
2
.gitignore
vendored
Executable file → Normal file
2
.gitignore
vendored
Executable file → Normal file
@@ -39,5 +39,7 @@ node_modules
|
||||
Tools/github_oauth_token.txt
|
||||
_releases
|
||||
ReactNativeClient/lib/csstojs/
|
||||
ReactNativeClient/lib/rnInjectedJs/
|
||||
ElectronClient/app/gui/note-viewer/fonts/
|
||||
ElectronClient/app/gui/note-viewer/lib.js
|
||||
Tools/commit_hook.txt
|
@@ -3,6 +3,14 @@ if: tag IS present
|
||||
|
||||
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:
|
||||
include:
|
||||
- os: osx
|
||||
|
62
Assets/JoplinLetter.svg
Normal file
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 |
Binary file not shown.
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 336 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 244 KiB |
Binary file not shown.
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.png
Normal file
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 249 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.psd
Normal file
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.psd
Normal file
Binary file not shown.
26
BUILD.md
26
BUILD.md
@@ -1,15 +1,14 @@
|
||||
[](https://travis-ci.org/laurent22/joplin) [](https://ci.appveyor.com/project/laurent22/joplin)
|
||||
|
||||
# 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
|
||||
|
||||
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
|
||||
|
||||
## Linux and Windows (WSL) dependencies
|
||||
|
||||
@@ -37,12 +36,31 @@ 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`
|
||||
|
||||
For node-gyp to work, you might need to install the `windows-build-tools` using `npm install --global windows-build-tools`.
|
||||
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`.
|
||||
|
||||
If you get the error `libtool: unrecognized option '-static'`, follow the instructions [in this post](https://stackoverflow.com/a/38552393/561309) to use the correct libtool version.
|
||||
|
||||
That will create the executable file in the `dist` directory.
|
||||
|
||||
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.
|
||||
|
||||
The [building\_win32\_tips on this page](./readme/building_win32_tips.md) might be helpful.
|
||||
|
||||
# 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.
|
||||
|
@@ -1,20 +1,28 @@
|
||||
# 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.
|
||||
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. Adding a "+1" comment does nothing.
|
||||
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
|
||||
# Creating a pull request
|
||||
|
||||
If you want to add a new feature, consider asking about it before implementing it to make sure it is within the scope of the project. Of course you are free to create the pull request directly but it is not guaranteed it is going to be accepted.
|
||||
- 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. As a rule of thumb **if your change is likely to involve more than 50 lines of code, you should discuss it in the forum**, just so that you don't spend too much time implementing something that might not be accepted.
|
||||
|
||||
- Bug fixes are always welcome.
|
||||
|
||||
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
|
||||
|
||||
- Only use tabs for indentation, not spaces.
|
||||
- Do not remove or add optional characters from other lines (such as colons or new line characters) as it can make the commit needlessly big, and create conflicts with other changes.
|
||||
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.
|
||||
|
4
CliClient/.gitignore
vendored
4
CliClient/.gitignore
vendored
@@ -13,9 +13,11 @@ tests/fuzzing.*
|
||||
tests/fuzzing -*
|
||||
tests/logs/*
|
||||
tests/cli-integration/
|
||||
tests/tmp/
|
||||
*.mo
|
||||
*.*~
|
||||
tests/sync
|
||||
out.txt
|
||||
linkToLocal.sh
|
||||
yarn-error.log
|
||||
yarn-error.log
|
||||
tests/support/dropbox-auth.txt
|
@@ -1,5 +1,6 @@
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
@@ -9,6 +10,8 @@ const { reducer, defaultState } = require('lib/reducer.js');
|
||||
const { splitCommandString } = require('lib/string-utils.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Entities = require('html-entities').AllHtmlEntities;
|
||||
const htmlentities = (new Entities()).encode;
|
||||
|
||||
const chalk = require('chalk');
|
||||
const tk = require('terminal-kit');
|
||||
@@ -35,38 +38,55 @@ const ConsoleWidget = require('./gui/ConsoleWidget.js');
|
||||
|
||||
class AppGui {
|
||||
|
||||
constructor(app, store) {
|
||||
this.app_ = app;
|
||||
this.store_ = store;
|
||||
constructor(app, store, keymap) {
|
||||
try {
|
||||
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;
|
||||
this.logger_ = new Logger();
|
||||
this.buildUi();
|
||||
// Some keys are directly handled by the tkwidget framework
|
||||
// so they need to be remapped in a different way.
|
||||
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) => {
|
||||
await this.handleModelAction(event.action);
|
||||
});
|
||||
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
|
||||
|
||||
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.lastShortcutKeyTime_ = 0;
|
||||
this.commandCancelCalled_ = false;
|
||||
|
||||
// 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();
|
||||
this.currentShortcutKeys_ = [];
|
||||
this.lastShortcutKeyTime_ = 0;
|
||||
|
||||
// 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() {
|
||||
@@ -105,6 +125,7 @@ class AppGui {
|
||||
buildUi() {
|
||||
this.rootWidget_ = new ReduxRootWidget(this.store_);
|
||||
this.rootWidget_.name = 'root';
|
||||
this.rootWidget_.autoShortcutsEnabled = false;
|
||||
|
||||
const folderList = new FolderListWidget();
|
||||
folderList.style = {
|
||||
@@ -269,155 +290,31 @@ class AppGui {
|
||||
|
||||
addCommandToConsole(cmd) {
|
||||
if (!cmd) return;
|
||||
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
|
||||
if (isConfigPassword) return;
|
||||
this.stdout(chalk.cyan.bold('> ' + cmd));
|
||||
}
|
||||
|
||||
setupShortcuts() {
|
||||
const shortcuts = {};
|
||||
setupKeymap(keymap) {
|
||||
const output = [];
|
||||
|
||||
shortcuts['TAB'] = {
|
||||
friendlyName: 'Tab',
|
||||
description: () => _('Give focus to next pane'),
|
||||
isDocOnly: true,
|
||||
}
|
||||
for (let i = 0; i < keymap.length; i++) {
|
||||
const item = Object.assign({}, keymap[i]);
|
||||
|
||||
shortcuts['SHIFT_TAB'] = {
|
||||
friendlyName: 'Shift+Tab',
|
||||
description: () => _('Give focus to previous pane'),
|
||||
isDocOnly: true,
|
||||
}
|
||||
if (!item.command) throw new Error('Missing command for keymap item: ' + JSON.stringify(item));
|
||||
|
||||
shortcuts[':'] = {
|
||||
description: () => _('Enter command line mode'),
|
||||
action: async () => {
|
||||
const cmd = await this.widget('statusBar').prompt();
|
||||
if (!cmd) return;
|
||||
this.addCommandToConsole(cmd);
|
||||
await this.processCommand(cmd);
|
||||
},
|
||||
};
|
||||
if (!('type' in item)) item.type = 'exec';
|
||||
|
||||
shortcuts['ESC'] = { // Built into terminal-kit inputField
|
||||
description: () => _('Exit command line mode'),
|
||||
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) return;
|
||||
|
||||
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.'));
|
||||
}
|
||||
if (item.command in this.tkWidgetKeys_) {
|
||||
item.type = 'tkwidgets';
|
||||
}
|
||||
};
|
||||
|
||||
shortcuts['BACKSPACE'] = {
|
||||
alias: 'DELETE',
|
||||
};
|
||||
item.canRunAlongOtherCommands = item.type === 'function' && ['toggle_metadata', 'toggle_console'].indexOf(item.command) >= 0;
|
||||
|
||||
shortcuts[' '] = {
|
||||
friendlyName: 'SPACE',
|
||||
description: () => _('Set a to-do as completed / not completed'),
|
||||
action: 'todo toggle $n',
|
||||
output.push(item);
|
||||
}
|
||||
|
||||
shortcuts['tc'] = {
|
||||
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;
|
||||
return output;
|
||||
}
|
||||
|
||||
toggleConsole() {
|
||||
@@ -492,8 +389,16 @@ class AppGui {
|
||||
return this.logger_;
|
||||
}
|
||||
|
||||
shortcuts() {
|
||||
return this.shortcuts_;
|
||||
keymap() {
|
||||
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() {
|
||||
@@ -524,17 +429,77 @@ 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;
|
||||
cmd = cmd.trim();
|
||||
if (!cmd.length) return;
|
||||
|
||||
this.logger().info('Got command: ' + cmd);
|
||||
|
||||
if (cmd === 'q' || cmd === 'wq' || cmd === 'qa') { // Vim bonus
|
||||
await this.app().exit();
|
||||
return;
|
||||
}
|
||||
// this.logger().debug('Got command: ' + cmd);
|
||||
|
||||
try {
|
||||
let note = this.widget('noteList').currentItem;
|
||||
@@ -676,12 +641,27 @@ class AppGui {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (link.type === 'resource') {
|
||||
const resourceId = link.id;
|
||||
let resource = await Resource.load(resourceId);
|
||||
if (!resource) throw new Error('No resource with ID ' + resourceId); // Should be nearly impossible
|
||||
if (resource.mime) response.setHeader('Content-Type', resource.mime);
|
||||
response.write(await Resource.content(resource));
|
||||
if (link.type === 'item') {
|
||||
const itemId = link.id;
|
||||
let item = await BaseItem.loadItemById(itemId);
|
||||
if (!item) throw new Error('No item with ID ' + itemId); // Should be nearly impossible
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -697,7 +677,7 @@ class AppGui {
|
||||
|
||||
if (resourceIdRegex.test(url)) {
|
||||
noteLinks[index] = {
|
||||
type: 'resource',
|
||||
type: 'item',
|
||||
id: url.substr(2),
|
||||
};
|
||||
} else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) {
|
||||
@@ -786,35 +766,34 @@ class AppGui {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
const shortcutKey = this.currentShortcutKeys_.join('');
|
||||
let cmd = shortcutKey in this.shortcuts_ ? this.shortcuts_[shortcutKey] : null;
|
||||
let keymapItem = this.keymapItemByKey(shortcutKey);
|
||||
|
||||
// If this command is an alias to another command, resolve to the actual command
|
||||
if (cmd && cmd.alias) cmd = this.shortcuts_[cmd.alias];
|
||||
|
||||
let processShortcutKeys = !this.app().currentCommand() && cmd;
|
||||
if (cmd && cmd.canRunAlongOtherCommands) processShortcutKeys = true;
|
||||
let processShortcutKeys = !this.app().currentCommand() && keymapItem;
|
||||
if (keymapItem && keymapItem.canRunAlongOtherCommands) processShortcutKeys = true;
|
||||
if (statusBar.promptActive) processShortcutKeys = false;
|
||||
if (cmd && cmd.isDocOnly) processShortcutKeys = false;
|
||||
|
||||
if (processShortcutKeys) {
|
||||
this.logger().info('Shortcut:', shortcutKey, cmd.description());
|
||||
this.logger().debug('Shortcut:', shortcutKey, keymapItem);
|
||||
|
||||
this.currentShortcutKeys_ = [];
|
||||
if (typeof cmd.action === 'function') {
|
||||
await cmd.action();
|
||||
} else if (typeof cmd.action === 'object') {
|
||||
if (cmd.action.type === 'prompt') {
|
||||
let promptOptions = {};
|
||||
if ('cursorPosition' in cmd.action) promptOptions.cursorPosition = cmd.action.cursorPosition;
|
||||
const commandString = await statusBar.prompt(cmd.action.initialText ? cmd.action.initialText : '', null, promptOptions);
|
||||
this.addCommandToConsole(commandString);
|
||||
await this.processCommand(commandString);
|
||||
} else {
|
||||
throw new Error('Unknown command: ' + JSON.stringify(cmd.action));
|
||||
}
|
||||
} else { // String
|
||||
this.stdout(cmd.action);
|
||||
await this.processCommand(cmd.action);
|
||||
|
||||
if (keymapItem.type === 'function') {
|
||||
this.processFunctionCommand(keymapItem.command);
|
||||
} else if (keymapItem.type === 'prompt') {
|
||||
let promptOptions = {};
|
||||
if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition;
|
||||
const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions);
|
||||
this.addCommandToConsole(commandString);
|
||||
await this.processPromptCommand(commandString);
|
||||
} else if (keymapItem.type === 'exec') {
|
||||
this.stdout(keymapItem.command);
|
||||
await this.processPromptCommand(keymapItem.command);
|
||||
} else if (keymapItem.type === 'tkwidgets') {
|
||||
this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]);
|
||||
} else {
|
||||
throw new Error('Unknown command type: ' + JSON.stringify(keymapItem));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||
const { Database } = require('lib/database.js');
|
||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
@@ -20,8 +21,9 @@ const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/loc
|
||||
const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const EventEmitter = require('events');
|
||||
const Cache = require('lib/Cache');
|
||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
@@ -283,7 +285,7 @@ class Application extends BaseApplication {
|
||||
exit: () => {},
|
||||
showModalOverlay: (text) => {},
|
||||
hideModalOverlay: () => {},
|
||||
stdoutMaxWidth: () => { return 78; },
|
||||
stdoutMaxWidth: () => { return 100; },
|
||||
forceRender: () => {},
|
||||
termSaveState: () => {},
|
||||
termRestoreState: (state) => {},
|
||||
@@ -292,7 +294,7 @@ class Application extends BaseApplication {
|
||||
|
||||
async execCommand(argv) {
|
||||
if (!argv.length) return this.execCommand(['help']);
|
||||
reg.logger().info('execCommand()', argv);
|
||||
// reg.logger().debug('execCommand()', argv);
|
||||
const commandName = argv[0];
|
||||
this.activeCommand_ = this.findCommandByName(commandName);
|
||||
|
||||
@@ -312,6 +314,63 @@ class Application extends BaseApplication {
|
||||
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) {
|
||||
argv = await super.start(argv);
|
||||
|
||||
@@ -319,6 +378,8 @@ class Application extends BaseApplication {
|
||||
return this.stdout(object);
|
||||
});
|
||||
|
||||
await WelcomeUtils.install(this.dispatch.bind(this));
|
||||
|
||||
// If we have some arguments left at this point, it's a command
|
||||
// so execute it.
|
||||
if (argv.length) {
|
||||
@@ -330,16 +391,25 @@ class Application extends BaseApplication {
|
||||
await this.execCommand(argv);
|
||||
} catch (error) {
|
||||
if (this.showStackTraces_) {
|
||||
console.info(error);
|
||||
console.error(error);
|
||||
} else {
|
||||
console.info(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await Setting.saveAll();
|
||||
|
||||
// Need to call exit() explicitely, otherwise Node wait for any timeout to complete
|
||||
// https://stackoverflow.com/questions/18050095
|
||||
process.exit(0);
|
||||
} else { // Otherwise open the GUI
|
||||
this.initRedux();
|
||||
|
||||
const keymap = await this.loadKeymaps();
|
||||
|
||||
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_);
|
||||
await this.gui_.start();
|
||||
|
||||
@@ -352,6 +422,10 @@ class Application extends BaseApplication {
|
||||
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
RevisionService.instance().runInBackground();
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
items: tags,
|
||||
|
@@ -36,7 +36,7 @@ async function handleAutocompletionPromise(line) {
|
||||
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 seperated by comma and
|
||||
//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);
|
||||
|
@@ -32,10 +32,6 @@ class BaseCommand {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
|
||||
aliases() {
|
||||
return [];
|
||||
}
|
||||
|
||||
options() {
|
||||
return [];
|
||||
}
|
||||
|
@@ -102,7 +102,7 @@ function getFooter() {
|
||||
|
||||
output.push('WEBSITE');
|
||||
output.push('');
|
||||
output.push(INDENT + 'http://joplin.cozic.net');
|
||||
output.push(INDENT + 'https://joplinapp.org');
|
||||
|
||||
output.push('');
|
||||
|
||||
|
299
CliClient/app/command-apidoc.js
Normal file
299
CliClient/app/command-apidoc.js
Normal file
@@ -0,0 +1,299 @@
|
||||
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 BaseModel = require('lib/BaseModel');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { toTitleCase } = require('lib/string-utils.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const markdownUtils = require('lib/markdownUtils');
|
||||
const { Database } = require('lib/database.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'apidoc';
|
||||
}
|
||||
|
||||
description() {
|
||||
return 'Build the API doc';
|
||||
}
|
||||
|
||||
createPropertiesTable(tableFields) {
|
||||
const headers = [
|
||||
{ name: 'name', label: 'Name' },
|
||||
{ name: 'type', label: 'Type', filter: (value) => {
|
||||
return Database.enumName('fieldType', value);
|
||||
}},
|
||||
{ name: 'description', label: 'Description' },
|
||||
];
|
||||
|
||||
return markdownUtils.createMarkdownTable(headers, tableFields);
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const models = [
|
||||
{
|
||||
type: BaseModel.TYPE_NOTE,
|
||||
},
|
||||
{
|
||||
type: BaseModel.TYPE_FOLDER,
|
||||
},
|
||||
{
|
||||
type: BaseModel.TYPE_RESOURCE,
|
||||
},
|
||||
{
|
||||
type: BaseModel.TYPE_TAG,
|
||||
},
|
||||
];
|
||||
|
||||
const lines = [];
|
||||
|
||||
lines.push('# Joplin API');
|
||||
lines.push('');
|
||||
|
||||
lines.push('When the Web Clipper service is enabled, Joplin exposes a [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) which allows third-party applications to access Joplin\'s data and to create, modify or delete notes, notebooks, resources or tags.');
|
||||
lines.push('');
|
||||
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
|
||||
lines.push('');
|
||||
lines.push('```javascript');
|
||||
lines.push('let port = null;');
|
||||
lines.push('for (let portToTest = 41184; portToTest <= 41194; portToTest++) {');
|
||||
lines.push(' const result = pingPort(portToTest); // Call GET /ping');
|
||||
lines.push(' if (result == \'JoplinClipperServer\') {');
|
||||
lines.push(' port = portToTest; // Found the port');
|
||||
lines.push(' break;');
|
||||
lines.push(' }');
|
||||
lines.push('}');
|
||||
lines.push('```');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# Authorisation')
|
||||
lines.push('');
|
||||
lines.push('To prevent unauthorised applications from accessing the API, the calls must be authentified. To do so, you must provide a token as a query parameter for each API call. You can get this token from the Joplin desktop application, on the Web Clipper Options screen.');
|
||||
lines.push('');
|
||||
lines.push('This would be an example of valid cURL call using a token:');
|
||||
lines.push('');
|
||||
lines.push('\tcurl http://localhost:41184/notes?token=ABCD123ABCD123ABCD123ABCD123ABCD123');
|
||||
lines.push('');
|
||||
lines.push('In the documentation below, the token will not be specified every time however you will need to include it.');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# Using the API');
|
||||
lines.push('');
|
||||
lines.push('All the calls, unless noted otherwise, receives and send **JSON data**. For example to create a new note:');
|
||||
lines.push('');
|
||||
lines.push('\tcurl --data \'{ "title": "My note", "body": "Some note in **Markdown**"}\' http://localhost:41184/notes');
|
||||
lines.push('');
|
||||
lines.push('In the documentation below, the calls may include special parameters such as :id or :note_id. You would replace this with the item ID or note ID.');
|
||||
lines.push('');
|
||||
lines.push('For example, for the endpoint `DELETE /tags/:id/notes/:note_id`, to remove the tag with ID "ABCD1234" from the note with ID "EFGH789", you would run for example:');
|
||||
lines.push('');
|
||||
lines.push('\tcurl -X DELETE http://localhost:41184/tags/ABCD1234/notes/EFGH789');
|
||||
lines.push('');
|
||||
lines.push('The four verbs supported by the API are the following ones:');
|
||||
lines.push('');
|
||||
lines.push('* **GET**: To retrieve items (notes, notebooks, etc.).');
|
||||
lines.push('* **POST**: To create new items. In general most item properties are optional. If you omit any, a default value will be used.');
|
||||
lines.push('* **PUT**: To update an item. Note in a REST API, traditionally PUT is used to completely replace an item, however in this API it will only replace the properties that are provided. For example if you PUT {"title": "my new title"}, only the "title" property will be changed. The other properties will be left untouched (they won\'t be cleared nor changed).');
|
||||
lines.push('* **DELETE**: To delete items.');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# Filtering data');
|
||||
lines.push('');
|
||||
lines.push('You can change the fields that will be returned by the API using the `fields=` query parameter, which takes a list of comma separated fields. For example, to get the longitude and latitude of a note, use this:');
|
||||
lines.push('');
|
||||
lines.push('\tcurl http://localhost:41184/notes/ABCD123?fields=longitude,latitude');
|
||||
lines.push('');
|
||||
lines.push('To get the IDs only of all the tags:');
|
||||
lines.push('');
|
||||
lines.push('\tcurl http://localhost:41184/tags?fields=id');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# Error handling');
|
||||
lines.push('');
|
||||
lines.push('In case of an error, an HTTP status code >= 400 will be returned along with a JSON object that provides more info about the error. The JSON object is in the format `{ "error": "description of error" }`.');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# About the property types');
|
||||
lines.push('');
|
||||
lines.push('* Text is UTF-8.');
|
||||
lines.push('* All date/time are Unix timestamps in milliseconds.');
|
||||
lines.push('* Booleans are integer values 0 or 1.');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# Testing if the service is available');
|
||||
lines.push('');
|
||||
lines.push('Call **GET /ping** to check if the service is available. It should return "JoplinClipperServer" if it works.');
|
||||
lines.push('');
|
||||
|
||||
lines.push('# Searching');
|
||||
lines.push('');
|
||||
lines.push('Call **GET /search?query=YOUR_QUERY** to search for notes. This end-point supports the `field` parameter which is recommended to use so that you only get the data that you need. The query syntax is as described in the main documentation: https://joplinapp.org/#searching');
|
||||
lines.push('');
|
||||
|
||||
for (let i = 0; i < models.length; i++) {
|
||||
const model = models[i];
|
||||
const ModelClass = BaseItem.getClassByItemType(model.type);
|
||||
const tableName = ModelClass.tableName();
|
||||
let tableFields = reg.db().tableFields(tableName, { includeDescription: true });
|
||||
const singular = tableName.substr(0, tableName.length - 1);
|
||||
|
||||
if (model.type === BaseModel.TYPE_NOTE) {
|
||||
tableFields = tableFields.slice();
|
||||
tableFields.push({
|
||||
name: 'body_html',
|
||||
type: Database.enumId('fieldType', 'text'),
|
||||
description: 'Note body, in HTML format',
|
||||
});
|
||||
tableFields.push({
|
||||
name: 'base_url',
|
||||
type: Database.enumId('fieldType', 'text'),
|
||||
description: 'If `body_html` is provided and contains relative URLs, provide the `base_url` parameter too so that all the URLs can be converted to absolute ones. The base URL is basically where the HTML was fetched from, minus the query (everything after the \'?\'). For example if the original page was `https://stackoverflow.com/search?q=%5Bjava%5D+test`, the base URL is `https://stackoverflow.com/search`.',
|
||||
});
|
||||
tableFields.push({
|
||||
name: 'image_data_url',
|
||||
type: Database.enumId('fieldType', 'text'),
|
||||
description: 'An image to attach to the note, in [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.',
|
||||
});
|
||||
tableFields.push({
|
||||
name: 'crop_rect',
|
||||
type: Database.enumId('fieldType', 'text'),
|
||||
description: 'If an image is provided, you can also specify an optional rectangle that will be used to crop the image. In format `{ x: x, y: y, width: width, height: height }`',
|
||||
});
|
||||
// tableFields.push({
|
||||
// name: 'tags',
|
||||
// type: Database.enumId('fieldType', 'text'),
|
||||
// description: 'Comma-separated list of tags. eg. `tag1,tag2`.',
|
||||
// });
|
||||
}
|
||||
|
||||
lines.push('# ' + toTitleCase(tableName));
|
||||
lines.push('');
|
||||
|
||||
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||
lines.push('This is actually a notebook. Internally notebooks are called "folders".');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('## Properties');
|
||||
lines.push('');
|
||||
lines.push(this.createPropertiesTable(tableFields));
|
||||
lines.push('');
|
||||
|
||||
lines.push('## GET /' + tableName);
|
||||
lines.push('');
|
||||
lines.push('Gets all ' + tableName);
|
||||
lines.push('');
|
||||
|
||||
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||
lines.push('The folders are returned as a tree. The sub-notebooks of a notebook, if any, are under the `children` key.');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('## GET /' + tableName + '/:id');
|
||||
lines.push('');
|
||||
lines.push('Gets ' + singular + ' with ID :id');
|
||||
lines.push('');
|
||||
|
||||
if (model.type === BaseModel.TYPE_TAG) {
|
||||
lines.push('## GET /tags/:id/notes');
|
||||
lines.push('');
|
||||
lines.push('Gets all the notes with this tag.');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (model.type === BaseModel.TYPE_NOTE) {
|
||||
lines.push('## GET /notes/:id/tags');
|
||||
lines.push('');
|
||||
lines.push('Gets all the tags attached to this note.');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (model.type === BaseModel.TYPE_FOLDER) {
|
||||
lines.push('## GET /folders/:id/notes');
|
||||
lines.push('');
|
||||
lines.push('Gets all the notes inside this folder.');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (model.type === BaseModel.TYPE_RESOURCE) {
|
||||
lines.push('## GET /resources/:id/file');
|
||||
lines.push('');
|
||||
lines.push('Gets the actual file associated with this resource.');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('## POST /' + tableName);
|
||||
lines.push('');
|
||||
lines.push('Creates a new ' + singular);
|
||||
lines.push('');
|
||||
|
||||
if (model.type === BaseModel.TYPE_RESOURCE) {
|
||||
lines.push('Creating a new resource is special because you also need to upload the file. Unlike other API calls, this one must have the "multipart/form-data" Content-Type. The file data must be passed to the "data" form field, and the other properties to the "props" form field. An example of a valid call with cURL would be:');
|
||||
lines.push('');
|
||||
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
|
||||
lines.push('');
|
||||
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (model.type === BaseModel.TYPE_TAG) {
|
||||
lines.push('## POST /tags/:id/notes');
|
||||
lines.push('');
|
||||
lines.push('Post a note to this endpoint to add the tag to the note. The note data must at least contain an ID property (all other properties will be ignored).');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (model.type === BaseModel.TYPE_NOTE) {
|
||||
lines.push('You can either specify the note body as Markdown by setting the `body` parameter, or in HTML by setting the `body_html`.');
|
||||
lines.push('');
|
||||
lines.push('Examples:');
|
||||
lines.push('');
|
||||
lines.push('* Create a note from some Markdown text');
|
||||
lines.push('');
|
||||
lines.push(' curl --data \'{ "title": "My note", "body": "Some note in **Markdown**"}\' http://127.0.0.1:41184/notes');
|
||||
lines.push('');
|
||||
lines.push('* Create a note from some HTML');
|
||||
lines.push('');
|
||||
lines.push(' curl --data \'{ "title": "My note", "body_html": "Some note in <b>HTML</b>"}\' http://127.0.0.1:41184/notes');
|
||||
lines.push('');
|
||||
lines.push('* Create a note and attach an image to it:');
|
||||
lines.push('');
|
||||
lines.push(' curl --data \'{ "title": "Image test", "body": "Here is Joplin icon:", "image_data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII="}\' http://127.0.0.1:41184/notes');
|
||||
lines.push('');
|
||||
lines.push('### Creating a note with a specific ID');
|
||||
lines.push('');
|
||||
lines.push('When a new note is created, it is automatically assigned a new unique ID so **normally you do not need to set the ID**. However, if for some reason you want to set it, you can supply it as the `id` property. It needs to be a 32 characters long hexadecimal string. **Make sure it is unique**, for example by generating it using whatever GUID function is available in your programming language.');
|
||||
lines.push('');
|
||||
lines.push(' curl --data \'{ "id": "00a87474082744c1a8515da6aa5792d2", "title": "My note with custom ID"}\' http://127.0.0.1:41184/notes');
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('## PUT /' + tableName + '/:id');
|
||||
lines.push('');
|
||||
lines.push('Sets the properties of the ' + singular + ' with ID :id');
|
||||
lines.push('');
|
||||
|
||||
lines.push('## DELETE /' + tableName + '/:id');
|
||||
lines.push('');
|
||||
lines.push('Deletes the ' + singular + ' with ID :id');
|
||||
lines.push('');
|
||||
|
||||
if (model.type === BaseModel.TYPE_TAG) {
|
||||
lines.push('## DELETE /tags/:id/notes/:note_id');
|
||||
lines.push('');
|
||||
lines.push('Remove the tag from the note.');
|
||||
lines.push('');
|
||||
}
|
||||
}
|
||||
|
||||
this.stdout(lines.join('\n'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@@ -26,7 +26,7 @@ class Command extends BaseCommand {
|
||||
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 (md.secure && value) value = '********';
|
||||
|
||||
if (Setting.isEnum(name)) {
|
||||
return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));
|
||||
|
@@ -6,6 +6,10 @@ 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');
|
||||
const { shim } = require('lib/shim');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
const imageType = require('image-type');
|
||||
const readChunk = require('read-chunk');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -14,7 +18,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.');
|
||||
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file` and `target-status`.');
|
||||
}
|
||||
|
||||
options() {
|
||||
@@ -22,6 +26,7 @@ class Command extends BaseCommand {
|
||||
// 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'],
|
||||
['-o, --output <directory>', 'Output directory'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -30,6 +35,18 @@ class Command extends BaseCommand {
|
||||
|
||||
const options = args.options;
|
||||
|
||||
const askForMasterKey = async (error) => {
|
||||
const masterKeyId = error.masterKeyId;
|
||||
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||
if (!password) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return false;
|
||||
}
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.command === 'enable') {
|
||||
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||
if (!password) {
|
||||
@@ -47,30 +64,29 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
if (args.command === 'decrypt') {
|
||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||
if (args.path) {
|
||||
const plainText = await EncryptionService.instance().decryptString(args.path);
|
||||
this.stdout(plainText);
|
||||
} else {
|
||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
await DecryptionWorker.instance().start();
|
||||
break;
|
||||
} 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;
|
||||
while (true) {
|
||||
try {
|
||||
await DecryptionWorker.instance().start();
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.code === 'masterKeyNotLoaded') {
|
||||
const ok = await askForMasterKey(error);
|
||||
if (!ok) return;
|
||||
continue;
|
||||
}
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
continue;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.stdout(_('Completed decryption.'));
|
||||
this.stdout(_('Completed decryption.'));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -80,6 +96,36 @@ class Command extends BaseCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.command === 'decrypt-file') {
|
||||
while (true) {
|
||||
try {
|
||||
const outputDir = options.output ? options.output : require('os').tmpdir();
|
||||
let outFile = outputDir + '/' + pathUtils.filename(args.path) + '.' + Date.now() + '.bin';
|
||||
await EncryptionService.instance().decryptFile(args.path, outFile);
|
||||
const buffer = await readChunk(outFile, 0, 64);
|
||||
const detectedType = imageType(buffer);
|
||||
|
||||
if (detectedType) {
|
||||
const newOutFile = outFile + '.' + detectedType.ext;
|
||||
await shim.fsDriver().move(outFile, newOutFile);
|
||||
outFile = newOutFile;
|
||||
}
|
||||
|
||||
this.stdout(outFile);
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.code === 'masterKeyNotLoaded') {
|
||||
const ok = await askForMasterKey(error);
|
||||
if (!ok) return;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.command === 'target-status') {
|
||||
const fs = require('fs-extra');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const { Exporter } = require('lib/services/exporter.js');
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
@@ -10,15 +10,21 @@ const fs = require('fs-extra');
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'export <directory>';
|
||||
return 'export <path>';
|
||||
}
|
||||
|
||||
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() {
|
||||
const service = new InteropService();
|
||||
const formats = service.modules()
|
||||
.filter(m => m.type === 'exporter')
|
||||
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
|
||||
|
||||
return [
|
||||
['--format <format>', _('Destination format: %s', formats.join(', '))],
|
||||
['--note <note>', _('Exports only the given note.')],
|
||||
['--notebook <notebook>', _('Exports only the given notebook.')],
|
||||
];
|
||||
@@ -26,13 +32,9 @@ class Command extends BaseCommand {
|
||||
|
||||
async action(args) {
|
||||
let exportOptions = {};
|
||||
exportOptions.destDir = args.directory;
|
||||
exportOptions.writeFile = (filePath, data) => {
|
||||
return fs.writeFile(filePath, data);
|
||||
};
|
||||
exportOptions.copyFile = (source, dest) => {
|
||||
return fs.copy(source, dest, { overwrite: true });
|
||||
};
|
||||
exportOptions.path = args.path;
|
||||
|
||||
exportOptions.format = args.options.format ? args.options.format : 'jex';
|
||||
|
||||
if (args.options.note) {
|
||||
|
||||
@@ -48,10 +50,10 @@ class Command extends BaseCommand {
|
||||
|
||||
}
|
||||
|
||||
const exporter = new Exporter();
|
||||
const result = await exporter.export(exportOptions);
|
||||
const service = new InteropService();
|
||||
const result = await service.export(exportOptions);
|
||||
|
||||
reg.logger().info('Export result: ', result);
|
||||
result.warnings.map((w) => this.stdout(w));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -36,21 +36,22 @@ class Command extends BaseCommand {
|
||||
async action(args) {
|
||||
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://joplinapp.org/terminal/#shortcuts'));
|
||||
this.stdout('');
|
||||
|
||||
if (app().gui().isDummy()) {
|
||||
throw new Error(_('Shortcuts are not available in CLI mode.'));
|
||||
}
|
||||
|
||||
const shortcuts = app().gui().shortcuts();
|
||||
const keymap = app().gui().keymap();
|
||||
|
||||
let rows = [];
|
||||
|
||||
for (let n in shortcuts) {
|
||||
if (!shortcuts.hasOwnProperty(n)) continue;
|
||||
const shortcut = shortcuts[n];
|
||||
if (!shortcut.description) continue;
|
||||
n = shortcut.friendlyName ? shortcut.friendlyName : n;
|
||||
rows.push([n, shortcut.description()]);
|
||||
for (let i = 0; i < keymap.length; i++) {
|
||||
const item = keymap[i];
|
||||
const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k);
|
||||
rows.push([keys.join(', '), item.command]);
|
||||
}
|
||||
|
||||
cliUtils.printArray(this.stdout.bind(this), rows);
|
||||
@@ -71,14 +72,14 @@ class Command extends BaseCommand {
|
||||
this.stdout('');
|
||||
this.stdout(commandNames.join(', '));
|
||||
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(_('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(_('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 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();
|
||||
|
@@ -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
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;
|
@@ -22,7 +22,7 @@ class Command extends BaseCommand {
|
||||
enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
options() {
|
||||
return [
|
||||
['-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(time.unixMsToLocalDateTime(item.user_updated_time));
|
||||
row.push(time.formatMsToLocal(item.user_updated_time));
|
||||
}
|
||||
|
||||
let title = item.title;
|
||||
@@ -123,4 +123,4 @@ class Command extends BaseCommand {
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
||||
module.exports = Command;
|
||||
|
@@ -14,10 +14,6 @@ class Command extends BaseCommand {
|
||||
return _('Creates a new notebook.');
|
||||
}
|
||||
|
||||
aliases() {
|
||||
return ['mkdir'];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||
app().switchCurrentFolder(folder);
|
||||
|
@@ -29,7 +29,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
|
||||
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
|
||||
const ok = force ? true : await this.prompt(_('Delete notebook? All notes within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' });
|
||||
const ok = force ? true : await this.prompt(_('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' });
|
||||
if (!ok) return;
|
||||
|
||||
await Folder.delete(folder.id);
|
||||
|
@@ -49,35 +49,6 @@ class Command extends BaseCommand {
|
||||
type: 'SEARCH_SELECT',
|
||||
id: searchId,
|
||||
});
|
||||
|
||||
// let fields = Note.previewFields();
|
||||
// fields.push('body');
|
||||
// const notes = await Note.previews(folder ? folder.id : null, {
|
||||
// fields: fields,
|
||||
// anywherePattern: '*' + pattern + '*',
|
||||
// });
|
||||
|
||||
// const fragmentLength = 50;
|
||||
|
||||
// let parents = {};
|
||||
|
||||
// for (let i = 0; i < notes.length; i++) {
|
||||
// const note = notes[i];
|
||||
// const parent = parents[note.parent_id] ? parents[note.parent_id] : await Folder.load(note.parent_id);
|
||||
// parents[note.parent_id] = parent;
|
||||
|
||||
// const idx = note.body.indexOf(pattern);
|
||||
// let line = '';
|
||||
// if (idx >= 0) {
|
||||
// let fragment = note.body.substr(Math.max(0, idx - fragmentLength / 2), fragmentLength);
|
||||
// fragment = fragment.replace(/\n/g, ' ');
|
||||
// line = sprintf('%s: %s / %s: %s', BaseModel.shortId(note.id), parent.title, note.title, fragment);
|
||||
// } else {
|
||||
// line = sprintf('%s: %s / %s', BaseModel.shortId(note.id), parent.title, note.title);
|
||||
// }
|
||||
|
||||
// this.stdout(line);
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,13 +4,13 @@ const { _ } = require('lib/locale.js');
|
||||
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const BaseItem = require('lib/models/BaseItem.js');
|
||||
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const md5 = require('md5');
|
||||
const locker = require('proper-lockfile');
|
||||
const fs = require('fs-extra');
|
||||
const osTmpdir = require('os-tmpdir');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
@@ -79,10 +79,26 @@ class Command extends BaseCommand {
|
||||
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.', syncTarget.label()));
|
||||
this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTargetMd.label));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -101,7 +117,7 @@ class Command extends BaseCommand {
|
||||
this.releaseLockFn_ = null;
|
||||
|
||||
// Lock is unique per profile/database
|
||||
const lockFilePath = osTmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
||||
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
|
||||
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
|
||||
|
||||
try {
|
||||
@@ -131,7 +147,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||
|
||||
if (!syncTarget.isAuthenticated()) {
|
||||
if (!await syncTarget.isAuthenticated()) {
|
||||
app().gui().showConsole();
|
||||
app().gui().maximizeConsole();
|
||||
|
||||
@@ -175,6 +191,15 @@ class Command extends BaseCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// When using the tool in command line mode, the ResourceFetcher service is
|
||||
// not going to be running in the background, so the resources need to be
|
||||
// explicitely downloaded below.
|
||||
if (!app().hasGui()) {
|
||||
this.stdout(_('Downloading resources...'));
|
||||
await ResourceFetcher.instance().fetchAll();
|
||||
await ResourceFetcher.instance().waitForAllFinished();
|
||||
}
|
||||
|
||||
await app().refreshCurrentFolder();
|
||||
} catch (error) {
|
||||
cleanUp();
|
||||
@@ -198,7 +223,7 @@ class Command extends BaseCommand {
|
||||
|
||||
const syncTarget = reg.syncTarget(syncTargetId);
|
||||
|
||||
if (syncTarget.isAuthenticated()) {
|
||||
if (await syncTarget.isAuthenticated()) {
|
||||
const sync = await syncTarget.synchronizer();
|
||||
if (sync) await sync.cancel();
|
||||
} else {
|
||||
|
@@ -3,6 +3,7 @@ const { app } = require('./app.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
@@ -11,11 +12,19 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
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) {
|
||||
let tag = null;
|
||||
let options = args.options;
|
||||
|
||||
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
||||
let notes = [];
|
||||
if (args.note) {
|
||||
@@ -41,7 +50,28 @@ class Command extends BaseCommand {
|
||||
} else if (command == 'list') {
|
||||
if (tag) {
|
||||
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 {
|
||||
let tags = await Tag.all();
|
||||
tags.map((tag) => { this.stdout(tag.title); });
|
||||
|
@@ -18,26 +18,36 @@ class FolderListWidget extends ListWidget {
|
||||
this.notesParentType_ = 'Folder';
|
||||
this.updateIndexFromSelectedFolderId_ = false;
|
||||
this.updateItems_ = false;
|
||||
this.trimItemTitle = false;
|
||||
|
||||
this.itemRenderer = (item) => {
|
||||
let output = [];
|
||||
if (item === '-') {
|
||||
output.push('-'.repeat(this.innerWidth));
|
||||
} else if (item.type_ === Folder.modelType()) {
|
||||
output.push(Folder.displayTitle(item));
|
||||
output.push(' '.repeat(this.folderDepth(this.folders, item.id)) + Folder.displayTitle(item));
|
||||
} else if (item.type_ === Tag.modelType()) {
|
||||
output.push('[' + Folder.displayTitle(item) + ']');
|
||||
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
|
||||
output.push(_('Search:'));
|
||||
output.push(item.title);
|
||||
}
|
||||
|
||||
// if (item && item.id) output.push(item.id.substr(0, 5));
|
||||
}
|
||||
|
||||
return output.join(' ');
|
||||
};
|
||||
}
|
||||
|
||||
folderDepth(folders, folderId) {
|
||||
let output = 0;
|
||||
while (true) {
|
||||
const folder = BaseModel.byId(folders, folderId);
|
||||
if (!folder.parent_id) return output;
|
||||
output++;
|
||||
folderId = folder.parent_id;
|
||||
}
|
||||
throw new Error('unreachable');
|
||||
}
|
||||
|
||||
get selectedFolderId() {
|
||||
return this.selectedFolderId_;
|
||||
}
|
||||
@@ -73,7 +83,6 @@ class FolderListWidget extends ListWidget {
|
||||
}
|
||||
|
||||
set notesParentType(v) {
|
||||
//if (this.notesParentType_ === v) return;
|
||||
this.notesParentType_ = v;
|
||||
this.updateIndexFromSelectedItemId()
|
||||
this.invalidate();
|
||||
@@ -111,6 +120,14 @@ class FolderListWidget extends ListWidget {
|
||||
this.updateIndexFromSelectedItemId()
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
folderHasChildren_(folders, folderId) {
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
let folder = folders[i];
|
||||
if (folder.parent_id === folderId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.updateItems_) {
|
||||
@@ -118,7 +135,19 @@ class FolderListWidget extends ListWidget {
|
||||
const wasSelectedItemId = this.selectedJoplinItemId;
|
||||
const previousParentType = this.notesParentType;
|
||||
|
||||
let newItems = this.folders.slice();
|
||||
let newItems = [];
|
||||
const orderFolders = (parentId) => {
|
||||
for (let i = 0; i < this.folders.length; i++) {
|
||||
const f = this.folders[i];
|
||||
const folderParentId = f.parent_id ? f.parent_id : '';
|
||||
if (folderParentId === parentId) {
|
||||
newItems.push(f);
|
||||
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orderFolders('');
|
||||
|
||||
if (this.tags.length) {
|
||||
if (newItems.length) newItems.push('-');
|
||||
|
@@ -133,7 +133,8 @@ class StatusBarWidget extends BaseWidget {
|
||||
resolveResult = input ? input.trim() : input;
|
||||
// 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.
|
||||
if (!isSecurePrompt && input && input.length > 1) this.history_.push(input);
|
||||
const isConfigPassword = input.indexOf('config ') >= 0 && input.indexOf('password') >= 0;
|
||||
if (!isSecurePrompt && input && input.length > 1 && !isConfigPassword) this.history_.push(input);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -53,9 +53,8 @@ function renderCommandHelp(cmd, width = null) {
|
||||
desc.push(label);
|
||||
}
|
||||
|
||||
if (md.description) {
|
||||
desc.push(md.description());
|
||||
}
|
||||
const description = Setting.keyDescription(md.key, 'cli');
|
||||
if (description) desc.push(description);
|
||||
|
||||
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)')));
|
||||
|
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Use njstrace to find out what Node.js might be spending time on
|
||||
// var njstrace = require('njstrace').inject();
|
||||
|
||||
// Make it possible to require("/lib/...") without specifying full path
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
@@ -19,6 +22,7 @@ const Tag = require('lib/models/Tag.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const MasterKey = require('lib/models/MasterKey');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const { Logger } = require('lib/logger.js');
|
||||
const { FsDriverNode } = require('lib/fs-driver-node.js');
|
||||
const { shimInit } = require('lib/shim-init-node.js');
|
||||
@@ -40,6 +44,7 @@ BaseItem.loadClass('Resource', Resource);
|
||||
BaseItem.loadClass('Tag', Tag);
|
||||
BaseItem.loadClass('NoteTag', NoteTag);
|
||||
BaseItem.loadClass('MasterKey', MasterKey);
|
||||
BaseItem.loadClass('Revision', Revision);
|
||||
|
||||
Setting.setConstant('appId', 'net.cozic.joplin-cli');
|
||||
Setting.setConstant('appType', 'cli');
|
||||
@@ -66,54 +71,14 @@ process.stdout.on('error', function( err ) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// async function main() {
|
||||
// const WebDavApi = require('lib/WebDavApi');
|
||||
// const api = new WebDavApi('http://nextcloud.local/remote.php/dav/files/admin/Joplin', { username: 'admin', password: '1234567' });
|
||||
// const { FileApiDriverWebDav } = new require('lib/file-api-driver-webdav');
|
||||
// const driver = new FileApiDriverWebDav(api);
|
||||
|
||||
// const stat = await driver.stat('');
|
||||
// console.info(stat);
|
||||
|
||||
// // const stat = await driver.stat('testing.txt');
|
||||
// // console.info(stat);
|
||||
|
||||
|
||||
// // const content = await driver.get('testing.txta');
|
||||
// // console.info(content);
|
||||
|
||||
// // const content = await driver.get('testing.txta', { target: 'file', path: '/var/www/joplin/CliClient/testing-file.txt' });
|
||||
// // console.info(content);
|
||||
|
||||
// // const content = await driver.mkdir('newdir5');
|
||||
// // console.info(content);
|
||||
|
||||
// //await driver.put('myfile4.md', 'this is my content');
|
||||
|
||||
// // await driver.put('testimg.jpg', null, { source: 'file', path: '/mnt/d/test.jpg' });
|
||||
|
||||
// // await driver.delete('myfile4.md');
|
||||
|
||||
// // const deltaResult = await driver.delta('', {
|
||||
// // allItemIdsHandler: () => { return []; }
|
||||
// // });
|
||||
// // console.info(deltaResult);
|
||||
// }
|
||||
|
||||
// main().catch((error) => { console.error(error); });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
application.start(process.argv).catch((error) => {
|
||||
console.error(_('Fatal error:'));
|
||||
console.error(error);
|
||||
if (error.code == 'flagError') {
|
||||
console.error(error.message);
|
||||
console.error(_('Type `joplin help` for usage information.'));
|
||||
} else {
|
||||
console.error(_('Fatal error:'));
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
1949
CliClient/locales/ar.po
Normal file
1949
CliClient/locales/ar.po
Normal file
File diff suppressed because it is too large
Load Diff
2003
CliClient/locales/ca.po
Normal file
2003
CliClient/locales/ca.po
Normal file
File diff suppressed because it is too large
Load Diff
1979
CliClient/locales/cs_CZ.po
Normal file
1979
CliClient/locales/cs_CZ.po
Normal file
File diff suppressed because it is too large
Load Diff
1988
CliClient/locales/da_DK.po
Normal file
1988
CliClient/locales/da_DK.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1839
CliClient/locales/en_US.po
Normal file
1839
CliClient/locales/en_US.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2067
CliClient/locales/eu.po
Normal file
2067
CliClient/locales/eu.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1978
CliClient/locales/gl_ES.po
Normal file
1978
CliClient/locales/gl_ES.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1969
CliClient/locales/ko.po
Normal file
1969
CliClient/locales/ko.po
Normal file
File diff suppressed because it is too large
Load Diff
1972
CliClient/locales/nb_NO.po
Normal file
1972
CliClient/locales/nb_NO.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2007
CliClient/locales/nl_NL.po
Normal file
2007
CliClient/locales/nl_NL.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1863
CliClient/locales/ro.po
Normal file
1863
CliClient/locales/ro.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1996
CliClient/locales/sl_SI.po
Normal file
1996
CliClient/locales/sl_SI.po
Normal file
File diff suppressed because it is too large
Load Diff
2008
CliClient/locales/sv.po
Normal file
2008
CliClient/locales/sv.po
Normal file
File diff suppressed because it is too large
Load Diff
1961
CliClient/locales/tr_TR.po
Normal file
1961
CliClient/locales/tr_TR.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1925
CliClient/locales/zh_TW.po
Normal file
1925
CliClient/locales/zh_TW.po
Normal file
File diff suppressed because it is too large
Load Diff
3118
CliClient/package-lock.json
generated
3118
CliClient/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,11 +15,12 @@
|
||||
"years": [
|
||||
2016,
|
||||
2017,
|
||||
2018
|
||||
2018,
|
||||
2019
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "0.10.93",
|
||||
"version": "1.0.128",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
@@ -28,38 +29,52 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"app-module-path": "^2.2.0",
|
||||
"async-mutex": "^0.1.3",
|
||||
"base-64": "^0.1.0",
|
||||
"compare-version": "^0.1.2",
|
||||
"diacritics": "^1.3.0",
|
||||
"diff-match-patch": "^1.0.4",
|
||||
"es6-promise-pool": "^2.5.0",
|
||||
"file-uri-to-path": "^1.0.0",
|
||||
"follow-redirects": "^1.2.4",
|
||||
"form-data": "^2.1.4",
|
||||
"fs-extra": "^5.0.0",
|
||||
"html-entities": "^1.2.1",
|
||||
"html-minifier": "^3.5.15",
|
||||
"image-data-uri": "^2.0.0",
|
||||
"image-type": "^3.0.0",
|
||||
"joplin-turndown": "^4.0.11",
|
||||
"joplin-turndown-plugin-gfm": "^1.0.8",
|
||||
"jssha": "^2.3.0",
|
||||
"levenshtein": "^1.0.5",
|
||||
"lodash": "^4.17.4",
|
||||
"markdown-it": "^8.4.2",
|
||||
"md5": "^2.2.1",
|
||||
"mime": "^2.0.3",
|
||||
"moment": "^2.18.1",
|
||||
"node-emoji": "^1.8.1",
|
||||
"node-fetch": "^1.7.1",
|
||||
"node-persist": "^2.1.0",
|
||||
"os-tmpdir": "^1.0.2",
|
||||
"promise": "^7.1.1",
|
||||
"proper-lockfile": "^2.0.1",
|
||||
"query-string": "4.3.4",
|
||||
"read-chunk": "^2.1.0",
|
||||
"redux": "^3.7.2",
|
||||
"sax": "^1.2.2",
|
||||
"server-destroy": "^1.0.1",
|
||||
"sharp": "^0.18.4",
|
||||
"sharp": "^0.22.1",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"sqlite3": "^3.1.8",
|
||||
"sqlite3": "^4.0.7",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"syswide-cas": "^5.2.0",
|
||||
"tar": "^4.4.0",
|
||||
"tcp-port-used": "^0.1.2",
|
||||
"tkwidgets": "^0.5.21",
|
||||
"tkwidgets": "^0.5.26",
|
||||
"url-parse": "^1.2.0",
|
||||
"uuid": "^3.0.1",
|
||||
"valid-url": "^1.0.9",
|
||||
"word-wrap": "^1.2.3",
|
||||
"xml2js": "^0.4.19",
|
||||
"yargs-parser": "^7.0.0"
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
START_DIR="$(pwd)"
|
||||
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
BUILD_DIR="$ROOT_DIR/tests-build"
|
||||
TEST_FILE="$1"
|
||||
@@ -8,8 +9,37 @@ rsync -a "$ROOT_DIR/../ReactNativeClient/lib/" "$BUILD_DIR/lib/"
|
||||
rsync -a "$ROOT_DIR/build/locales/" "$BUILD_DIR/locales/"
|
||||
mkdir -p "$BUILD_DIR/data"
|
||||
|
||||
if [[ $TEST_FILE == "" ]]; then
|
||||
(cd "$ROOT_DIR" && npm test tests-build/synchronizer.js tests-build/encryption.js tests-build/ArrayUtils.js tests-build/models_Setting.js)
|
||||
else
|
||||
if [[ $TEST_FILE != "" ]]; then
|
||||
(cd "$ROOT_DIR" && npm test tests-build/$TEST_FILE.js)
|
||||
fi
|
||||
exit
|
||||
fi
|
||||
|
||||
function finish {
|
||||
cd "$START_DIR"
|
||||
}
|
||||
|
||||
trap finish EXIT
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
npm test tests-build/ArrayUtils.js
|
||||
npm test tests-build/encryption.js
|
||||
npm test tests-build/EnexToMd.js
|
||||
npm test tests-build/HtmlToMd.js
|
||||
npm test tests-build/markdownUtils.js
|
||||
npm test tests-build/models_BaseItem.js
|
||||
npm test tests-build/models_Folder.js
|
||||
npm test tests-build/models_ItemChange.js
|
||||
npm test tests-build/models_Note.js
|
||||
npm test tests-build/models_Resource.js
|
||||
npm test tests-build/models_Revision.js
|
||||
npm test tests-build/models_Setting.js
|
||||
npm test tests-build/models_Tag.js
|
||||
npm test tests-build/pathUtils.js
|
||||
npm test tests-build/services_InteropService.js
|
||||
npm test tests-build/services_ResourceService.js
|
||||
npm test tests-build/services_rest_Api.js
|
||||
npm test tests-build/services_SearchEngine.js
|
||||
npm test tests-build/services_Revision.js
|
||||
npm test tests-build/StringUtils.js
|
||||
npm test tests-build/synchronizer.js
|
||||
npm test tests-build/urlUtils.js
|
@@ -44,4 +44,13 @@ describe('ArrayUtils', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should compare arrays', async (done) => {
|
||||
expect(ArrayUtils.contentEquals([], [])).toBe(true);
|
||||
expect(ArrayUtils.contentEquals(['a'], ['a'])).toBe(true);
|
||||
expect(ArrayUtils.contentEquals(['b', 'a'], ['a', 'b'])).toBe(true);
|
||||
expect(ArrayUtils.contentEquals(['b'], ['a', 'b'])).toBe(false);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
68
CliClient/tests/EnexToMd.js
Normal file
68
CliClient/tests/EnexToMd.js
Normal file
@@ -0,0 +1,68 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const os = require('os');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { filename } = require('lib/path-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('EnexToMd', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should convert from Enex to Markdown', asyncTest(async () => {
|
||||
const basePath = __dirname + '/enex_to_md';
|
||||
const files = await shim.fsDriver().readDirStats(basePath);
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const htmlFilename = files[i].path;
|
||||
if (htmlFilename.indexOf('.html') < 0) continue;
|
||||
|
||||
const htmlPath = basePath + '/' + htmlFilename;
|
||||
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
|
||||
|
||||
// if (htmlFilename !== 'list5.html') continue;
|
||||
|
||||
const html = await shim.fsDriver().readFile(htmlPath);
|
||||
let expectedMd = await shim.fsDriver().readFile(mdPath);
|
||||
|
||||
let actualMd = await enexXmlToMd('<div>' + html + '</div>', []);
|
||||
|
||||
if (os.EOL === '\r\n') {
|
||||
expectedMd = expectedMd.replace(/\r\n/g, '\n')
|
||||
actualMd = actualMd.replace(/\r\n/g, '\n')
|
||||
}
|
||||
|
||||
if (actualMd !== expectedMd) {
|
||||
console.info('');
|
||||
console.info('Error converting file: ' + htmlFilename);
|
||||
console.info('--------------------------------- Got:');
|
||||
console.info(actualMd.split('\n'));
|
||||
console.info('--------------------------------- Expected:');
|
||||
console.info(expectedMd.split('\n'));
|
||||
console.info('--------------------------------------------');
|
||||
console.info('');
|
||||
|
||||
expect(false).toBe(true);
|
||||
// return;
|
||||
} else {
|
||||
expect(true).toBe(true)
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
72
CliClient/tests/HtmlToMd.js
Normal file
72
CliClient/tests/HtmlToMd.js
Normal file
@@ -0,0 +1,72 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const os = require('os');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { filename } = require('lib/path-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
const HtmlToMd = require('lib/HtmlToMd');
|
||||
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('HtmlToMd', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should convert from Html to Markdown', asyncTest(async () => {
|
||||
const basePath = __dirname + '/html_to_md';
|
||||
const files = await shim.fsDriver().readDirStats(basePath);
|
||||
const htmlToMd = new HtmlToMd();
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const htmlFilename = files[i].path;
|
||||
if (htmlFilename.indexOf('.html') < 0) continue;
|
||||
|
||||
const htmlPath = basePath + '/' + htmlFilename;
|
||||
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
|
||||
|
||||
// if (htmlFilename !== 'picture.html') continue;
|
||||
|
||||
const html = await shim.fsDriver().readFile(htmlPath);
|
||||
let expectedMd = await shim.fsDriver().readFile(mdPath);
|
||||
|
||||
let actualMd = await htmlToMd.parse('<div>' + html + '</div>', []);
|
||||
|
||||
if (os.EOL === '\r\n') {
|
||||
expectedMd = expectedMd.replace(/\r\n/g, '\n')
|
||||
actualMd = actualMd.replace(/\r\n/g, '\n')
|
||||
}
|
||||
|
||||
if (actualMd !== expectedMd) {
|
||||
console.info('');
|
||||
console.info('Error converting file: ' + htmlFilename);
|
||||
console.info('--------------------------------- Got:');
|
||||
console.info(actualMd);
|
||||
console.info('--------------------------------- Raw:');
|
||||
console.info(actualMd.split('\n'));
|
||||
console.info('--------------------------------- Expected:');
|
||||
console.info(expectedMd.split('\n'));
|
||||
console.info('--------------------------------------------');
|
||||
console.info('');
|
||||
|
||||
expect(false).toBe(true);
|
||||
// return;
|
||||
} else {
|
||||
expect(true).toBe(true)
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
45
CliClient/tests/StringUtils.js
Normal file
45
CliClient/tests/StringUtils.js
Normal file
@@ -0,0 +1,45 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const StringUtils = require('lib/string-utils');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('StringUtils', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should surround keywords with strings', async (done) => {
|
||||
const testCases = [
|
||||
[[], 'test', 'a', 'b', 'test'],
|
||||
[['test'], 'test', 'a', 'b', 'atestb'],
|
||||
[['test'], 'Test', 'a', 'b', 'aTestb'],
|
||||
[['te[]st'], 'Te[]st', 'a', 'b', 'aTe[]stb'],
|
||||
// [['test1', 'test2'], 'bla test1 blabla test1 bla test2 not this one - test22', 'a', 'b', 'bla atest1b blabla atest1b bla atest2b not this one - test22'],
|
||||
[['test1', 'test2'], 'bla test1 test1 bla test2', '<span class="highlighted-keyword">', '</span>', 'bla <span class="highlighted-keyword">test1</span> <span class="highlighted-keyword">test1</span> bla <span class="highlighted-keyword">test2</span>'],
|
||||
// [[{ type:'regex', value:'test.*?'}], 'bla test1 test1 bla test2 test tttest', 'a', 'b', 'bla atest1b atest1b bla atest2b atestb tttest'],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const t = testCases[i];
|
||||
|
||||
const keywords = t[0];
|
||||
const input = t[1];
|
||||
const prefix = t[2];
|
||||
const suffix = t[3];
|
||||
const expected = t[4];
|
||||
|
||||
const actual = StringUtils.surroundKeywords(keywords, input, prefix, suffix);
|
||||
|
||||
expect(actual).toBe(expected, 'Test case ' + i);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
@@ -25,8 +25,7 @@ describe('Encryption', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
//await setupDatabaseAndSynchronizer(2);
|
||||
//await switchClient(1);
|
||||
await switchClient(1);
|
||||
service = new EncryptionService();
|
||||
BaseItem.encryptionService_ = service;
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
|
16
CliClient/tests/enex_to_md/code1.html
Normal file
16
CliClient/tests/enex_to_md/code1.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<p>For example, consider a web page like this:</p>
|
||||
|
||||
<pre class="brush: html line-numbers language-html"><code class=" language-html"><span class="token doctype"><!DOCTYPE html></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span><span class="token punctuation">></span></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>content-type<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text/html; charset<span class="token punctuation">=</span>utf-8<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span>
|
||||
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>page-scripts/page-script.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
|
||||
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span><span class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
|
||||
|
||||
<p>The script "page-script.js" does this:</p>
|
||||
</div>
|
14
CliClient/tests/enex_to_md/code1.md
Normal file
14
CliClient/tests/enex_to_md/code1.md
Normal file
@@ -0,0 +1,14 @@
|
||||
For example, consider a web page like this:
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="page-scripts/page-script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
The script "page-script.js" does this:
|
7
CliClient/tests/enex_to_md/code2.html
Normal file
7
CliClient/tests/enex_to_md/code2.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<p>Subshell:</p>
|
||||
<pre><code>(
|
||||
set -e
|
||||
false
|
||||
echo Unreachable
|
||||
) && echo Great success
|
||||
</code></pre>
|
7
CliClient/tests/enex_to_md/code2.md
Normal file
7
CliClient/tests/enex_to_md/code2.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Subshell:
|
||||
|
||||
(
|
||||
set -e
|
||||
false
|
||||
echo Unreachable
|
||||
) && echo Great success
|
9
CliClient/tests/enex_to_md/heading.html
Normal file
9
CliClient/tests/enex_to_md/heading.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
<div class="note">
|
||||
<p>Values added to the global scope of a content script with</p>
|
||||
</div>
|
||||
|
||||
<h2 id="Loading_content_scripts">Loading content scripts</h2>
|
||||
|
||||
<p>You can load a content script into a web page in one of three ways:</p>
|
||||
</div>
|
5
CliClient/tests/enex_to_md/heading.md
Normal file
5
CliClient/tests/enex_to_md/heading.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Values added to the global scope of a content script with
|
||||
|
||||
## Loading content scripts
|
||||
|
||||
You can load a content script into a web page in one of three ways:
|
3
CliClient/tests/enex_to_md/inlineCode.html
Normal file
3
CliClient/tests/enex_to_md/inlineCode.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<p>Similarly, I need another regex to match double newlines (<code>\n\n</code>) that are not part of a longer run of newline characters like <code>\n\n\n</code> or <code>\n\n\n\n\n\n</code> etc.</p>
|
||||
</div>
|
1
CliClient/tests/enex_to_md/inlineCode.md
Normal file
1
CliClient/tests/enex_to_md/inlineCode.md
Normal file
@@ -0,0 +1 @@
|
||||
Similarly, I need another regex to match double newlines (`\n\n`) that are not part of a longer run of newline characters like `\n\n\n` or `\n\n\n\n\n\n` etc.
|
3
CliClient/tests/enex_to_md/inlineCodeWithLink.html
Normal file
3
CliClient/tests/enex_to_md/inlineCodeWithLink.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<p>the <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onConnect">runtime.onConnect</a></code> listener gets passed its own <code><a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/Port">runtime.Port</a></code> object.</p>
|
||||
</div>
|
1
CliClient/tests/enex_to_md/inlineCodeWithLink.md
Normal file
1
CliClient/tests/enex_to_md/inlineCodeWithLink.md
Normal file
@@ -0,0 +1 @@
|
||||
the `[runtime.onConnect](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onConnect)` listener gets passed its own `[runtime.Port](/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/Port)` object.
|
4
CliClient/tests/enex_to_md/link1.html
Normal file
4
CliClient/tests/enex_to_md/link1.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<a href="https://arstechnica.com/civis/ucp.php?mode=login&return_to=%2Ftech-policy%2F2018%2F05%2Fjails-are-replacing-in-person-visits-with-video-calling-services-theyre-awful%2F" class="dropdown-toggle">
|
||||
Sign in
|
||||
<span class="icon dropdown-indicator icon-drop-indicator"></span>
|
||||
</a>
|
1
CliClient/tests/enex_to_md/link1.md
Normal file
1
CliClient/tests/enex_to_md/link1.md
Normal file
@@ -0,0 +1 @@
|
||||
[Sign in](https://arstechnica.com/civis/ucp.php?mode=login&return_to=%2Ftech-policy%2F2018%2F05%2Fjails-are-replacing-in-person-visits-with-video-calling-services-theyre-awful%2F)
|
17
CliClient/tests/enex_to_md/list.html
Normal file
17
CliClient/tests/enex_to_md/list.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<div>
|
||||
<p>Liste de courses</p>
|
||||
|
||||
<div>
|
||||
<div><en-todo checked="true"/>Pizzas</div>
|
||||
<div><en-todo checked="true"/>Pain</div>
|
||||
<div><en-todo checked="true"/>Jambon</div>
|
||||
</div>
|
||||
|
||||
<div><br/></div>
|
||||
|
||||
<div>
|
||||
<div><en-todo checked="true"/>On its own</div>
|
||||
</div>
|
||||
|
||||
<p>End</p>
|
||||
</div>
|
9
CliClient/tests/enex_to_md/list.md
Normal file
9
CliClient/tests/enex_to_md/list.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Liste de courses
|
||||
|
||||
- [X] Pizzas
|
||||
- [X] Pain
|
||||
- [X] Jambon
|
||||
|
||||
- [X] On its own
|
||||
|
||||
End
|
1
CliClient/tests/enex_to_md/list2.html
Normal file
1
CliClient/tests/enex_to_md/list2.html
Normal file
@@ -0,0 +1 @@
|
||||
<ul class="find-me-on"><li><a href="https://github.com/zetter">Github</a></li><li><a href="https://twitter.com/czetter">Twitter</a></li><li><a href="http://lanyrd.com/profile/czetter/">Lanyrd</a></li></ul>
|
3
CliClient/tests/enex_to_md/list2.md
Normal file
3
CliClient/tests/enex_to_md/list2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
- [Github](https://github.com/zetter)
|
||||
- [Twitter](https://twitter.com/czetter)
|
||||
- [Lanyrd](http://lanyrd.com/profile/czetter/)
|
1
CliClient/tests/enex_to_md/list3.html
Normal file
1
CliClient/tests/enex_to_md/list3.html
Normal file
@@ -0,0 +1 @@
|
||||
<ul class="find-me-on"><li>Github</li><li>Twitter</li></ul>
|
2
CliClient/tests/enex_to_md/list3.md
Normal file
2
CliClient/tests/enex_to_md/list3.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Github
|
||||
- Twitter
|
5
CliClient/tests/enex_to_md/list4.html
Normal file
5
CliClient/tests/enex_to_md/list4.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<li><div>This note has an unordered list</div></li>
|
||||
<li><div>List item</div></li>
|
||||
<li><div>List item</div></li>
|
||||
</ul>
|
3
CliClient/tests/enex_to_md/list4.md
Normal file
3
CliClient/tests/enex_to_md/list4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
- This note has an unordered list
|
||||
- List item
|
||||
- List item
|
16
CliClient/tests/enex_to_md/list5.html
Normal file
16
CliClient/tests/enex_to_md/list5.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<ul>
|
||||
<li lang="en-US">
|
||||
<div>Protocols</div>
|
||||
</li>
|
||||
<ul type="circle">
|
||||
<li lang="en-US">
|
||||
<div>two common network protocols used to send data packets over a network</div>
|
||||
</li>
|
||||
<li lang="en-US">
|
||||
<div>TCP Transmission control protocol</div>
|
||||
</li>
|
||||
</ul>
|
||||
<li lang="en-US">
|
||||
<div>Network port - a network port is a process-specific or an application-specific software construct serving as a communication endpoint, which is used by the Transport Layer protocols of Internet Protocol suite, such as UDP and TCP</div>
|
||||
</li>
|
||||
</ul>
|
7
CliClient/tests/enex_to_md/list5.md
Normal file
7
CliClient/tests/enex_to_md/list5.md
Normal file
@@ -0,0 +1,7 @@
|
||||
- Protocols
|
||||
|
||||
- two common network protocols used to send data packets over a network
|
||||
|
||||
- TCP Transmission control protocol
|
||||
|
||||
- Network port - a network port is a process-specific or an application-specific software construct serving as a communication endpoint, which is used by the Transport Layer protocols of Internet Protocol suite, such as UDP and TCP
|
11
CliClient/tests/enex_to_md/paragraph.html
Normal file
11
CliClient/tests/enex_to_md/paragraph.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<p>Short paragraphs are merged together:</p>
|
||||
<p>Something something</p>
|
||||
<p>Blablbla blabla lbla</p>
|
||||
<p>Last line</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>Longer ones are separated by new lines. In 1894 Joplin arrived in Sedalia, Missouri. At first, Joplin stayed with the family of Arthur Marshall, at the time a 13-year-old boy but later one of Joplin's students and a rag-time composer in his own right.[26] There is no record of Joplin having a permanent residence in the town until 1904, as Joplin was making a living as a touring musician.</p>
|
||||
<p>There is little precise evidence known about Joplin's activities at this time, although he performed as a solo musician at dances and at the major black clubs in Sedalia, the Black 400 club and the Maple Leaf Club. He performed in the Queen City Cornet Band, and his own six-piece dance orchestra.</p>
|
||||
</div>
|
8
CliClient/tests/enex_to_md/paragraph.md
Normal file
8
CliClient/tests/enex_to_md/paragraph.md
Normal file
@@ -0,0 +1,8 @@
|
||||
Short paragraphs are merged together:
|
||||
Something something
|
||||
Blablbla blabla lbla
|
||||
Last line
|
||||
|
||||
Longer ones are separated by new lines. In 1894 Joplin arrived in Sedalia, Missouri. At first, Joplin stayed with the family of Arthur Marshall, at the time a 13-year-old boy but later one of Joplin's students and a rag-time composer in his own right.[26] There is no record of Joplin having a permanent residence in the town until 1904, as Joplin was making a living as a touring musician.
|
||||
|
||||
There is little precise evidence known about Joplin's activities at this time, although he performed as a solo musician at dances and at the major black clubs in Sedalia, the Black 400 club and the Maple Leaf Club. He performed in the Queen City Cornet Band, and his own six-piece dance orchestra.
|
12
CliClient/tests/enex_to_md/table1.html
Normal file
12
CliClient/tests/enex_to_md/table1.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div>$ sudo ethtool --set-priv-flags p2p1 mlx4_rss_xor_hash_function on</div>
|
||||
<div># Three empty lines follow</div>
|
||||
<div><br/></div>
|
||||
<div><br/></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Some text
|
5
CliClient/tests/enex_to_md/table1.md
Normal file
5
CliClient/tests/enex_to_md/table1.md
Normal file
@@ -0,0 +1,5 @@
|
||||
| |
|
||||
| --- |
|
||||
| $ sudo ethtool --set-priv-flags p2p1 mlx4_rss_xor_hash_function on<br># Three empty lines follow |
|
||||
|
||||
Some text
|
3
CliClient/tests/enex_to_md/tableWithNewLines.html
Normal file
3
CliClient/tests/enex_to_md/tableWithNewLines.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<table style="-evernote-table:true;border-collapse:collapse;width:100%;table-layout:fixed;margin-left:0px;"><tr><td style="border-style:solid;border-width:1px;border-color:rgb(211,211,211);padding:10px;margin:0px;width:50%;"><div>line 1</div><div>line 2</div></td><td style="border-style:solid;border-width:1px;border-color:rgb(211,211,211);padding:10px;margin:0px;width:50%;"><div><br/></div></td></tr><tr><td style="border-style:solid;border-width:1px;border-color:rgb(211,211,211);padding:10px;margin:0px;width:50%;"><div>aaaaaa</div></td><td style="border-style:solid;border-width:1px;border-color:rgb(211,211,211);padding:10px;margin:0px;width:50%;"><div>line 3</div><div>line 4</div></td></tr></table>
|
||||
</div>
|
4
CliClient/tests/enex_to_md/tableWithNewLines.md
Normal file
4
CliClient/tests/enex_to_md/tableWithNewLines.md
Normal file
@@ -0,0 +1,4 @@
|
||||
| | |
|
||||
| --- | --- |
|
||||
| line 1<br>line 2 | |
|
||||
| aaaaaa | line 3<br>line 4 |
|
1
CliClient/tests/html_to_md/anchor_with_inner_tags.html
Normal file
1
CliClient/tests/html_to_md/anchor_with_inner_tags.html
Normal file
@@ -0,0 +1 @@
|
||||
<a href="https://joplinapp.org"><h1 id="joplin"><img class="title-icon" src="https://joplinapp.org/images/Icon512.png">oplin</h1></a>
|
1
CliClient/tests/html_to_md/anchor_with_inner_tags.md
Normal file
1
CliClient/tests/html_to_md/anchor_with_inner_tags.md
Normal file
@@ -0,0 +1 @@
|
||||
[# oplin](https://joplinapp.org)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user