Compare commits
702 Commits
android-v1
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
6fc0ee3062 | ||
|
7b42d7d2c8 | ||
|
eb083ae925 | ||
|
905e65365f | ||
|
a0c04c0e6a | ||
|
635baa5b6f | ||
|
7591b614c5 | ||
|
5b5ec682c0 | ||
|
b0a80ddf65 | ||
|
893531f8c7 | ||
|
3d498e7a75 | ||
|
45ad201132 | ||
|
6e64b950ca | ||
|
6d4e67769c | ||
|
3aa0394062 | ||
|
b65767a43c | ||
|
3a9817d11e | ||
|
6a42ef50ec | ||
|
35b6b3fc46 | ||
|
f5515e3496 | ||
|
fd509bb4af | ||
|
b21c0f5d69 | ||
|
1033b3626f | ||
|
f407c8d756 | ||
|
6436dff94b | ||
|
3f7b4e10b6 | ||
|
36168a9a5d | ||
|
118540c733 | ||
|
cd5d412c69 | ||
|
e29fb3eb66 | ||
|
8ff1668c8f | ||
|
14fc73b388 | ||
|
f34330f101 | ||
|
2ba50321d4 | ||
|
38177c7e54 | ||
|
687e308a73 | ||
|
490db0db62 | ||
|
feb5f17479 | ||
|
fbb3543818 | ||
|
30d0dfb424 | ||
|
7239a2013c | ||
|
2361c5a5e7 | ||
|
38e8a881d5 | ||
|
d45d1b4225 | ||
|
e9c88dfdc4 | ||
|
fbb0ac5892 | ||
|
7a902bbd25 | ||
|
c75618eb8f | ||
|
74ee629266 | ||
|
8ecc58e1bf | ||
|
71078637db | ||
|
5460a977b1 | ||
|
a0dd0702fb | ||
|
3e48992eb4 | ||
|
bdb31f2890 | ||
|
0255546ae1 | ||
|
d066350eea | ||
|
a1e3260309 | ||
|
be1f57a8a6 | ||
|
ca4dfe0f0f | ||
|
fa69957d3f | ||
|
f7203ed7e2 | ||
|
331858bd4f | ||
|
4d2c9523a3 | ||
|
4d9d84a8f3 | ||
|
ec1089870f | ||
|
dbedefc021 | ||
|
f6b0da3f5e | ||
|
85bf89fd97 | ||
|
c2a80b12f0 | ||
|
981c97cca5 | ||
|
091cbc5355 | ||
|
e5a8114887 | ||
|
4779fc6f43 | ||
|
86e7daaec4 | ||
|
cab73a26e7 | ||
|
554ddb3b51 | ||
|
3b22bdb8ae | ||
|
5fdd07679e | ||
|
69f75a1520 | ||
|
f9b7acb8b1 | ||
|
91f700ad54 | ||
|
966aca7753 | ||
|
4de8816ed5 | ||
|
bea68a1056 | ||
|
6fea7116b6 | ||
|
2955914ca5 | ||
|
fd150b5b9d | ||
|
334ffad196 | ||
|
a796a9d179 | ||
|
917dcea28a | ||
|
c901228dc5 | ||
|
da21580785 | ||
|
4d92187327 | ||
|
207d433fb3 | ||
|
ffc311d7bd | ||
|
a1e8e71359 | ||
|
7942e74dc6 | ||
|
c4e21c2b6a | ||
|
0a06aa6f9f | ||
|
f985cfa25c | ||
|
6e143aef5c | ||
|
bf16aa6192 | ||
|
d96c58d192 | ||
|
e7e0264411 | ||
|
430a11282b | ||
|
9957b2798c | ||
|
2c5b0010bf | ||
|
1e3c6ed98c | ||
|
484f290eb0 | ||
|
06ad539941 | ||
|
5b84e80ac4 | ||
|
ca0f349348 | ||
|
d79089aea3 | ||
|
03611ad5ca | ||
|
c78c1cd3cf | ||
|
55afa7b5b7 | ||
|
a6c407b62b | ||
|
21897a3cd4 | ||
|
5796dd2098 | ||
|
d050071437 | ||
|
eaf8510f49 | ||
|
6ee2595dce | ||
|
0ecf2d6d9a | ||
|
50fd075168 | ||
|
6fa76bb83a | ||
|
b175c1fc94 | ||
|
b461625518 | ||
|
3819897ba1 | ||
|
6a031857ba | ||
|
0e57b7eb46 | ||
|
e21a0ba5b7 | ||
|
78f731e616 | ||
|
f6688a65ae | ||
|
035b9c6d1a | ||
|
266ff244d9 | ||
|
de1bfa5c34 | ||
|
478b8f00d8 | ||
|
860d2fd7f5 | ||
|
6ce091f4d8 | ||
|
ce595ac5e4 | ||
|
267436a00d | ||
|
97e0f4258a | ||
|
faa6ccc150 | ||
|
349cade946 | ||
|
0200aa92de | ||
|
60ed2cbee5 | ||
|
0e7b2f36c8 | ||
|
4083221b21 | ||
|
d55c511b4a | ||
|
7863e1dffe | ||
|
7cfdf778de | ||
|
075e55c077 | ||
|
dc818e8a0c | ||
|
caa58dd913 | ||
|
c84c3cd026 | ||
|
bda5ac9fb5 | ||
|
f928f645e5 | ||
|
53d7e906d4 | ||
|
e670b5d03f | ||
|
2a7d555859 | ||
|
e6675f500c | ||
|
c3f20d3ebc | ||
|
ff257060d1 | ||
|
68cde202a4 | ||
|
1a7a87e170 | ||
|
2990642923 | ||
|
0818de036e | ||
|
122bc29035 | ||
|
861cf8a1b2 | ||
|
7d6959e9e4 | ||
|
85091052e7 | ||
|
f46ad5bfda | ||
|
29f7937fc2 | ||
|
7fae9fda10 | ||
|
a37961dccc | ||
|
55155646aa | ||
|
7bffe86439 | ||
|
86136e0c6c | ||
|
21ae447d9c | ||
|
ad211b4b4e | ||
|
d6218f35fe | ||
|
7af0dcd19a | ||
|
e1a52c5606 | ||
|
468c345527 | ||
|
041bdc08a2 | ||
|
7535f1a8c6 | ||
|
0b24433db3 | ||
|
f7de0c5ffd | ||
|
62c48b9a46 | ||
|
3fafda9684 | ||
|
2cf1cda128 | ||
|
fedee9499b | ||
|
7c6c7f34ba | ||
|
0d65edd0e8 | ||
|
d57520c66a | ||
|
fa28ae1433 | ||
|
0acf9823e5 | ||
|
799ad5f1da | ||
|
9f6b3ccf40 | ||
|
df714c357d | ||
|
de5fdc84f8 | ||
|
b5b228af15 | ||
|
7a85628f46 | ||
|
075e76fc4b | ||
|
172a98fed4 | ||
|
409bdd7b78 | ||
|
d012c689e1 | ||
|
89ec0629e6 | ||
|
b3475ae195 | ||
|
a72ab67473 | ||
|
90fbfec914 | ||
|
49ef023a90 | ||
|
64bfd74f18 | ||
|
655e35056e | ||
|
a13ba63ab8 | ||
|
e2e00d4c87 | ||
|
159dc44f6c | ||
|
8fe2091926 | ||
|
c362c38dc0 | ||
|
316a52bbc2 | ||
|
0de9f6f944 | ||
|
3ba021fdd9 | ||
|
04c6579f2c | ||
|
bc7bd456a7 | ||
|
6d7511efbb | ||
|
a0fb99d78f | ||
|
685a52c2c5 | ||
|
fc77419ca1 | ||
|
bab3a12e92 | ||
|
21f0b90f48 | ||
|
83682ab513 | ||
|
7b987b5a8f | ||
|
cb5aa425c8 | ||
|
dd222381dd | ||
|
d7210811f6 | ||
|
3a43cfeebf | ||
|
31e33c6628 | ||
|
875f8d6997 | ||
|
aaaf27af6e | ||
|
43624ffa75 | ||
|
996b6623f1 | ||
|
613041b806 | ||
|
ff1d01a864 | ||
|
bcbbe10bf8 | ||
|
3de0abfc84 | ||
|
133fd03469 | ||
|
4f97c5c017 | ||
|
2d8fbac58c | ||
|
95f7ac4a4a | ||
|
6a56a6ccf0 | ||
|
1eb8df9fa6 | ||
|
485b4baebb | ||
|
5590d887c9 | ||
|
d00bfa997e | ||
|
1a8590e9b9 | ||
|
5a978977df | ||
|
5d763c7e6c | ||
|
050b089e72 | ||
|
733ea4027c | ||
|
10500c78b1 | ||
|
0040cc02a2 | ||
|
74afd20f0c | ||
|
dc9bde2184 | ||
|
5243ea7eb2 | ||
|
7d93492658 | ||
|
c6b56345f5 | ||
|
8a6fe20a69 | ||
|
6bcbedd6a4 | ||
|
4c935b78f9 | ||
|
94cddda6d0 | ||
|
1924ea062c | ||
|
07e88b2eeb | ||
|
e4a08c29d7 | ||
|
d60afcaabe | ||
|
1a091460ca | ||
|
8ebaa7f6eb | ||
|
e2a64e21a2 | ||
|
78ddd22f09 | ||
|
c546b7076a | ||
|
0e2bb5d784 | ||
|
5c069c38f5 | ||
|
451b9c0ae9 | ||
|
047897621a | ||
|
52e5cec585 | ||
|
bc98b65efa | ||
|
9250e77862 | ||
|
cd69e71945 | ||
|
e705e6e990 | ||
|
4638f11c5e | ||
|
9de7c15e93 | ||
|
61736546b4 | ||
|
82b6dd23a7 | ||
|
64427f0160 | ||
|
cbf47cb9ee | ||
|
dba3e4202d | ||
|
173cd6de4d | ||
|
3d333bd8f2 | ||
|
a82f8c7dd0 | ||
|
cde079e44e | ||
|
f8d20b61ea | ||
|
d279435502 | ||
|
eb2065128e | ||
|
10e81aa476 | ||
|
3e808f05fd | ||
|
e57bfad9b1 | ||
|
5f344f07d4 | ||
|
e1b7b64e1b | ||
|
ed3970be81 | ||
|
c27861d40f | ||
|
565dfba8c9 | ||
|
553a26eb63 | ||
|
9c85bc2cd1 | ||
|
e96bc9c48a | ||
|
0d036d8183 | ||
|
e5f2a7f2f5 | ||
|
016ce3dd61 | ||
|
afb375955e | ||
|
b702b0b40c | ||
|
91ecab51c5 | ||
|
7628506926 | ||
|
4e7f7c0c9c | ||
|
863f5bcf18 | ||
|
dccd489fcc | ||
|
333c3f6369 | ||
|
808413d0bf | ||
|
440be3d920 | ||
|
9b27a4f601 | ||
|
6e36ca32b4 | ||
|
85a9c303f2 | ||
|
7e9972d99f | ||
|
771975cd35 | ||
|
356f8e580b | ||
|
68268cb35d | ||
|
8e58ed12af | ||
|
beb428b246 | ||
|
4d81caff0b | ||
|
78372c9bac | ||
|
a4db1bc671 | ||
|
8ea1c373ed | ||
|
8ef27dfcdc | ||
|
23e43c7bc1 | ||
|
edb8f4c79f | ||
|
e115fa4bb3 | ||
|
52a2daddbf | ||
|
c400142996 | ||
|
219171a18c | ||
|
d7d573d9dd | ||
|
c4b17f8919 | ||
|
da2f4b96c7 | ||
|
08af9de190 | ||
|
9e2982992a | ||
|
c03ac5c5f1 | ||
|
5934f2f08e | ||
|
f136f40fdc | ||
|
d213e4ab57 | ||
|
782aae4ddf | ||
|
4f47bd7bcd | ||
|
4f76946140 | ||
|
aa60923cbd | ||
|
7670ce32b1 | ||
|
0b98632336 | ||
|
49c998de83 | ||
|
da69d6b2c9 | ||
|
a757aefce0 | ||
|
b2129cb8c4 | ||
|
27f14c175f | ||
|
aad49c520b | ||
|
af794a16d6 | ||
|
ca7266cd69 | ||
|
fb758afc81 | ||
|
b806f0da49 | ||
|
4e3b1f3e13 | ||
|
475467c41c | ||
|
28e5039873 | ||
|
1efc6e6151 | ||
|
e280a02643 | ||
|
788dc42684 | ||
|
01f2759a62 | ||
|
9419e3af9c | ||
|
6d220005cc | ||
|
6d68e61bbd | ||
|
29582623b0 | ||
|
4571e7853a | ||
|
d6e59c5238 | ||
|
6335cbedb8 | ||
|
f3344ce05d | ||
|
155d38d24a | ||
|
412f6d8316 | ||
|
d0f3ed80e0 | ||
|
b86f3b74bd | ||
|
60054d1d8b | ||
|
c40c6428d7 | ||
|
e708ecccee | ||
|
04f991d3bf | ||
|
d5d7368ba0 | ||
|
abff929d4e | ||
|
49edc82594 | ||
|
7dd7d0ec17 | ||
|
61aaf64f95 | ||
|
da35785951 | ||
|
e394034678 | ||
|
edf002ab32 | ||
|
50f2076981 | ||
|
a9ae78bcde | ||
|
0d7f9a2ab3 | ||
|
a5ee120281 | ||
|
12ebf44e22 | ||
|
220f5d0967 | ||
|
4ef05272c4 | ||
|
c3262aa5f8 | ||
|
42119c8f42 | ||
|
27cce03968 | ||
|
776aba1e49 | ||
|
9356841cfc | ||
|
7fc233e808 | ||
|
7b2eac3abd | ||
|
93323deea5 | ||
|
e8fa399e9e | ||
|
a974eb5d9f | ||
|
6a3f04274d | ||
|
f8e1395087 | ||
|
82b5af51e5 | ||
|
cf40c14a86 | ||
|
be2b2b7836 | ||
|
0cebae8032 | ||
|
6ebc77cbba | ||
|
542a5e88b7 | ||
|
72b36522e8 | ||
|
252d937405 | ||
|
a73b0309b9 | ||
|
c22283e799 | ||
|
1e51ab4a59 | ||
|
0c2f2667d3 | ||
|
496c9ddb91 | ||
|
5ad0b2eed9 | ||
|
577d62e783 | ||
|
dcb73c9916 | ||
|
6b2910c3c7 | ||
|
db04906416 | ||
|
54fceeb07d | ||
|
fa32678645 | ||
|
ee1df1a396 | ||
|
729be8767c | ||
|
8471f0d86d | ||
|
390b818d71 | ||
|
1a1c190ea3 | ||
|
40d82b80f1 | ||
|
7647ecbbc7 | ||
|
b2a5cf9dd0 | ||
|
cbf3ab2ec2 | ||
|
c4a37ff0ba | ||
|
bdc7ea4346 | ||
|
d4c4b9b10a | ||
|
4b9105edff | ||
|
c0980a5a9e | ||
|
272055fc1d | ||
|
cbb1851b12 | ||
|
45d758d52e | ||
|
49936ef095 | ||
|
986d4be601 | ||
|
b6ad9719ad | ||
|
96a1546da1 | ||
|
6884dd2b9e | ||
|
9c027e59c4 | ||
|
9e16ff3644 | ||
|
4000cb5d1c | ||
|
1030b412ff | ||
|
54f0fbcf6b | ||
|
1602182085 | ||
|
20bb1238c5 | ||
|
68fbe8125e | ||
|
23e6e6e69d | ||
|
ade5af2559 | ||
|
0a993dc012 | ||
|
4baa46507f | ||
|
0d8878abd3 | ||
|
f962084591 | ||
|
921b45286b | ||
|
301bfed05e | ||
|
62e7d6fa86 | ||
|
7e34cd4452 | ||
|
b35cb9a7ab | ||
|
18b836525c | ||
|
e34e49b88d | ||
|
5bf879c2d9 | ||
|
61d6309c0e | ||
|
17c9c0f9ef | ||
|
9bd62fd3d4 | ||
|
de73d4baa7 | ||
|
379ff5163b | ||
|
d9538ccb08 | ||
|
9289dbdf77 | ||
|
5719ae495a | ||
|
a89e3b7924 | ||
|
e576d09712 | ||
|
43600a7824 | ||
|
921f01d9dc | ||
|
dec5668582 | ||
|
e30bc12354 | ||
|
fca4fa666d | ||
|
59478160c8 | ||
|
8110fe89ef | ||
|
c7ed1b5eae | ||
|
25951e7097 | ||
|
687b9d1bef | ||
|
630e77b9eb | ||
|
68ff2e17b3 | ||
|
945d83608a | ||
|
833d473268 | ||
|
2256b0c5ec | ||
|
677aa7d59b | ||
|
4363005e92 | ||
|
2e3ef618db | ||
|
1adbbd14c6 | ||
|
4dfd7db729 | ||
|
e70562a102 | ||
|
a0e5947ba4 | ||
|
e841ea8a91 | ||
|
770a435029 | ||
|
49b56e84a7 | ||
|
ff1a6fdbbd | ||
|
2168090b96 | ||
|
33f7b680bc | ||
|
0957298cb8 | ||
|
08f2f982cf | ||
|
3376fbfa55 | ||
|
4a31e5fe73 | ||
|
baacec5ba6 | ||
|
95188b71b8 | ||
|
cf57be6e98 | ||
|
b691092d7a | ||
|
03e60fc028 | ||
|
2e25ec318f | ||
|
7236e5e9ae | ||
|
6f7dd51a98 | ||
|
db1dab9293 | ||
|
06f1b9e4d7 | ||
|
8f958ac931 | ||
|
eae63bfb79 | ||
|
8adfc81c30 | ||
|
0c516443e3 | ||
|
ad9bc0bf63 | ||
|
b0596670a6 | ||
|
998011ff43 | ||
|
081e1c5b62 | ||
|
edfd2c4d54 | ||
|
9d65a3a34c | ||
|
1a86cbdb9d | ||
|
849cb4456c | ||
|
1736717f2e | ||
|
50b75e1e63 | ||
|
179005dd6c | ||
|
6b3fe6b2cb | ||
|
c34872bb26 | ||
|
4845a21287 | ||
|
ddd513fe09 | ||
|
4ce118d459 | ||
|
99da184ba5 | ||
|
e2e4e62c4f | ||
|
229dd7a6dd | ||
|
1e0c4cc5cd | ||
|
b40ccc7a15 | ||
|
7d6b7e588c | ||
|
22cacd2c5b | ||
|
6a22e7836a | ||
|
32a67b9b33 | ||
|
b5dff09c28 | ||
|
c56d8153e8 | ||
|
eb5950d126 | ||
|
4241436e40 | ||
|
e93af7aed5 | ||
|
d2416f850e | ||
|
7af22eb006 | ||
|
3f1be5e7e7 | ||
|
a4e649c82d | ||
|
cde1a8f0a8 | ||
|
cda6eb7c2f | ||
|
3c3e6aeca0 | ||
|
99156311db | ||
|
91b2e5e703 | ||
|
573fd816d0 | ||
|
e6aa002758 | ||
|
361d46ac5d | ||
|
9bc7c2fd65 | ||
|
ce49f5f8b7 | ||
|
81e4cd319d | ||
|
71f905535f | ||
|
d3bff0a9e3 | ||
|
88e6315d09 | ||
|
3d933c5244 | ||
|
73af19314d | ||
|
1d71712c8a | ||
|
1333c35389 | ||
|
e0f5f47a15 | ||
|
34323042d5 | ||
|
aa86fa9986 | ||
|
d2d659d5a9 | ||
|
1595248b52 | ||
|
fc94c616b5 | ||
|
f6f0bcf1c3 | ||
|
6eeeda5dab | ||
|
58993d2ead | ||
|
7c0b608769 | ||
|
259be84a3e | ||
|
57c880cf85 | ||
|
0469fe76d7 | ||
|
a3e74320fa | ||
|
4e0f4397b2 | ||
|
b26aab3863 | ||
|
75ec97fe61 | ||
|
9a356453fc | ||
|
f0020b3393 | ||
|
ea9f1dc91d | ||
|
2ef77dcf1f | ||
|
29e7ec4cc9 | ||
|
5710e3fad0 | ||
|
a03aa62d58 | ||
|
2203a39917 | ||
|
bc58668483 | ||
|
0b4650f355 | ||
|
aecdec48ad | ||
|
e49198a0d4 | ||
|
6aa4553dd3 | ||
|
860e8a8f5a | ||
|
434037d793 | ||
|
214eae27da | ||
|
0567188fa8 | ||
|
4326902683 | ||
|
da3589149d | ||
|
69b4b4d1f4 | ||
|
dd4b46a88b | ||
|
6f2253b2f4 | ||
|
4c00d9512e | ||
|
6f511cb1e6 | ||
|
029e84f538 | ||
|
42b1db1d08 | ||
|
9d4b34cad7 | ||
|
cd9aff0f59 | ||
|
032816fffc | ||
|
1408f06c8d | ||
|
49f8d0c6d8 | ||
|
0d6443c30a | ||
|
e62d91dda8 | ||
|
0e122c9dc5 | ||
|
c2bd453e8c | ||
|
949ea7afb7 | ||
|
9f575101d2 | ||
|
2b4470054e | ||
|
b220613e54 | ||
|
11328babe8 | ||
|
735bc92bc4 | ||
|
6894b9b1b7 | ||
|
4de7815f31 | ||
|
4ce7b48468 | ||
|
77e4cb87ad | ||
|
dd6b43035e | ||
|
e76094c546 | ||
|
9c00dc4cab | ||
|
fc416de348 | ||
|
a2156be4ec | ||
|
cf427eba0f | ||
|
0050c90678 | ||
|
5eeff02dbe | ||
|
eb283efc20 | ||
|
87121c9c21 | ||
|
a2dbbbf832 | ||
|
fd251cd9a9 | ||
|
8ced2d288e | ||
|
242926d381 | ||
|
8c9a148e71 | ||
|
9e165fc7dc | ||
|
f46e4e0cec | ||
|
efcf5ecef4 | ||
|
b6ba843d09 | ||
|
915112e274 | ||
|
cc8f8fcd2c | ||
|
bda3ea9a35 | ||
|
a7aed1f93a | ||
|
a33f602f3b | ||
|
4d08b49578 | ||
|
21e049ab45 | ||
|
1d4234caea | ||
|
d1269de3a7 | ||
|
8c19fcf8fc | ||
|
beaba2be55 | ||
|
32c9ad1d59 | ||
|
a194513252 | ||
|
cd93a1d1e1 | ||
|
2867728996 | ||
|
394cc78851 | ||
|
76f0a26322 | ||
|
92d7a577a0 | ||
|
9c1219b188 | ||
|
f62bbfe286 | ||
|
fef176eb96 | ||
|
ed541dac3b | ||
|
4a175b2158 | ||
|
4076899e11 | ||
|
998bdf3b56 | ||
|
76b211eb6d | ||
|
2a4812cb87 |
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: joplin
|
||||
custom: https://joplinapp.org/donate/
|
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
👉 Please follow one of these issue templates:
|
||||
- https://github.com/laurent22/joplin/issues/new/choose
|
||||
|
||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: "🐛 Bug Report"
|
||||
about: Report a reproducible bug or regression in Joplin.
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Please provide a clear and concise description of what the bug is. (In the section Steps To Reproduce.)
|
||||
Include screenshots if needed.
|
||||
Please test using the latest Joplin release to make sure your issue has not already been fixed.
|
||||
-->
|
||||
|
||||
## Environment
|
||||
|
||||
Joplin version:
|
||||
Platform:
|
||||
OS specifcs:
|
||||
<!--
|
||||
Platform can be one of: macOS, Linux, Windows, Android, iOS, terminal (or a combination)
|
||||
OS specifcs: e.g. OS version, Linux distribution, Android/iOS version, ...
|
||||
-->
|
||||
|
||||
## Steps To Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
<!--
|
||||
Issues without reproduction steps are likely to stall.
|
||||
-->
|
||||
|
||||
Describe what you expected to happen:
|
||||
|
||||
|
||||
|
||||
## Logfile
|
||||
|
||||
<!--
|
||||
Please attach a debug log. Issues without a debug log are likely to stall.
|
||||
For information on how to collect a log file: https://joplinapp.org/debugging/
|
||||
-->
|
33
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature for Joplin.
|
||||
title: '[Feature request] '
|
||||
labels: 'feature request'
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Please search open issues first - many features have already been requested!
|
||||
-->
|
||||
|
||||
## Has it been discussed in the forum? Link to topic.
|
||||
<!--
|
||||
Feature requests should be discussed in the forum first. https://discourse.joplinapp.org
|
||||
Please provide a link to the topic.
|
||||
-->
|
||||
|
||||
|
||||
## Is your feature request related to a problem? Please describe.
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
|
||||
## Describe the solution you'd like
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
|
||||
## Describe alternatives you've considered
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
29
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: "🤔 Questions and Help"
|
||||
about: The issue tracker is not for questions. Please ask questions on https://discourse.joplinapp.org/.
|
||||
title: 'Question: '
|
||||
labels: 'question'
|
||||
|
||||
---
|
||||
|
||||
🚨 The issue tracker is not for questions. 🚨
|
||||
|
||||
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek.
|
||||
|
||||
## Questions and Help
|
||||
|
||||
Please read the [documentation](https://joplinapp.org/) and [FAQ](https://joplinapp.org/faq/) first.
|
||||
|
||||
### https://discourse.joplinapp.org/
|
||||
|
||||
If you have still questions related to Joplin, please open a topic in the [forum](https://discourse.joplinapp.org/).
|
||||
You can use your GitHub credentials to login to the forum.
|
||||
|
||||
## Links
|
||||
|
||||
- Documentation: https://joplinapp.org
|
||||
- FAQ: https://joplinapp.org/faq/
|
||||
- Forum: https://discourse.joplinapp.org
|
||||
- How to enable end-to-end encryption: https://joplinapp.org/e2ee/
|
||||
- API documentation: https://joplinapp.org/api/
|
||||
- How to enable debug mode: https://joplinapp.org/debugging/
|
25
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "good first issue"
|
||||
- "essential"
|
||||
- "essential-reviewed"
|
||||
- "help wanted"
|
||||
- "nice to have"
|
||||
- "upstream"
|
||||
- "backlog"
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs.
|
||||
You may also label this issue as "backlog" and I will leave it open.
|
||||
Thank you for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.
|
||||
only: issues
|
1
.gitignore
vendored
@@ -39,6 +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
|
Before Width: | Height: | Size: 986 B |
BIN
Assets/AdresseSupport.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
Assets/AdresseTranslation.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 336 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 244 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.png
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
Assets/Screenshots/iOS/Screenshot_iPhone_Portrait_X.psd
Normal file
5
BUILD.md
@@ -1,7 +1,8 @@
|
||||
[](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
|
||||
|
||||
@@ -58,6 +59,8 @@ If node-gyp does not works (MSBUILD: error MSB3428: Could not load the Visual C+
|
||||
|
||||
If `yarn dist` fails, it may need administrative rights.
|
||||
|
||||
If you get an `error MSB8020: The build tools for v140 cannot be found.` try to run with a different toolset version, eg `npm install --toolset=v141` (See [here](https://github.com/mapbox/node-sqlite3/issues/1124) for more info).
|
||||
|
||||
The [building\_win32\_tips on this page](./readme/building_win32_tips.md) might be helpful.
|
||||
|
||||
# Building the Mobile application
|
||||
|
@@ -1,34 +1,63 @@
|
||||
**IMPORTANT:** At the moment pull requests for new features are no longer being accepted. More info there: https://github.com/laurent22/joplin/issues/1112
|
||||
|
||||
* * *
|
||||
|
||||
# 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.
|
||||
The [Joplin Forum](https://discourse.joplinapp.org/) is the community driven place for user support, general discussion about Joplin, problems with installation, new features and software development questions. It is possible to login with your GitHub account. Don't use the issue tracker for support questions.
|
||||
|
||||
# Reporting a bug
|
||||
|
||||
Please check first that it [has not already been reported](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Also consider [enabling debug mode](https://github.com/laurent22/joplin/blob/master/readme/debugging.md) before reporting the issue so that you can provide as much details as possible to help fix it.
|
||||
File bugs in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Please follow these guidelines:
|
||||
|
||||
If possible, **please provide a screenshot**. A screenshot showing the problem is often more useful than a paragraph describing it as it can make it immediately clear what the issue is.
|
||||
- Search existing issues first, make sure yours hasn't already been reported.
|
||||
- Consider [enabling debug mode](https://joplinapp.org/debugging/) so that you can provide as much details as possible when reporting the issue.
|
||||
- Stay on topic, but describe the issue in detail so that others can reproduce it.
|
||||
- **Provide a screenshot** if possible. A screenshot showing the problem is often more useful than a paragraph describing it.
|
||||
- For web clipper bugs, **please provide the URL causing the issue**. Sometimes the clipper works in one page but not in another so it is important to know what URL has a problem.
|
||||
|
||||
# Feature requests
|
||||
|
||||
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.
|
||||
Please check that your request has not already been posted in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). If it has, **up-voting the issue** increases the chances it'll be noticed and implemented in the future. "+1" comments are not tracked.
|
||||
|
||||
# Creating a pull request
|
||||
As a general rule, suggestions to *improve Joplin* should be posted first in the [Joplin Forum](https://discourse.joplinapp.org/) for discussion.
|
||||
|
||||
- If you want to add a new feature, consider asking about it before implementing it or checking existing discussions to make sure it is within the scope of the project. That scope, due to limited resources, might be narrower than you think. 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 waste your time implementing something that might not be accepted.
|
||||
Avoid listing multiple requests in one report in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). One issue per request makes it easier to track and discuss it.
|
||||
|
||||
- Bug fixes have a very high change of being accepted.
|
||||
Finally, when submitting a pull request, don't forget to [test your code](#unit-tests).
|
||||
|
||||
- A pull request that is relevant to the current roadmap has a very high change of being accepted.
|
||||
# Contribute to the project
|
||||
|
||||
## Contributing to Joplin's translation
|
||||
|
||||
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
|
||||
|
||||
## Contributing to Joplin's code
|
||||
|
||||
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
|
||||
|
||||
- Bug fixes are always welcome. Start by reviewing the list of [essential issues](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Aessential)
|
||||
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
|
||||
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
|
||||
|
||||
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
|
||||
|
||||
# Coding style
|
||||
## Coding style
|
||||
|
||||
There are only two rules, but not following them means the pull request will not be accepted (it can be accepted once the issues are fixed):
|
||||
|
||||
- **Please use tabs, NOT spaces.**
|
||||
- **Please do not add or remove optional characters, such as spaces or colons.** Please setup your editor so that it only changes what you are working on and is not making automated changes elsewhere. The reason for this is that small white space changes make diff hard to read and can cause needless conflicts.
|
||||
|
||||
## Unit tests
|
||||
|
||||
When submitting a pull request for a new feature or bug fix, please add unit tests for your code. Unit testing GUI changes is not always possible so it is not required, but any change in a file under /lib for example should be unit tested.
|
||||
|
||||
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
|
||||
|
||||
cd CliClient
|
||||
npm i
|
||||
|
||||
Then to run all the test units:
|
||||
|
||||
./run_test.sh
|
||||
|
||||
To run just one particular file:
|
||||
|
||||
./run_test.sh markdownUtils # Don't add the .js extension
|
@@ -22,6 +22,8 @@ const os = require('os');
|
||||
const fs = require('fs-extra');
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
const Cache = require('lib/Cache');
|
||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
@@ -393,6 +395,12 @@ class Application extends BaseApplication {
|
||||
}
|
||||
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();
|
||||
|
||||
@@ -413,6 +421,8 @@ class Application extends BaseApplication {
|
||||
const tags = await Tag.allWithNotes();
|
||||
|
||||
ResourceService.runInBackground();
|
||||
|
||||
RevisionService.instance().runInBackground();
|
||||
|
||||
this.dispatch({
|
||||
type: 'TAG_UPDATE_ALL',
|
||||
|
@@ -102,7 +102,7 @@ function getFooter() {
|
||||
|
||||
output.push('WEBSITE');
|
||||
output.push('');
|
||||
output.push(INDENT + 'https://joplin.cozic.net');
|
||||
output.push(INDENT + 'https://joplinapp.org');
|
||||
|
||||
output.push('');
|
||||
|
||||
|
@@ -113,6 +113,11 @@ class Command extends BaseCommand {
|
||||
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.');
|
||||
@@ -125,6 +130,11 @@ class Command extends BaseCommand {
|
||||
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);
|
||||
@@ -154,11 +164,11 @@ class Command extends BaseCommand {
|
||||
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`.',
|
||||
});
|
||||
// tableFields.push({
|
||||
// name: 'tags',
|
||||
// type: Database.enumId('fieldType', 'text'),
|
||||
// description: 'Comma-separated list of tags. eg. `tag1,tag2`.',
|
||||
// });
|
||||
}
|
||||
|
||||
lines.push('# ' + toTitleCase(tableName));
|
||||
|
@@ -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,63 +64,28 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
if (args.command === 'decrypt') {
|
||||
while (true) {
|
||||
try {
|
||||
if (args.path) {
|
||||
const plainText = await EncryptionService.instance().decryptString(args.path);
|
||||
this.stdout(plainText);
|
||||
return;
|
||||
} else {
|
||||
if (process.stdin.isTTY) {
|
||||
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
|
||||
await DecryptionWorker.instance().start();
|
||||
this.stdout(_('Completed decryption.'));
|
||||
return;
|
||||
} else {
|
||||
// var repl = require("repl");
|
||||
// var r = repl.start("node> ");
|
||||
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.'));
|
||||
|
||||
const text = await new Promise((accept, reject) => {
|
||||
var buffer = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', function(chunk) {
|
||||
buffer += chunk;
|
||||
// process.stdout.write(chunk);
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
accept(buffer.trim());
|
||||
});
|
||||
});
|
||||
|
||||
if (text.length > 0) {
|
||||
var cipherText = text;
|
||||
try {
|
||||
var item = await BaseItem.unserialize(text);
|
||||
cipherText = item.encryption_cipher_text;
|
||||
} catch (error) {
|
||||
// we already got the pure cipher text
|
||||
}
|
||||
const plainText = await EncryptionService.instance().decryptString(cipherText);
|
||||
this.stdout(plainText);
|
||||
}
|
||||
return;
|
||||
while (true) {
|
||||
try {
|
||||
await DecryptionWorker.instance().start();
|
||||
break;
|
||||
} catch (error) {
|
||||
if (error.code === 'masterKeyNotLoaded') {
|
||||
const ok = await askForMasterKey(error);
|
||||
if (!ok) return;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'masterKeyNotLoaded') {
|
||||
const masterKeyId = error.masterKeyId;
|
||||
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
|
||||
if (!password) {
|
||||
this.stdout(_('Operation cancelled'));
|
||||
return;
|
||||
}
|
||||
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
|
||||
await EncryptionService.instance().loadMasterKeysFromSettings();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.stdout(_('Completed decryption.'));
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -114,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');
|
||||
@@ -215,4 +227,4 @@ class Command extends BaseCommand {
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
||||
module.exports = Command;
|
@@ -37,7 +37,7 @@ class Command extends BaseCommand {
|
||||
const stdoutWidth = app().commandStdoutMaxWidth();
|
||||
|
||||
if (args.command === 'shortcuts' || args.command === 'keymap') {
|
||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplin.cozic.net/terminal/#shortcuts'));
|
||||
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplinapp.org/terminal/#shortcuts'));
|
||||
this.stdout('');
|
||||
|
||||
if (app().gui().isDummy()) {
|
||||
|
@@ -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');
|
||||
|
2115
CliClient/locales/ar.po
Normal file
2145
CliClient/locales/bg_BG.po
Normal file
@@ -119,7 +119,7 @@ msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
|
||||
"`status` and `target-status`."
|
||||
"`status`, `decrypt-file` and `target-status`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter master password:"
|
||||
@@ -470,6 +470,10 @@ msgstr ""
|
||||
msgid "Fatal error:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "All potential ports are in use - please report the issue at %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The application has been authorised - you may now close this browser tab."
|
||||
msgstr ""
|
||||
@@ -508,6 +512,18 @@ msgstr ""
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sidebar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note body"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
@@ -515,7 +531,7 @@ msgstr ""
|
||||
msgid "PDF File"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgid "Synchronisation status"
|
||||
msgstr ""
|
||||
|
||||
msgid "New note"
|
||||
@@ -527,13 +543,63 @@ msgstr ""
|
||||
msgid "New notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "General Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Web clipper options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create note from template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create to-do from template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open template directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh templates"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Revision: %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "&File"
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print"
|
||||
msgid "Synchronise"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
@@ -543,7 +609,10 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit"
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy"
|
||||
@@ -564,19 +633,28 @@ msgstr ""
|
||||
msgid "Italic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert Date Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit in external editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search in all the notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search in current note"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgid "&View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle sidebar"
|
||||
@@ -585,22 +663,13 @@ msgstr ""
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgid "Focus"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation status"
|
||||
msgid "&Tools"
|
||||
msgstr ""
|
||||
|
||||
msgid "Web clipper options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption options"
|
||||
msgstr ""
|
||||
|
||||
msgid "General Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Help"
|
||||
msgid "&Help"
|
||||
msgstr ""
|
||||
|
||||
msgid "Website and documentation"
|
||||
@@ -609,14 +678,7 @@ msgstr ""
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
@@ -643,11 +705,11 @@ msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Your version: v%s"
|
||||
msgid "Your version: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "New version: v%s"
|
||||
msgid "New version: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
@@ -716,11 +778,14 @@ msgid ""
|
||||
"access Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
@@ -801,6 +866,9 @@ msgstr ""
|
||||
msgid "Encryption is:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
@@ -821,9 +889,6 @@ msgstr ""
|
||||
msgid "Add or remove tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Separate each tag by a comma."
|
||||
msgstr ""
|
||||
|
||||
msgid "Rename notebook:"
|
||||
msgstr ""
|
||||
|
||||
@@ -833,6 +898,9 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Template file:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
@@ -845,40 +913,12 @@ msgstr ""
|
||||
msgid "View them now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s - Copy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between note and to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to note type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy Markdown link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "No notes in here. Create one by clicking on \"New note\"."
|
||||
msgstr ""
|
||||
|
||||
@@ -892,9 +932,34 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note History"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -942,9 +1007,6 @@ msgstr ""
|
||||
msgid "Attach file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr ""
|
||||
|
||||
@@ -955,9 +1017,6 @@ msgstr ""
|
||||
msgid "Hyperlink"
|
||||
msgstr ""
|
||||
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Numbered List"
|
||||
msgstr ""
|
||||
|
||||
@@ -1013,32 +1072,70 @@ msgstr ""
|
||||
msgid "Clipper Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove this tag from all the notes?"
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Delete notebook \"%s\"?\n"
|
||||
"\n"
|
||||
"All notes and sub-notebooks within this notebook will also be deleted."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Remove tag \"%s\" from all notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove this search from the sidebar?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rename"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronise"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s - Copy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between note and to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to note type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy Markdown link"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Delete note \"%s\"?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Delete these %d notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Usage: %s"
|
||||
msgstr ""
|
||||
@@ -1143,12 +1240,22 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted items cannot be modified"
|
||||
msgstr ""
|
||||
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
msgid "updated date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflicts"
|
||||
msgstr ""
|
||||
|
||||
@@ -1159,12 +1266,6 @@ msgstr ""
|
||||
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
|
||||
msgstr ""
|
||||
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
msgid "updated date"
|
||||
msgstr ""
|
||||
|
||||
msgid "created date"
|
||||
msgstr ""
|
||||
|
||||
@@ -1179,6 +1280,63 @@ msgstr ""
|
||||
msgid "Cannot move note to \"%s\" notebook"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Attention: If you change this location, make sure you copy all your content "
|
||||
"to it before syncing, otherwise all files will be removed! See the FAQ for "
|
||||
"more details: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max concurrent connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
@@ -1197,6 +1355,12 @@ msgstr ""
|
||||
msgid "Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Solarized Light"
|
||||
msgstr ""
|
||||
|
||||
msgid "Solarized Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr ""
|
||||
|
||||
@@ -1209,6 +1373,9 @@ msgstr ""
|
||||
msgid "Reverse sort order"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sort notebooks by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr ""
|
||||
|
||||
@@ -1224,6 +1391,42 @@ msgstr ""
|
||||
msgid "When creating a new note:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable soft breaks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable math expressions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable footnotes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
@@ -1286,50 +1489,6 @@ msgid ""
|
||||
"If none is provided it will try to auto-detect the default editor."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The path to synchronise with when file system synchronisation is enabled. "
|
||||
"See `sync.target`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Attention: If you change this location, make sure you copy all your content "
|
||||
"to it before syncing, otherwise all files will be removed! See the FAQ for "
|
||||
"more details: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom TLS certificates"
|
||||
msgstr ""
|
||||
|
||||
@@ -1343,10 +1502,41 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr ""
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The tag \"%s\" already exists. Please choose a different name."
|
||||
msgstr ""
|
||||
@@ -1366,6 +1556,9 @@ msgstr ""
|
||||
msgid "Json Export Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
@@ -1389,11 +1582,10 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
@@ -1402,6 +1594,23 @@ msgid ""
|
||||
"(which is displayed in brackets above)."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be decrypted"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Joplin failed to decrypt these items multiple times, possibly because they "
|
||||
"are corrupted or too large. These items will remain on the device but Joplin "
|
||||
"will no longer attempt to decrypt them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sync status (synced items / total items)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1447,18 +1656,6 @@ msgstr ""
|
||||
msgid "Delete these notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export Debug Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption Config"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move to notebook..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1481,7 +1678,31 @@ msgstr ""
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel synchronisation"
|
||||
#, javascript-format
|
||||
msgid "Notebook: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted notebooks cannot be renamed"
|
||||
msgstr ""
|
||||
|
||||
msgid "New Notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
msgid "All notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking... Please wait."
|
||||
@@ -1517,6 +1738,49 @@ msgstr ""
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In order to use file system synchronisation your permission to write to "
|
||||
"external storage is required."
|
||||
msgstr ""
|
||||
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption Config"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sync Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
msgid "Creating report..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Export Debug Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixing search index..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Fix search index"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Use this to rebuild the search index if there is a problem with search. It "
|
||||
"may take a long time depending on the number of notes."
|
||||
msgstr ""
|
||||
|
||||
msgid "More information"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"To work correctly, the app needs the following permissions. Please enable "
|
||||
"them in your phone settings, in Apps > Joplin > Permissions"
|
||||
@@ -1536,10 +1800,6 @@ msgstr ""
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Joplin v%s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Database v%s"
|
||||
msgstr ""
|
||||
@@ -1578,6 +1838,9 @@ msgstr ""
|
||||
msgid "Edit notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter notebook title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
@@ -1601,10 +1864,30 @@ msgstr ""
|
||||
msgid "The Joplin mobile app does not currently support this type of link: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Links with protocol \"%s\" are not supported"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported image type: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Updated: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "View on map"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to source URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Attach..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose an option"
|
||||
msgstr ""
|
||||
|
||||
msgid "Take photo"
|
||||
msgstr ""
|
||||
|
||||
@@ -1623,19 +1906,16 @@ msgstr ""
|
||||
msgid "Convert to todo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide metadata"
|
||||
msgid "Properties"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show metadata"
|
||||
msgid "Add body"
|
||||
msgstr ""
|
||||
|
||||
msgid "View on map"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to source URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete notebook"
|
||||
msgid "Add title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login with OneDrive"
|
||||
@@ -1643,14 +1923,3 @@ msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
msgstr ""
|
||||
|
||||
msgid "You currently have no notebook. Create one by clicking on (+) button."
|
||||
msgstr ""
|
||||
|
||||
msgid "Welcome"
|
||||
msgstr ""
|
||||
|
1960
CliClient/locales/en_US.po
Normal file
1991
CliClient/locales/fa.po
Normal file
@@ -119,7 +119,7 @@ msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
|
||||
"`status` and `target-status`."
|
||||
"`status`, `decrypt-file` and `target-status`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter master password:"
|
||||
@@ -470,6 +470,10 @@ msgstr ""
|
||||
msgid "Fatal error:"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "All potential ports are in use - please report the issue at %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The application has been authorised - you may now close this browser tab."
|
||||
msgstr ""
|
||||
@@ -508,6 +512,18 @@ msgstr ""
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sidebar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note body"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
@@ -515,7 +531,7 @@ msgstr ""
|
||||
msgid "PDF File"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgid "Synchronisation status"
|
||||
msgstr ""
|
||||
|
||||
msgid "New note"
|
||||
@@ -527,13 +543,63 @@ msgstr ""
|
||||
msgid "New notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print"
|
||||
msgstr ""
|
||||
|
||||
msgid "General Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Web clipper options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create note from template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create to-do from template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open template directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh templates"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Revision: %s (%s)"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "&File"
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr ""
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export"
|
||||
msgstr ""
|
||||
|
||||
msgid "Print"
|
||||
msgid "Synchronise"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
@@ -543,7 +609,10 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit"
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
msgid "&Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy"
|
||||
@@ -564,19 +633,28 @@ msgstr ""
|
||||
msgid "Italic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert Date Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit in external editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search in all the notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search in current note"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgid "&View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle sidebar"
|
||||
@@ -585,22 +663,13 @@ msgstr ""
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgid "Focus"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation status"
|
||||
msgid "&Tools"
|
||||
msgstr ""
|
||||
|
||||
msgid "Web clipper options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption options"
|
||||
msgstr ""
|
||||
|
||||
msgid "General Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Help"
|
||||
msgid "&Help"
|
||||
msgstr ""
|
||||
|
||||
msgid "Website and documentation"
|
||||
@@ -609,14 +678,7 @@ msgstr ""
|
||||
msgid "Make a donation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr ""
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
@@ -643,11 +705,11 @@ msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Your version: v%s"
|
||||
msgid "Your version: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "New version: v%s"
|
||||
msgid "New version: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
@@ -716,11 +778,14 @@ msgid ""
|
||||
"access Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
@@ -801,6 +866,9 @@ msgstr ""
|
||||
msgid "Encryption is:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
@@ -821,9 +889,6 @@ msgstr ""
|
||||
msgid "Add or remove tags:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Separate each tag by a comma."
|
||||
msgstr ""
|
||||
|
||||
msgid "Rename notebook:"
|
||||
msgstr ""
|
||||
|
||||
@@ -833,6 +898,9 @@ msgstr ""
|
||||
msgid "Set alarm:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Template file:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
@@ -845,40 +913,12 @@ msgstr ""
|
||||
msgid "View them now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s - Copy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between note and to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to note type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy Markdown link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "No notes in here. Create one by clicking on \"New note\"."
|
||||
msgstr ""
|
||||
|
||||
@@ -892,9 +932,34 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note History"
|
||||
msgstr ""
|
||||
|
||||
msgid "Markup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
msgid "This note has no history"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr ""
|
||||
|
||||
@@ -942,9 +1007,6 @@ msgstr ""
|
||||
msgid "Attach file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr ""
|
||||
|
||||
@@ -955,9 +1017,6 @@ msgstr ""
|
||||
msgid "Hyperlink"
|
||||
msgstr ""
|
||||
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Numbered List"
|
||||
msgstr ""
|
||||
|
||||
@@ -1013,32 +1072,70 @@ msgstr ""
|
||||
msgid "Clipper Options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove this tag from all the notes?"
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Delete notebook \"%s\"?\n"
|
||||
"\n"
|
||||
"All notes and sub-notebooks within this notebook will also be deleted."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Remove tag \"%s\" from all notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove this search from the sidebar?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rename"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronise"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s - Copy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between note and to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to note type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch to to-do type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy Markdown link"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Delete note \"%s\"?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Delete these %d notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Usage: %s"
|
||||
msgstr ""
|
||||
@@ -1143,12 +1240,22 @@ msgstr ""
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted items cannot be modified"
|
||||
msgstr ""
|
||||
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
msgid "updated date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Conflicts"
|
||||
msgstr ""
|
||||
|
||||
@@ -1159,12 +1266,6 @@ msgstr ""
|
||||
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
|
||||
msgstr ""
|
||||
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
msgid "updated date"
|
||||
msgstr ""
|
||||
|
||||
msgid "created date"
|
||||
msgstr ""
|
||||
|
||||
@@ -1179,6 +1280,63 @@ msgstr ""
|
||||
msgid "Cannot move note to \"%s\" notebook"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Attention: If you change this location, make sure you copy all your content "
|
||||
"to it before syncing, otherwise all files will be removed! See the FAQ for "
|
||||
"more details: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max concurrent connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
@@ -1197,6 +1355,12 @@ msgstr ""
|
||||
msgid "Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Solarized Light"
|
||||
msgstr ""
|
||||
|
||||
msgid "Solarized Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr ""
|
||||
|
||||
@@ -1209,6 +1373,9 @@ msgstr ""
|
||||
msgid "Reverse sort order"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sort notebooks by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr ""
|
||||
|
||||
@@ -1224,6 +1391,42 @@ msgstr ""
|
||||
msgid "When creating a new note:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable soft breaks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable math expressions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable footnotes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr ""
|
||||
|
||||
@@ -1286,50 +1489,6 @@ msgid ""
|
||||
"If none is provided it will try to auto-detect the default editor."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The path to synchronise with when file system synchronisation is enabled. "
|
||||
"See `sync.target`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Attention: If you change this location, make sure you copy all your content "
|
||||
"to it before syncing, otherwise all files will be removed! See the FAQ for "
|
||||
"more details: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr ""
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Custom TLS certificates"
|
||||
msgstr ""
|
||||
|
||||
@@ -1343,10 +1502,41 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable note history"
|
||||
msgstr ""
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The tag \"%s\" already exists. Please choose a different name."
|
||||
msgstr ""
|
||||
@@ -1366,6 +1556,9 @@ msgstr ""
|
||||
msgid "Json Export Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
@@ -1389,11 +1582,10 @@ msgstr ""
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
@@ -1402,6 +1594,23 @@ msgid ""
|
||||
"(which is displayed in brackets above)."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be decrypted"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Joplin failed to decrypt these items multiple times, possibly because they "
|
||||
"are corrupted or too large. These items will remain on the device but Joplin "
|
||||
"will no longer attempt to decrypt them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sync status (synced items / total items)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1447,18 +1656,6 @@ msgstr ""
|
||||
msgid "Delete these notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export Debug Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption Config"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move to notebook..."
|
||||
msgstr ""
|
||||
|
||||
@@ -1481,7 +1678,31 @@ msgstr ""
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel synchronisation"
|
||||
#, javascript-format
|
||||
msgid "Notebook: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted notebooks cannot be renamed"
|
||||
msgstr ""
|
||||
|
||||
msgid "New Notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr ""
|
||||
|
||||
msgid "All notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notebooks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checking... Please wait."
|
||||
@@ -1517,6 +1738,49 @@ msgstr ""
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In order to use file system synchronisation your permission to write to "
|
||||
"external storage is required."
|
||||
msgstr ""
|
||||
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption Config"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sync Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
msgid "Creating report..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Export Debug Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixing search index..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Fix search index"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Use this to rebuild the search index if there is a problem with search. It "
|
||||
"may take a long time depending on the number of notes."
|
||||
msgstr ""
|
||||
|
||||
msgid "More information"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"To work correctly, the app needs the following permissions. Please enable "
|
||||
"them in your phone settings, in Apps > Joplin > Permissions"
|
||||
@@ -1536,10 +1800,6 @@ msgstr ""
|
||||
msgid "Joplin website"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Joplin v%s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Database v%s"
|
||||
msgstr ""
|
||||
@@ -1578,6 +1838,9 @@ msgstr ""
|
||||
msgid "Edit notebook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter notebook title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
@@ -1601,10 +1864,30 @@ msgstr ""
|
||||
msgid "The Joplin mobile app does not currently support this type of link: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Links with protocol \"%s\" are not supported"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported image type: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Updated: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "View on map"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to source URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Attach..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose an option"
|
||||
msgstr ""
|
||||
|
||||
msgid "Take photo"
|
||||
msgstr ""
|
||||
|
||||
@@ -1623,19 +1906,16 @@ msgstr ""
|
||||
msgid "Convert to todo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide metadata"
|
||||
msgid "Properties"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show metadata"
|
||||
msgid "Add body"
|
||||
msgstr ""
|
||||
|
||||
msgid "View on map"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Go to source URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete notebook"
|
||||
msgid "Add title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Login with OneDrive"
|
||||
@@ -1643,14 +1923,3 @@ msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
msgstr ""
|
||||
|
||||
msgid "You currently have no notebook. Create one by clicking on (+) button."
|
||||
msgstr ""
|
||||
|
||||
msgid "Welcome"
|
||||
msgstr ""
|
||||
|
2160
CliClient/locales/pl_PL.po
Normal file
@@ -123,7 +123,7 @@ msgstr "Notița nu este o sarcină: \"%s\""
|
||||
|
||||
msgid ""
|
||||
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
|
||||
"`status` and `target-status`."
|
||||
"`status`, `decrypt-file` and `target-status`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enter master password:"
|
||||
@@ -476,6 +476,10 @@ msgstr "Tastați `joplin help` pentru informații de utilizare."
|
||||
msgid "Fatal error:"
|
||||
msgstr "Eroare fatală:"
|
||||
|
||||
#, javascript-format
|
||||
msgid "All potential ports are in use - please report the issue at %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The application has been authorised - you may now close this browser tab."
|
||||
msgstr ""
|
||||
@@ -521,6 +525,20 @@ msgstr ""
|
||||
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr "Se exportă către \"%s\" în format \"%s\". Vă rugăm să așteotați..."
|
||||
|
||||
msgid "Sidebar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note list"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note title"
|
||||
msgstr "Titlul caietului de notițe:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note body"
|
||||
msgstr "Caiete de notițe"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
|
||||
msgstr ""
|
||||
@@ -528,8 +546,8 @@ msgstr ""
|
||||
msgid "PDF File"
|
||||
msgstr "Fișier PDF"
|
||||
|
||||
msgid "File"
|
||||
msgstr "Fișier"
|
||||
msgid "Synchronisation status"
|
||||
msgstr "Statusul sincronizării"
|
||||
|
||||
msgid "New note"
|
||||
msgstr "Adăugați o nouă notiță"
|
||||
@@ -540,14 +558,65 @@ msgstr "Adăugați o nouă sarcină"
|
||||
msgid "New notebook"
|
||||
msgstr "Adăugați un nou caiet de notițe"
|
||||
|
||||
msgid "Print"
|
||||
msgstr "Printați"
|
||||
|
||||
msgid "General Options"
|
||||
msgstr "Opțiuni Generale"
|
||||
|
||||
msgid "Encryption options"
|
||||
msgstr "Opțiuni de criptare"
|
||||
|
||||
msgid "Web clipper options"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create note from template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create to-do from template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Insert template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open template directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Refresh templates"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Revision: %s (%s)"
|
||||
msgstr "%s %s (%s)"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
|
||||
#, fuzzy
|
||||
msgid "&File"
|
||||
msgstr "Fișier"
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "Despre Joplin"
|
||||
|
||||
msgid "Preferences..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Verificați actualizările..."
|
||||
|
||||
msgid "Templates"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import"
|
||||
msgstr "Importați"
|
||||
|
||||
msgid "Export"
|
||||
msgstr "Exportați"
|
||||
|
||||
msgid "Print"
|
||||
msgstr "Printați"
|
||||
msgid "Synchronise"
|
||||
msgstr "Sincronizați"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Hide %s"
|
||||
@@ -556,7 +625,11 @@ msgstr "Ascundeți %s"
|
||||
msgid "Quit"
|
||||
msgstr "Ieșiți"
|
||||
|
||||
msgid "Edit"
|
||||
msgid "Close Window"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "&Edit"
|
||||
msgstr "Editați"
|
||||
|
||||
msgid "Copy"
|
||||
@@ -578,12 +651,21 @@ msgstr ""
|
||||
msgid "Italic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Code"
|
||||
msgstr "Cod"
|
||||
|
||||
msgid "Insert Date Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit in external editor"
|
||||
msgstr "Editați într-un editor extern"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Etichete"
|
||||
|
||||
msgid "Search in all the notes"
|
||||
msgstr "Căutați în toate notițele"
|
||||
|
||||
@@ -591,7 +673,8 @@ msgstr "Căutați în toate notițele"
|
||||
msgid "Search in current note"
|
||||
msgstr "Căutați în toate notițele"
|
||||
|
||||
msgid "View"
|
||||
#, fuzzy
|
||||
msgid "&View"
|
||||
msgstr "Vizualizați"
|
||||
|
||||
msgid "Toggle sidebar"
|
||||
@@ -600,22 +683,15 @@ msgstr ""
|
||||
msgid "Toggle editor layout"
|
||||
msgstr ""
|
||||
|
||||
msgid "Tools"
|
||||
msgstr "Unelte"
|
||||
|
||||
msgid "Synchronisation status"
|
||||
msgstr "Statusul sincronizării"
|
||||
|
||||
msgid "Web clipper options"
|
||||
msgid "Focus"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption options"
|
||||
msgstr "Opțiuni de criptare"
|
||||
#, fuzzy
|
||||
msgid "&Tools"
|
||||
msgstr "Unelte"
|
||||
|
||||
msgid "General Options"
|
||||
msgstr "Opțiuni Generale"
|
||||
|
||||
msgid "Help"
|
||||
#, fuzzy
|
||||
msgid "&Help"
|
||||
msgstr "Ajutor"
|
||||
|
||||
msgid "Website and documentation"
|
||||
@@ -624,15 +700,8 @@ msgstr "Website și documentație"
|
||||
msgid "Make a donation"
|
||||
msgstr "Faceți o donație"
|
||||
|
||||
msgid "Check for updates..."
|
||||
msgstr "Verificați actualizările..."
|
||||
|
||||
msgid "About Joplin"
|
||||
msgstr "Despre Joplin"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s %s (%s, %s)"
|
||||
msgstr "%s %s (%s, %s)"
|
||||
msgid "Toggle development tools"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Open %s"
|
||||
@@ -658,11 +727,11 @@ msgid "An update is available, do you want to download it now?"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Your version: v%s"
|
||||
msgid "Your version: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "New version: v%s"
|
||||
msgid "New version: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Yes"
|
||||
@@ -732,11 +801,14 @@ msgid ""
|
||||
"access Joplin."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Check synchronisation configuration"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Notes and settings are stored in: %s"
|
||||
msgid "Browse..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply"
|
||||
@@ -817,6 +889,10 @@ msgstr "Status"
|
||||
msgid "Encryption is:"
|
||||
msgstr "Criptarea este:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Usage"
|
||||
msgstr "Utilizare: %s"
|
||||
|
||||
msgid "Back"
|
||||
msgstr "Înapoi"
|
||||
|
||||
@@ -837,9 +913,6 @@ msgstr "Titlul caietului de notițe:"
|
||||
msgid "Add or remove tags:"
|
||||
msgstr "Adăugați ori eliminați etichete:"
|
||||
|
||||
msgid "Separate each tag by a comma."
|
||||
msgstr "Separați fiecare etichetă printr-o virgulă."
|
||||
|
||||
msgid "Rename notebook:"
|
||||
msgstr "Redenumiți caietul de notițe:"
|
||||
|
||||
@@ -849,6 +922,9 @@ msgstr "Redenumiți eticheta:"
|
||||
msgid "Set alarm:"
|
||||
msgstr "Setați alarma:"
|
||||
|
||||
msgid "Template file:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Layout"
|
||||
msgstr ""
|
||||
|
||||
@@ -861,42 +937,12 @@ msgstr "Câțiva itemi nu pot fi sincronizați."
|
||||
msgid "View them now"
|
||||
msgstr "Vizualizați-le acum"
|
||||
|
||||
msgid "Some items cannot be decrypted."
|
||||
msgstr "Câțiva itemi nu pot fi descriptați."
|
||||
msgid "One or more master keys need a password."
|
||||
msgstr ""
|
||||
|
||||
msgid "Set the password"
|
||||
msgstr "Setați parola"
|
||||
|
||||
msgid "Add or remove tags"
|
||||
msgstr "Adăugați ori eliminați etichete"
|
||||
|
||||
msgid "Duplicate"
|
||||
msgstr "Duplicați"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s - Copy"
|
||||
msgstr "%s - Copiați"
|
||||
|
||||
msgid "Switch between note and to-do type"
|
||||
msgstr "Schimbați între notiță și sarcină"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Switch to note type"
|
||||
msgstr "Schimbați între notiță și sarcină"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Switch to to-do type"
|
||||
msgstr "Schimbați între notiță și sarcină"
|
||||
|
||||
msgid "Copy Markdown link"
|
||||
msgstr "Copiați link-ul Markdown"
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Ștergeți"
|
||||
|
||||
msgid "Delete notes?"
|
||||
msgstr "Ștergeți notițele?"
|
||||
|
||||
msgid "No notes in here. Create one by clicking on \"New note\"."
|
||||
msgstr ""
|
||||
|
||||
@@ -910,9 +956,36 @@ msgstr ""
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note History"
|
||||
msgstr "Caiete de notițe"
|
||||
|
||||
msgid "Markup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Previous versions of this note"
|
||||
msgstr ""
|
||||
|
||||
msgid "Note properties"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "This note has no history"
|
||||
msgstr "Această notiță a fost modificată:"
|
||||
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click \"%s\" to restore the note. It will be copied in the notebook named "
|
||||
"\"%s\". The current version of the note will not be replaced or modified."
|
||||
msgstr ""
|
||||
|
||||
msgid "Open..."
|
||||
msgstr "Deschideți..."
|
||||
|
||||
@@ -960,9 +1033,6 @@ msgstr "Introduceți hyperlink"
|
||||
msgid "Attach file"
|
||||
msgstr "Atașați fișier"
|
||||
|
||||
msgid "Tags"
|
||||
msgstr "Etichete"
|
||||
|
||||
msgid "Set alarm"
|
||||
msgstr "Setați alarma"
|
||||
|
||||
@@ -973,9 +1043,6 @@ msgstr "În: %s"
|
||||
msgid "Hyperlink"
|
||||
msgstr "Hyperlink"
|
||||
|
||||
msgid "Code"
|
||||
msgstr "Cod"
|
||||
|
||||
msgid "Numbered List"
|
||||
msgstr "Listă numerotată"
|
||||
|
||||
@@ -1031,30 +1098,71 @@ msgstr "Opțiuni de criptare"
|
||||
msgid "Clipper Options"
|
||||
msgstr "Opțiuni Clipper"
|
||||
|
||||
msgid "Remove this tag from all the notes?"
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Delete notebook \"%s\"?\n"
|
||||
"\n"
|
||||
"All notes and sub-notebooks within this notebook will also be deleted."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Remove tag \"%s\" from all notes?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Remove this search from the sidebar?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr "Ștergeți"
|
||||
|
||||
msgid "Rename"
|
||||
msgstr "Redenumiți"
|
||||
|
||||
msgid "Synchronise"
|
||||
msgstr "Sincronizați"
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Notebooks"
|
||||
msgstr "Caiete de notițe"
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove tags"
|
||||
msgstr "Adăugați ori eliminați etichete"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Duplicate"
|
||||
msgstr "Duplicați"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Se decriptează itemi: %d/%d"
|
||||
msgid "%s - Copy"
|
||||
msgstr "%s - Copiați"
|
||||
|
||||
msgid "Switch between note and to-do type"
|
||||
msgstr "Schimbați între notiță și sarcină"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Switch to note type"
|
||||
msgstr "Schimbați între notiță și sarcină"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Switch to to-do type"
|
||||
msgstr "Schimbați între notiță și sarcină"
|
||||
|
||||
msgid "Copy Markdown link"
|
||||
msgstr "Copiați link-ul Markdown"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d"
|
||||
msgstr "Resurse: %d."
|
||||
msgid "Delete note \"%s\"?"
|
||||
msgstr "Ștergeți notițele?"
|
||||
|
||||
msgid "Please select where the sync status should be exported to"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Delete these %d notes?"
|
||||
msgstr "Ștergeți aceste notițe?"
|
||||
|
||||
msgid ""
|
||||
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
|
||||
"followed by a notebook name."
|
||||
msgstr ""
|
||||
|
||||
msgid "Goto Anything..."
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
@@ -1161,12 +1269,22 @@ msgstr "În progres"
|
||||
msgid "Synchronisation is already in progress. State: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Unknown item type downloaded - please upgrade Joplin to the latest version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encrypted"
|
||||
msgstr "Criptat"
|
||||
|
||||
msgid "Encrypted items cannot be modified"
|
||||
msgstr "Itemii criptați nu pot fi editați"
|
||||
|
||||
msgid "title"
|
||||
msgstr "titlu"
|
||||
|
||||
msgid "updated date"
|
||||
msgstr "data actualizării"
|
||||
|
||||
msgid "Conflicts"
|
||||
msgstr "Conflicte"
|
||||
|
||||
@@ -1177,12 +1295,6 @@ msgstr "Nu se poate muta caietul de notițe în această locație"
|
||||
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
|
||||
msgstr ""
|
||||
|
||||
msgid "title"
|
||||
msgstr "titlu"
|
||||
|
||||
msgid "updated date"
|
||||
msgstr "data actualizării"
|
||||
|
||||
msgid "created date"
|
||||
msgstr "data creării"
|
||||
|
||||
@@ -1197,6 +1309,63 @@ msgstr "Nu se poate copia notița în caietul de notițe \"%s\""
|
||||
msgid "Cannot move note to \"%s\" notebook"
|
||||
msgstr "Nu se poate muta notița în caietul de notițe \"%s\""
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Attention: If you change this location, make sure you copy all your content "
|
||||
"to it before syncing, otherwise all files will be removed! See the FAQ for "
|
||||
"more details: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr "URL NextCloud WebDAV"
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr "Nume utilizator Nextcloud"
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr "Parolă Nextcloud"
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr "URL-ul WebDAV"
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr "Nume utilizator WebDAV"
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr "Parolă WebDAV"
|
||||
|
||||
msgid "Attachment download behaviour"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
|
||||
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
|
||||
"the attachments are downloaded whether you open the note or not."
|
||||
msgstr ""
|
||||
|
||||
msgid "Always"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manual"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto"
|
||||
msgstr ""
|
||||
|
||||
msgid "Max concurrent connections"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language"
|
||||
msgstr "Limbă"
|
||||
|
||||
@@ -1215,6 +1384,12 @@ msgstr "Deschisă"
|
||||
msgid "Dark"
|
||||
msgstr "Închisă"
|
||||
|
||||
msgid "Solarized Light"
|
||||
msgstr ""
|
||||
|
||||
msgid "Solarized Dark"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uncompleted to-dos on top"
|
||||
msgstr ""
|
||||
|
||||
@@ -1227,6 +1402,10 @@ msgstr "Sortați notițele după"
|
||||
msgid "Reverse sort order"
|
||||
msgstr "Inversați ordinea sortării"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Sort notebooks by"
|
||||
msgstr "Sortați notițele după"
|
||||
|
||||
msgid "Save geo-location with notes"
|
||||
msgstr "Salvați geo-locația în notițe"
|
||||
|
||||
@@ -1242,6 +1421,43 @@ msgstr ""
|
||||
msgid "When creating a new note:"
|
||||
msgstr "Când este creată o nouă notiță:"
|
||||
|
||||
msgid "Enable soft breaks"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable math expressions"
|
||||
msgstr "Activați criptarea"
|
||||
|
||||
msgid "Enable ==mark== syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable footnotes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable table of contents extension"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ~sub~ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ^sup^ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable deflist syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable abbreviation syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable markdown emoji"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable ++insert++ syntax"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable multimarkdown table extension"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show tray icon"
|
||||
msgstr "Afișați iconița coșul de gunoi"
|
||||
|
||||
@@ -1305,50 +1521,6 @@ msgid ""
|
||||
"If none is provided it will try to auto-detect the default editor."
|
||||
msgstr ""
|
||||
|
||||
msgid "Show advanced options"
|
||||
msgstr "Afișați opțiunile avansate"
|
||||
|
||||
msgid "Synchronisation target"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The target to synchonise to. Each sync target may have additional parameters "
|
||||
"which are named as `sync.NUM.NAME` (all documented below)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Directory to synchronise with (absolute path)"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"The path to synchronise with when file system synchronisation is enabled. "
|
||||
"See `sync.target`."
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud WebDAV URL"
|
||||
msgstr "URL NextCloud WebDAV"
|
||||
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Attention: If you change this location, make sure you copy all your content "
|
||||
"to it before syncing, otherwise all files will be removed! See the FAQ for "
|
||||
"more details: %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "Nextcloud username"
|
||||
msgstr "Nume utilizator Nextcloud"
|
||||
|
||||
msgid "Nextcloud password"
|
||||
msgstr "Parolă Nextcloud"
|
||||
|
||||
msgid "WebDAV URL"
|
||||
msgstr "URL-ul WebDAV"
|
||||
|
||||
msgid "WebDAV username"
|
||||
msgstr "Nume utilizator WebDAV"
|
||||
|
||||
msgid "WebDAV password"
|
||||
msgstr "Parolă WebDAV"
|
||||
|
||||
msgid "Custom TLS certificates"
|
||||
msgstr "Certificate TLS cusomizate"
|
||||
|
||||
@@ -1362,10 +1534,46 @@ msgstr ""
|
||||
msgid "Ignore TLS certificate errors"
|
||||
msgstr "Ignoră erorile de certificat TLS"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enable note history"
|
||||
msgstr "Activați criptarea"
|
||||
|
||||
msgid "days"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "%d days"
|
||||
msgstr ""
|
||||
|
||||
msgid "Keep note history for"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Invalid option value: \"%s\". Possible values are: %s."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "General"
|
||||
msgstr "Opțiuni Generale"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Synchronisation"
|
||||
msgstr "Statusul sincronizării"
|
||||
|
||||
msgid "Appearance"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Note"
|
||||
msgstr "Caiete de notițe"
|
||||
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Application"
|
||||
msgstr "Duplicați"
|
||||
|
||||
#, javascript-format
|
||||
msgid "The tag \"%s\" already exists. Please choose a different name."
|
||||
msgstr ""
|
||||
@@ -1385,6 +1593,9 @@ msgstr ""
|
||||
msgid "Json Export Directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "File"
|
||||
msgstr "Fișier"
|
||||
|
||||
msgid "Directory"
|
||||
msgstr ""
|
||||
|
||||
@@ -1408,19 +1619,36 @@ msgstr "Nu există date de exportat."
|
||||
msgid "Please specify the notebook where the notes should be imported to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Restored Notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Items that cannot be synchronised"
|
||||
msgstr "Itemii nu pot fi sincronizați"
|
||||
|
||||
#, javascript-format
|
||||
msgid "%s (%s): %s"
|
||||
msgstr "%s (%s): %s"
|
||||
|
||||
msgid ""
|
||||
"These items will remain on the device but will not be uploaded to the sync "
|
||||
"target. In order to find these items, either search for the title or the ID "
|
||||
"(which is displayed in brackets above)."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "%s (%s) could not be uploaded: %s"
|
||||
msgstr "Acest fișier nu a putut fi deschis: %s"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Item \"%s\" could not be downloaded: %s"
|
||||
msgstr "Acest fișier nu a putut fi deschis: %s"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Items that cannot be decrypted"
|
||||
msgstr "Itemii nu pot fi sincronizați"
|
||||
|
||||
msgid ""
|
||||
"Joplin failed to decrypt these items multiple times, possibly because they "
|
||||
"are corrupted or too large. These items will remain on the device but Joplin "
|
||||
"will no longer attempt to decrypt them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Sync status (synced items / total items)"
|
||||
msgstr ""
|
||||
|
||||
@@ -1466,18 +1694,6 @@ msgstr ""
|
||||
msgid "Delete these notes?"
|
||||
msgstr "Ștergeți aceste notițe?"
|
||||
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
msgid "Export Debug Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Encryption Config"
|
||||
msgstr ""
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "Configurare"
|
||||
|
||||
msgid "Move to notebook..."
|
||||
msgstr "Mutați în caietul de notițe..."
|
||||
|
||||
@@ -1501,8 +1717,35 @@ msgstr "Selectați data"
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmați"
|
||||
|
||||
msgid "Cancel synchronisation"
|
||||
msgstr "Amânați sincronizarea"
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Notebook: %s"
|
||||
msgstr "Caiete de notițe"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Encrypted notebooks cannot be renamed"
|
||||
msgstr "Itemii criptați nu pot fi editați"
|
||||
|
||||
#, fuzzy
|
||||
msgid "New Notebook"
|
||||
msgstr "Adăugați un nou caiet de notițe"
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "Configurare"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Decrypting items: %d/%d"
|
||||
msgstr "Se decriptează itemi: %d/%d"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Fetching resources: %d/%d"
|
||||
msgstr "Resurse: %d/%d"
|
||||
|
||||
#, fuzzy
|
||||
msgid "All notes"
|
||||
msgstr "notiță"
|
||||
|
||||
msgid "Notebooks"
|
||||
msgstr "Caiete de notițe"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Checking... Please wait."
|
||||
@@ -1538,6 +1781,54 @@ msgstr "Etichete noi:"
|
||||
msgid "Type new tags or select from list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"In order to use file system synchronisation your permission to write to "
|
||||
"external storage is required."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Information"
|
||||
msgstr "Configurare"
|
||||
|
||||
msgid "Encryption Config"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Tools"
|
||||
msgstr "Unelte"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Sync Status"
|
||||
msgstr "Status"
|
||||
|
||||
msgid "Log"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Creating report..."
|
||||
msgstr "Se crează un %s..."
|
||||
|
||||
msgid "Export Debug Report"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixing search index..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Fix search index"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Use this to rebuild the search index if there is a problem with search. It "
|
||||
"may take a long time depending on the number of notes."
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "More information"
|
||||
msgstr "Configurare"
|
||||
|
||||
msgid ""
|
||||
"To work correctly, the app needs the following permissions. Please enable "
|
||||
"them in your phone settings, in Apps > Joplin > Permissions"
|
||||
@@ -1557,10 +1848,6 @@ msgstr ""
|
||||
msgid "Joplin website"
|
||||
msgstr "Website Joplin"
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Joplin v%s"
|
||||
msgstr "Website Joplin"
|
||||
|
||||
#, javascript-format
|
||||
msgid "Database v%s"
|
||||
msgstr ""
|
||||
@@ -1599,6 +1886,10 @@ msgstr "Caietul de notițe nu a puut fi salvat: %s"
|
||||
msgid "Edit notebook"
|
||||
msgstr "Editați caietul de notițe"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Enter notebook title"
|
||||
msgstr "Titlul caietului de notițe:"
|
||||
|
||||
msgid "Show all"
|
||||
msgstr "Afișați tot"
|
||||
|
||||
@@ -1622,10 +1913,32 @@ msgstr "Niciun item cu ID-ul %s"
|
||||
msgid "The Joplin mobile app does not currently support this type of link: %s"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Links with protocol \"%s\" are not supported"
|
||||
msgstr ""
|
||||
|
||||
#, javascript-format
|
||||
msgid "Unsupported image type: %s"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy, javascript-format
|
||||
msgid "Updated: %s"
|
||||
msgstr "Actualizat: %d."
|
||||
|
||||
msgid "View on map"
|
||||
msgstr "Vizualizați pe hartă"
|
||||
|
||||
msgid "Go to source URL"
|
||||
msgstr ""
|
||||
|
||||
#, fuzzy
|
||||
msgid "Attach..."
|
||||
msgstr "Caută..."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Choose an option"
|
||||
msgstr "Afișați opțiunile avansate"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Take photo"
|
||||
msgstr "Atașează imagine"
|
||||
@@ -1645,20 +1958,18 @@ msgstr ""
|
||||
msgid "Convert to todo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide metadata"
|
||||
msgstr "Ascundeți metadatele"
|
||||
|
||||
msgid "Show metadata"
|
||||
msgstr "Afișați metadatele"
|
||||
|
||||
msgid "View on map"
|
||||
msgstr "Vizualizați pe hartă"
|
||||
|
||||
msgid "Go to source URL"
|
||||
msgid "Properties"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete notebook"
|
||||
msgstr "Ștergeți caiet de notițe"
|
||||
msgid "Add body"
|
||||
msgstr ""
|
||||
|
||||
msgid "Edit"
|
||||
msgstr "Editați"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Add title"
|
||||
msgstr "titlu"
|
||||
|
||||
# vezi context
|
||||
msgid "Login with OneDrive"
|
||||
@@ -1667,18 +1978,39 @@ msgstr "Autentificare prin OneDrive"
|
||||
msgid "Search"
|
||||
msgstr "Caută"
|
||||
|
||||
msgid ""
|
||||
"Click on the (+) button to create a new note or notebook. Click on the side "
|
||||
"menu to access your existing notebooks."
|
||||
msgstr ""
|
||||
#~ msgid "Cancel synchronisation"
|
||||
#~ msgstr "Amânați sincronizarea"
|
||||
|
||||
msgid "You currently have no notebook. Create one by clicking on (+) button."
|
||||
msgstr ""
|
||||
"Nu aveți niciun caiet de notițe creat. Creați unul făcând click pe butonnul "
|
||||
"(+)."
|
||||
#~ msgid "Hide metadata"
|
||||
#~ msgstr "Ascundeți metadatele"
|
||||
|
||||
msgid "Welcome"
|
||||
msgstr "Bine ați venit"
|
||||
#~ msgid "Show metadata"
|
||||
#~ msgstr "Afișați metadatele"
|
||||
|
||||
#~ msgid "Delete notebook"
|
||||
#~ msgstr "Ștergeți caiet de notițe"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "You currently have no notebook. Create one by clicking on (+) button."
|
||||
#~ msgstr ""
|
||||
#~ "Nu aveți niciun caiet de notițe creat. Creați unul făcând click pe "
|
||||
#~ "butonnul (+)."
|
||||
|
||||
#~ msgid "Welcome"
|
||||
#~ msgstr "Bine ați venit"
|
||||
|
||||
#~ msgid "Separate each tag by a comma."
|
||||
#~ msgstr "Separați fiecare etichetă printr-o virgulă."
|
||||
|
||||
#~ msgid "Some items cannot be decrypted."
|
||||
#~ msgstr "Câțiva itemi nu pot fi descriptați."
|
||||
|
||||
#~ msgid "%s (%s): %s"
|
||||
#~ msgstr "%s (%s): %s"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "Joplin v%s"
|
||||
#~ msgstr "Website Joplin"
|
||||
|
||||
#~ msgid "State: %s."
|
||||
#~ msgstr "Statut: %s."
|
||||
|
2141
CliClient/locales/sr_RS.po
Normal file
2130
CliClient/locales/tr_TR.po
Normal file
743
CliClient/package-lock.json
generated
@@ -20,7 +20,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "1.0.120",
|
||||
"version": "1.0.140",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
@@ -33,7 +33,9 @@
|
||||
"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",
|
||||
@@ -41,8 +43,8 @@
|
||||
"html-minifier": "^3.5.15",
|
||||
"image-data-uri": "^2.0.0",
|
||||
"image-type": "^3.0.0",
|
||||
"joplin-turndown": "^4.0.9",
|
||||
"joplin-turndown-plugin-gfm": "^1.0.7",
|
||||
"joplin-turndown": "^4.0.17",
|
||||
"joplin-turndown-plugin-gfm": "^1.0.8",
|
||||
"jssha": "^2.3.0",
|
||||
"levenshtein": "^1.0.5",
|
||||
"lodash": "^4.17.4",
|
||||
@@ -60,9 +62,9 @@
|
||||
"redux": "^3.7.2",
|
||||
"sax": "^1.2.2",
|
||||
"server-destroy": "^1.0.1",
|
||||
"sharp": "^0.20.8",
|
||||
"sharp": "^0.22.1",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"sqlite3": "^4.0.1",
|
||||
"sqlite3": "^4.0.7",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"strip-ansi": "^4.0.0",
|
||||
|
@@ -3,5 +3,4 @@ set -e
|
||||
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
bash "$CLIENT_DIR/build.sh" && node "$CLIENT_DIR/build/main.js" --profile ~/Temp/TestNotes2 --stack-trace-enabled --log-level debug --env dev "$@"
|
||||
|
||||
# bash $CLIENT_DIR/build.sh && NODE_PATH="$CLIENT_DIR/build/" node build/main.js --profile ~/.config/joplin --stack-trace-enabled --log-level debug "$@"
|
||||
# bash "$CLIENT_DIR/build.sh" && node "$CLIENT_DIR/build/main.js" --profile ~/.config/joplin --stack-trace-enabled --log-level debug --env dev "$@"
|
@@ -22,20 +22,26 @@ 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_Note.js
|
||||
npm test tests-build/models_Tag.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/StringUtils.js
|
||||
npm test tests-build/services_InteropService.js
|
||||
npm test tests-build/services_KvStore.js
|
||||
npm test tests-build/services_ResourceService.js
|
||||
npm test tests-build/urlUtils.js
|
||||
npm test tests-build/encryption.js
|
||||
npm test tests-build/services_rest_Api.js
|
||||
npm test tests-build/synchronizer.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/TaskQueue.js
|
||||
npm test tests-build/synchronizer.js
|
||||
npm test tests-build/urlUtils.js
|
@@ -35,7 +35,7 @@ describe('EnexToMd', function() {
|
||||
const htmlPath = basePath + '/' + htmlFilename;
|
||||
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
|
||||
|
||||
// if (htmlFilename !== 'text2.html') continue;
|
||||
// if (htmlFilename !== 'multiline_inner_text.html') continue;
|
||||
|
||||
const html = await shim.fsDriver().readFile(htmlPath);
|
||||
let expectedMd = await shim.fsDriver().readFile(mdPath);
|
||||
|
@@ -37,12 +37,22 @@ describe('HtmlToMd', function() {
|
||||
const htmlPath = basePath + '/' + htmlFilename;
|
||||
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
|
||||
|
||||
// if (htmlFilename !== 'picture.html') continue;
|
||||
// if (htmlFilename !== 'mathjax_block.html') continue;
|
||||
|
||||
const htmlToMdOptions = {}
|
||||
|
||||
if (htmlFilename === 'anchor_local.html') {
|
||||
// Normally the list of anchor names in the document are retrieved from the HTML code
|
||||
// This is straightforward when the document is still in DOM format, as with the clipper,
|
||||
// but otherwise it would need to be somehow parsed out from the HTML. Here we just
|
||||
// hard code the anchors that we know are in the file.
|
||||
htmlToMdOptions.anchorNames = ['first', 'second']
|
||||
}
|
||||
|
||||
const html = await shim.fsDriver().readFile(htmlPath);
|
||||
let expectedMd = await shim.fsDriver().readFile(mdPath);
|
||||
|
||||
let actualMd = await htmlToMd.parse('<div>' + html + '</div>', []);
|
||||
let actualMd = await htmlToMd.parse('<div>' + html + '</div>', htmlToMdOptions);
|
||||
|
||||
if (os.EOL === '\r\n') {
|
||||
expectedMd = expectedMd.replace(/\r\n/g, '\n')
|
||||
|
@@ -20,9 +20,9 @@ describe('StringUtils', function() {
|
||||
[['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 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'],
|
||||
// [[{ 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++) {
|
||||
@@ -36,7 +36,7 @@ describe('StringUtils', function() {
|
||||
|
||||
const actual = StringUtils.surroundKeywords(keywords, input, prefix, suffix);
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual).toBe(expected, 'Test case ' + i);
|
||||
}
|
||||
|
||||
done();
|
||||
|
57
CliClient/tests/TaskQueue.js
Normal file
@@ -0,0 +1,57 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const TaskQueue = require('lib/TaskQueue.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('TaskQueue', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should queue and execute tasks', asyncTest(async () => {
|
||||
const queue = new TaskQueue();
|
||||
|
||||
queue.push(1, async () => { await sleep(0.5); return 'a'; });
|
||||
queue.push(2, async () => { await sleep(0.5); return 'b'; });
|
||||
queue.push(3, async () => { await sleep(0.5); return 'c'; });
|
||||
|
||||
const results = [];
|
||||
|
||||
results.push(await queue.waitForResult(1));
|
||||
results.push(await queue.waitForResult(2));
|
||||
results.push(await queue.waitForResult(3));
|
||||
|
||||
expect(results[0].id).toBe(1);
|
||||
expect(results[0].result).toBe('a');
|
||||
expect(results[1].id).toBe(2);
|
||||
expect(results[1].result).toBe('b');
|
||||
expect(results[2].id).toBe(3);
|
||||
expect(results[2].result).toBe('c');
|
||||
}));
|
||||
|
||||
it('should handle errors', asyncTest(async () => {
|
||||
const queue = new TaskQueue();
|
||||
|
||||
queue.push(1, async () => { await sleep(0.5); return 'a'; });
|
||||
queue.push(2, async () => { await sleep(0.5); throw new Error('e'); });
|
||||
|
||||
const results = [];
|
||||
|
||||
results.push(await queue.waitForResult(1));
|
||||
results.push(await queue.waitForResult(2));
|
||||
|
||||
expect(results[0].id).toBe(1);
|
||||
expect(results[0].result).toBe('a');
|
||||
expect(results[1].id).toBe(2);
|
||||
expect(!results[1].result).toBe(true);
|
||||
expect(results[1].error.message).toBe('e');
|
||||
}));
|
||||
|
||||
});
|
@@ -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/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
@@ -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
|
5
CliClient/tests/enex_to_md/multiline_attribute.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<img src="https://joplinapp.org/images/Icon512.png" alt="multiple
|
||||
lines
|
||||
are
|
||||
possible
|
||||
I guess"/><img src="https://joplinapp.org/images/Icon512.png" alt="This should ] be escaped"/>
|
1
CliClient/tests/enex_to_md/multiline_attribute.md
Normal file
@@ -0,0 +1 @@
|
||||
![This should \] be escaped](https://joplinapp.org/images/Icon512.png)
|
7
CliClient/tests/enex_to_md/multiline_inner_text.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div>Sometimes Evernote
|
||||
wraps lines inside blocks</div>
|
||||
<div>Sometimes it doesn't wrap them</div>
|
||||
<pre>But
|
||||
careful
|
||||
with
|
||||
pre tags</pre>
|
6
CliClient/tests/enex_to_md/multiline_inner_text.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Sometimes Evernote wraps lines inside blocks
|
||||
Sometimes it doesn't wrap them
|
||||
But
|
||||
careful
|
||||
with
|
||||
pre tags
|
105
CliClient/tests/htmlUtils.js
Normal file
@@ -0,0 +1,105 @@
|
||||
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 htmlUtils = require('lib/htmlUtils.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('htmlUtils', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should extract image URLs', async (done) => {
|
||||
const testCases = [
|
||||
['<img src="http://test.com/img.png"/>', ['http://test.com/img.png']],
|
||||
['<img src="http://test.com/img.png"/> <img src="http://test.com/img2.png"/>', ['http://test.com/img.png', 'http://test.com/img2.png']],
|
||||
['<img src="http://test.com/img.png" alt="testing" >', ['http://test.com/img.png']],
|
||||
['nothing here', []],
|
||||
['', []],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const md = testCases[i][0];
|
||||
const expected = testCases[i][1];
|
||||
|
||||
expect(htmlUtils.extractImageUrls(md).join(' ')).toBe(expected.join(' '));
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should replace image URLs', async (done) => {
|
||||
const testCases = [
|
||||
['<img src="http://test.com/img.png"/>', ['http://other.com/img2.png'], '<img src="http://other.com/img2.png"/>'],
|
||||
['<img src="http://test.com/img.png"/> <img src="http://test.com/img2.png"/>', ['http://other.com/img2.png', 'http://other.com/img3.png'], '<img src="http://other.com/img2.png"/> <img src="http://other.com/img3.png"/>'],
|
||||
['<img src="http://test.com/img.png" alt="testing" >', ['http://other.com/img.png'], '<img src="http://other.com/img.png" alt="testing" >'],
|
||||
];
|
||||
|
||||
const callback = (urls) => {
|
||||
let i = -1;
|
||||
|
||||
return function(src) {
|
||||
i++;
|
||||
return urls[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const md = testCases[i][0];
|
||||
const r = htmlUtils.replaceImageUrls(md, callback(testCases[i][1]));
|
||||
expect(r.trim()).toBe(testCases[i][2].trim());
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should encode attributes', async (done) => {
|
||||
const testCases = [
|
||||
[{ a: 'one', b: 'two' }, 'a="one" b="two"'],
|
||||
[{ a: 'one&two' }, 'a="one&two"'],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const attrs = testCases[i][0];
|
||||
const expected = testCases[i][1];
|
||||
expect(htmlUtils.attributesHtml(attrs)).toBe(expected);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should prepend a base URL', async (done) => {
|
||||
const testCases = [
|
||||
[
|
||||
'<a href="a.html">Something</a>',
|
||||
'http://test.com',
|
||||
'<a href="http://test.com/a.html">Something</a>',
|
||||
],
|
||||
[
|
||||
'<a href="a.html">a</a> <a href="b.html">b</a>',
|
||||
'http://test.com',
|
||||
'<a href="http://test.com/a.html">a</a> <a href="http://test.com/b.html">b</a>',
|
||||
],
|
||||
[
|
||||
'<a href="a.html">a</a> <a href="b.html">b</a>',
|
||||
'http://test.com',
|
||||
'<a href="http://test.com/a.html">a</a> <a href="http://test.com/b.html">b</a>',
|
||||
],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const html = testCases[i][0];
|
||||
const baseUrl = testCases[i][1];
|
||||
const expected = testCases[i][2];
|
||||
expect(htmlUtils.prependBaseUrl(html, baseUrl)).toBe(expected);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
6
CliClient/tests/html_to_md/anchor_local.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<p><a href="#first">First</a></p>
|
||||
<p><a href="#second">Second</a></p>
|
||||
<p>Third</p>
|
||||
<p><a name="first"></a>First</p>
|
||||
<p><a id="second"></a>Second</p>
|
||||
<p><a id="third"></a>Third</p>
|
11
CliClient/tests/html_to_md/anchor_local.md
Normal file
@@ -0,0 +1,11 @@
|
||||
[First](#first)
|
||||
|
||||
[Second](#second)
|
||||
|
||||
Third
|
||||
|
||||
<a id="first"></a>First
|
||||
|
||||
<a id="second"></a>Second
|
||||
|
||||
Third
|
@@ -1 +1 @@
|
||||
<a href="https://joplin.cozic.net"><h1 id="joplin"><img class="title-icon" src="https://joplin.cozic.net/images/Icon512.png">oplin</h1></a>
|
||||
<a href="https://joplinapp.org"><h1 id="joplin"><img class="title-icon" src="https://joplinapp.org/images/Icon512.png">oplin</h1></a>
|
@@ -1 +1 @@
|
||||
[# oplin](https://joplin.cozic.net)
|
||||
[# oplin](https://joplinapp.org)
|
@@ -1 +1 @@
|
||||
[Some text]()
|
||||
Some text
|
@@ -1,5 +1,7 @@
|
||||
def ma_fonction():
|
||||
"""
|
||||
C'est une super fonction
|
||||
"""
|
||||
pass
|
||||
```
|
||||
def ma_fonction():
|
||||
"""
|
||||
C'est une super fonction
|
||||
"""
|
||||
pass
|
||||
```
|
2
CliClient/tests/html_to_md/code_2.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<pre style="font-family: Menlo, Monaco, Consolas, "Courier New", monospace;"><strong><font color="#008080">thatsCode();</font></strong></pre>
|
||||
<pre>thatsJustPre(); // In that case we do not have enough info to know if it is a codeblock or not, so we leave it as plain text</pre>
|
5
CliClient/tests/html_to_md/code_2.md
Normal file
@@ -0,0 +1,5 @@
|
||||
```
|
||||
thatsCode();
|
||||
```
|
||||
|
||||
thatsJustPre(); // In that case we do not have enough info to know if it is a codeblock or not, so we leave it as plain text
|
1
CliClient/tests/html_to_md/mathjax_block.html
Normal file
5
CliClient/tests/html_to_md/mathjax_block.md
Normal file
@@ -0,0 +1,5 @@
|
||||
_Block formulas_ are surrounded by double dollar signs. For example, `$$x = \frac{-b \pm \sqrt{b^2 - 4ac} }{2a}$$` renders, _on a separate line_, as
|
||||
|
||||
$$
|
||||
x = \frac{-b \pm \sqrt{b^2 - 4ac} }{2a}.
|
||||
$$
|
1
CliClient/tests/html_to_md/mathjax_inline.html
Normal file
@@ -0,0 +1 @@
|
||||
<p><em>Inline formulas</em> are surrounded by single dollar signs. For example, <code>$f(x) = ax^2 + bx + c$</code> renders as <span class="MathJax_Preview" style="color: inherit;"></span><span class="MathJax" id="MathJax-Element-2-Frame" tabindex="0" style="position: relative;" data-mathml="<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><mi>a</mi><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><mi>b</mi><mi>x</mi><mo>+</mo><mi>c</mi></math>" role="presentation"><nobr aria-hidden="true"><span class="math" id="MathJax-Span-26" style="width: 9.372em; display: inline-block;"><span style="display: inline-block; position: relative; width: 8.408em; height: 0px; font-size: 111%;"><span style="position: absolute; clip: rect(1.562em, 1008.39em, 2.95em, -1000em); top: -2.583em; left: 0em;"><span class="mrow" id="MathJax-Span-27"><span class="mi" id="MathJax-Span-28" style="font-family: STIXGeneral; font-style: italic;">𝑓<span style="display: inline-block; overflow: hidden; height: 1px; width: 0.06em;"></span></span><span class="mo" id="MathJax-Span-29" style="font-family: STIXGeneral;">(</span><span class="mi" id="MathJax-Span-30" style="font-family: STIXGeneral; font-style: italic;">𝑥</span><span class="mo" id="MathJax-Span-31" style="font-family: STIXGeneral;">)</span><span class="mo" id="MathJax-Span-32" style="font-family: STIXGeneral; padding-left: 0.313em;">=</span><span class="mi" id="MathJax-Span-33" style="font-family: STIXGeneral; font-style: italic; padding-left: 0.313em;">𝑎</span><span class="msubsup" id="MathJax-Span-34"><span style="display: inline-block; position: relative; width: 0.979em; height: 0px;"><span style="position: absolute; clip: rect(3.403em, 1000.51em, 4.213em, -1000em); top: -4.024em; left: 0em;"><span class="mi" id="MathJax-Span-35" style="font-family: STIXGeneral; font-style: italic;">𝑥</span><span style="display: inline-block; width: 0px; height: 4.024em;"></span></span><span style="position: absolute; top: -4.387em; left: 0.55em;"><span class="mn" id="MathJax-Span-36" style="font-size: 70.7%; font-family: STIXGeneral;">2</span><span style="display: inline-block; width: 0px; height: 4.024em;"></span></span></span></span><span class="mo" id="MathJax-Span-37" style="font-family: STIXGeneral; padding-left: 0.25em;">+</span><span class="mi" id="MathJax-Span-38" style="font-family: STIXGeneral; font-style: italic; padding-left: 0.25em;">𝑏</span><span class="mi" id="MathJax-Span-39" style="font-family: STIXGeneral; font-style: italic;">𝑥</span><span class="mo" id="MathJax-Span-40" style="font-family: STIXGeneral; padding-left: 0.25em;">+</span><span class="mi" id="MathJax-Span-41" style="font-family: STIXGeneral; font-style: italic; padding-left: 0.25em;">𝑐</span></span><span style="display: inline-block; width: 0px; height: 2.583em;"></span></span></span><span style="display: inline-block; overflow: hidden; vertical-align: -0.274em; border-left: 0px solid; width: 0px; height: 1.274em;"></span></span></nobr><span class="MJX_Assistive_MathML" role="presentation"><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><mi>a</mi><msup><mi>x</mi><mn>2</mn></msup><mo>+</mo><mi>b</mi><mi>x</mi><mo>+</mo><mi>c</mi></math></span></span><script type="math/tex" id="MathJax-Element-2">f(x)=ax^2+bx+c</script>.</p>
|
1
CliClient/tests/html_to_md/mathjax_inline.md
Normal file
@@ -0,0 +1 @@
|
||||
_Inline formulas_ are surrounded by single dollar signs. For example, `$f(x) = ax^2 + bx + c$` renders as $f(x)=ax^2+bx+c$.
|
42
CliClient/tests/html_to_md/table_with_empty_header.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Official Things</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://nim-lang.org">Web Site</a></td>
|
||||
<td>The project’s entry point</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/nim-lang/nim">Source</a></td>
|
||||
<td>The github project</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/nim-lang/nimble">nimble</a></td>
|
||||
<td>The nim package manager</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/dom96/choosenim">choosenim</a></td>
|
||||
<td>Toolchain installer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Community</strong></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://forum.nim-lang.org">Forums</a></td>
|
||||
<td>An async discussion board</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
9
CliClient/tests/html_to_md/table_with_empty_header.md
Normal file
@@ -0,0 +1,9 @@
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Official Things** | |
|
||||
| [Web Site](https://nim-lang.org) | The project’s entry point |
|
||||
| [Source](https://github.com/nim-lang/nim) | The github project |
|
||||
| [nimble](https://github.com/nim-lang/nimble) | The nim package manager |
|
||||
| [choosenim](https://github.com/dom96/choosenim) | Toolchain installer |
|
||||
| **Community** | |
|
||||
| [Forums](https://forum.nim-lang.org) | An async discussion board |
|
@@ -6,4 +6,6 @@ Some text, not an image, so it should remain escaped:
|
||||
|
||||
But this is code so it can be unescaped:
|
||||
|
||||
<img src="http://test.com/image.png" />
|
||||
```
|
||||
<img src="http://test.com/image.png" />
|
||||
```
|
@@ -47,5 +47,20 @@ describe('models_BaseItem', function() {
|
||||
|
||||
expect('ignore_me' in unserialized).toBe(false);
|
||||
}));
|
||||
|
||||
it('should not modify title when unserializing', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "" });
|
||||
let folder2 = await Folder.save({ title: "folder1" });
|
||||
|
||||
let serialized1 = await Folder.serialize(folder1);
|
||||
let unserialized1 = await Folder.unserialize(serialized1);
|
||||
|
||||
expect(unserialized1.title).toBe(folder1.title);
|
||||
|
||||
let serialized2 = await Folder.serialize(folder2);
|
||||
let unserialized2 = await Folder.unserialize(serialized2);
|
||||
|
||||
expect(unserialized2.title).toBe(folder2.title);
|
||||
}));
|
||||
|
||||
});
|
@@ -52,4 +52,77 @@ describe('models_Folder', function() {
|
||||
expect(all.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should sort by last modified, based on content', asyncTest(async () => {
|
||||
let folders;
|
||||
|
||||
let f1 = await Folder.save({ title: "folder1" }); await sleep(0.1);
|
||||
let f2 = await Folder.save({ title: "folder2" }); await sleep(0.1);
|
||||
let f3 = await Folder.save({ title: "folder3" }); await sleep(0.1);
|
||||
let n1 = await Note.save({ title: 'note1', parent_id: f2.id });
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
expect(folders.length).toBe(3);
|
||||
expect(folders[0].id).toBe(f2.id);
|
||||
expect(folders[1].id).toBe(f3.id);
|
||||
expect(folders[2].id).toBe(f1.id);
|
||||
|
||||
let n2 = await Note.save({ title: 'note1', parent_id: f1.id });
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
expect(folders[0].id).toBe(f1.id);
|
||||
expect(folders[1].id).toBe(f2.id);
|
||||
expect(folders[2].id).toBe(f3.id);
|
||||
|
||||
await Note.save({ id: n1.id, title: 'note1 mod' });
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
expect(folders[0].id).toBe(f2.id);
|
||||
expect(folders[1].id).toBe(f1.id);
|
||||
expect(folders[2].id).toBe(f3.id);
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'asc');
|
||||
expect(folders[0].id).toBe(f3.id);
|
||||
expect(folders[1].id).toBe(f1.id);
|
||||
expect(folders[2].id).toBe(f2.id);
|
||||
}));
|
||||
|
||||
it('should sort by last modified, based on content (sub-folders too)', asyncTest(async () => {
|
||||
let folders;
|
||||
|
||||
let f1 = await Folder.save({ title: "folder1" }); await sleep(0.1);
|
||||
let f2 = await Folder.save({ title: "folder2" }); await sleep(0.1);
|
||||
let f3 = await Folder.save({ title: "folder3", parent_id: f1.id }); await sleep(0.1);
|
||||
let n1 = await Note.save({ title: 'note1', parent_id: f3.id });
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
expect(folders.length).toBe(3);
|
||||
expect(folders[0].id).toBe(f1.id);
|
||||
expect(folders[1].id).toBe(f3.id);
|
||||
expect(folders[2].id).toBe(f2.id);
|
||||
|
||||
let n2 = await Note.save({ title: 'note2', parent_id: f2.id });
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
|
||||
expect(folders[0].id).toBe(f2.id);
|
||||
expect(folders[1].id).toBe(f1.id);
|
||||
expect(folders[2].id).toBe(f3.id);
|
||||
|
||||
await Note.save({ id: n1.id, title: 'note1 MOD' });
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
expect(folders[0].id).toBe(f1.id);
|
||||
expect(folders[1].id).toBe(f3.id);
|
||||
expect(folders[2].id).toBe(f2.id);
|
||||
|
||||
let f4 = await Folder.save({ title: "folder4", parent_id: f1.id }); await sleep(0.1);
|
||||
let n3 = await Note.save({ title: 'note3', parent_id: f4.id });
|
||||
|
||||
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
|
||||
expect(folders.length).toBe(4);
|
||||
expect(folders[0].id).toBe(f1.id);
|
||||
expect(folders[1].id).toBe(f4.id);
|
||||
expect(folders[2].id).toBe(f3.id);
|
||||
expect(folders[3].id).toBe(f2.id);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const SearchEngine = require('lib/services/SearchEngine');
|
||||
const ResourceService = require('lib/services/ResourceService');
|
||||
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
|
||||
@@ -34,19 +34,17 @@ describe('models_ItemChange', function() {
|
||||
const resourceService = new ResourceService();
|
||||
|
||||
await searchEngine.syncTables();
|
||||
|
||||
// If we run this now, it should not delete any change because
|
||||
// the resource service has not yet processed the change
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
await resourceService.indexNoteResources();
|
||||
|
||||
// Now that the resource service has processed the change,
|
||||
// the change can be deleted.
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
expect(await ItemChange.lastChangeId()).toBe(1);
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
await ItemChangeUtils.deleteProcessedChanges();
|
||||
expect(await ItemChange.lastChangeId()).toBe(0);
|
||||
}));
|
||||
|
||||
|
@@ -86,5 +86,32 @@ describe('models_Note', function() {
|
||||
expect(changedNote === note1).toBe(false);
|
||||
expect(!!changedNote.is_todo).toBe(false);
|
||||
}));
|
||||
|
||||
it('should serialize and unserialize without modifying data', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1"});
|
||||
const testCases = [
|
||||
[ {title: '', body:'Body and no title\nSecond line\nThird Line', parent_id: folder1.id},
|
||||
'', 'Body and no title\nSecond line\nThird Line'],
|
||||
[ {title: 'Note title', body:'Body and title', parent_id: folder1.id},
|
||||
'Note title', 'Body and title'],
|
||||
[ {title: 'Title and no body', body:'', parent_id: folder1.id},
|
||||
'Title and no body', ''],
|
||||
]
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const t = testCases[i];
|
||||
|
||||
const input = t[0];
|
||||
const expectedTitle = t[1];
|
||||
const expectedBody = t[1];
|
||||
|
||||
let note1 = await Note.save(input);
|
||||
let serialized = await Note.serialize(note1);
|
||||
let unserialized = await Note.unserialize( serialized);
|
||||
|
||||
expect(unserialized.title).toBe(input.title);
|
||||
expect(unserialized.body).toBe(input.body);
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
@@ -8,10 +8,14 @@ const Resource = require('lib/models/Resource.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
const testImagePath = __dirname + '/../tests/support/photo.jpg';
|
||||
|
||||
describe('models_Resource', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
@@ -23,7 +27,7 @@ describe('models_Resource', function() {
|
||||
it('should have a "done" fetch_status when created locally', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
let ls = await Resource.localState(resource1);
|
||||
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
|
||||
@@ -32,7 +36,7 @@ describe('models_Resource', function() {
|
||||
it('should have a default local state', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
let ls = await Resource.localState(resource1);
|
||||
expect(!ls.id).toBe(true);
|
||||
@@ -43,7 +47,7 @@ describe('models_Resource', function() {
|
||||
it('should save and delete local state', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
await Resource.setLocalState(resource1, { fetch_status: Resource.FETCH_STATUS_IDLE });
|
||||
|
||||
@@ -56,4 +60,31 @@ describe('models_Resource', function() {
|
||||
expect(!ls.id).toBe(true);
|
||||
}));
|
||||
|
||||
it('should resize the resource if the image is below the required dimensions', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
const previousMax = Resource.IMAGE_MAX_DIMENSION;
|
||||
Resource.IMAGE_MAX_DIMENSION = 5;
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
Resource.IMAGE_MAX_DIMENSION = previousMax;
|
||||
let resource1 = (await Resource.all())[0];
|
||||
|
||||
const originalStat = await shim.fsDriver().stat(testImagePath);
|
||||
const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1));
|
||||
|
||||
expect(newStat.size < originalStat.size).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not resize the resource if the image is below the required dimensions', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, testImagePath);
|
||||
let resource1 = (await Resource.all())[0];
|
||||
|
||||
const originalStat = await shim.fsDriver().stat(testImagePath);
|
||||
const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1));
|
||||
|
||||
expect(originalStat.size).toBe(newStat.size);
|
||||
}));
|
||||
|
||||
});
|
104
CliClient/tests/models_Revision.js
Normal file
@@ -0,0 +1,104 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-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 NoteTag = require('lib/models/NoteTag.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('models_Revision', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create patches of text and apply it', asyncTest(async () => {
|
||||
const note1 = await Note.save({ body: 'my note\nsecond line' });
|
||||
|
||||
const patch = Revision.createTextPatch(note1.body, 'my new note\nsecond line');
|
||||
const merged = Revision.applyTextPatch(note1.body, patch);
|
||||
|
||||
expect(merged).toBe('my new note\nsecond line');
|
||||
}));
|
||||
|
||||
it('should create patches of objects and apply it', asyncTest(async () => {
|
||||
const oldObject = {
|
||||
one: '123',
|
||||
two: '456',
|
||||
three: '789',
|
||||
};
|
||||
|
||||
const newObject = {
|
||||
one: '123',
|
||||
three: '999',
|
||||
}
|
||||
|
||||
const patch = Revision.createObjectPatch(oldObject, newObject);
|
||||
const merged = Revision.applyObjectPatch(oldObject, patch);
|
||||
|
||||
expect(JSON.stringify(merged)).toBe(JSON.stringify(newObject));
|
||||
}));
|
||||
|
||||
it('should move target revision to the top', asyncTest(async () => {
|
||||
const revs = [
|
||||
{ id: '123' },
|
||||
{ id: '456' },
|
||||
{ id: '789' },
|
||||
];
|
||||
|
||||
let newRevs;
|
||||
newRevs = Revision.moveRevisionToTop({ id: '456' }, revs);
|
||||
expect(newRevs[0].id).toBe('123');
|
||||
expect(newRevs[1].id).toBe('789');
|
||||
expect(newRevs[2].id).toBe('456');
|
||||
|
||||
newRevs = Revision.moveRevisionToTop({ id: '789' }, revs);
|
||||
expect(newRevs[0].id).toBe('123');
|
||||
expect(newRevs[1].id).toBe('456');
|
||||
expect(newRevs[2].id).toBe('789');
|
||||
}));
|
||||
|
||||
it('should create patch stats', asyncTest(async () => {
|
||||
const tests = [
|
||||
{
|
||||
patch: `@@ -625,16 +625,48 @@
|
||||
rrupted download
|
||||
+%0A- %5B %5D Fix mobile screen options`,
|
||||
expected: [-0, +32],
|
||||
},
|
||||
{
|
||||
patch: `@@ -564,17 +564,17 @@
|
||||
ages%0A- %5B
|
||||
-
|
||||
+x
|
||||
%5D Check `,
|
||||
expected: [-1, +1],
|
||||
},
|
||||
{
|
||||
patch: `@@ -1022,56 +1022,415 @@
|
||||
.%0A%0A#
|
||||
- How to view a note history%0A%0AWhile all the apps
|
||||
+%C2%A0How does it work?%0A%0AAll the apps save a version of the modified notes every 10 minutes.
|
||||
%0A%0A# `,
|
||||
expected: [-(19+27+2), 17+67+4],
|
||||
},
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
const stats = Revision.patchStats(test.patch);
|
||||
expect(stats.removed).toBe(-test.expected[0]);
|
||||
expect(stats.added).toBe(test.expected[1]);
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
@@ -4,6 +4,7 @@ const { time } = require('lib/time-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 NoteTag = require('lib/models/NoteTag.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const { shim } = require('lib/shim');
|
||||
@@ -42,4 +43,18 @@ describe('models_Tag', function() {
|
||||
expect(hasThrown).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not return tags without notes', asyncTest(async () => {
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await Tag.setNoteTagsByTitles(note1.id, ['un']);
|
||||
|
||||
let tags = await Tag.allWithNotes();
|
||||
expect(tags.length).toBe(1);
|
||||
|
||||
await Note.delete(note1.id);
|
||||
|
||||
tags = await Tag.allWithNotes();
|
||||
expect(tags.length).toBe(0);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,6 +1,6 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { friendlySafeFilename } = require('lib/path-utils.js');
|
||||
const { extractExecutablePath, quotePath, unquotePath, friendlySafeFilename } = require('lib/path-utils.js');
|
||||
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
@@ -36,4 +36,41 @@ describe('pathUtils', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should quote and unquote paths', async (done) => {
|
||||
const testCases = [
|
||||
['', ''],
|
||||
['/my/path', '/my/path'],
|
||||
['/my/path with spaces', '"/my/path with spaces"'],
|
||||
['/my/weird"path', '"/my/weird\\"path"'],
|
||||
['c:\\Windows\\test.dll', 'c:\\Windows\\test.dll'],
|
||||
['c:\\Windows\\test test.dll', '"c:\\Windows\\test test.dll"'],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const t = testCases[i];
|
||||
expect(quotePath(t[0])).toBe(t[1]);
|
||||
expect(unquotePath(quotePath(t[0]))).toBe(t[0]);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should extract executable path from command', async (done) => {
|
||||
const testCases = [
|
||||
['', ''],
|
||||
['/my/cmd -some -args', '/my/cmd'],
|
||||
['"/my/cmd" -some -args', '"/my/cmd"'],
|
||||
['"/my/cmd"', '"/my/cmd"'],
|
||||
['"/my/cmd and space" -some -flags', '"/my/cmd and space"'],
|
||||
['"" -some -flags', '""'],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const t = testCases[i];
|
||||
expect(extractExecutablePath(t[0])).toBe(t[1]);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
107
CliClient/tests/services_KvStore.js
Normal file
@@ -0,0 +1,107 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const KvStore = require('lib/services/KvStore.js');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
|
||||
function setupStore() {
|
||||
const store = KvStore.instance();
|
||||
store.setDb(db());
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('services_KvStore', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should set and get values', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
await store.setValue('a', 123);
|
||||
expect(await store.value('a')).toBe(123);
|
||||
|
||||
await store.setValue('a', 123);
|
||||
expect(await store.countKeys()).toBe(1);
|
||||
expect(await store.value('a')).toBe(123);
|
||||
|
||||
await store.setValue('a', 456);
|
||||
expect(await store.countKeys()).toBe(1);
|
||||
expect(await store.value('a')).toBe(456);
|
||||
|
||||
await store.setValue('b', 789);
|
||||
expect(await store.countKeys()).toBe(2);
|
||||
expect(await store.value('a')).toBe(456);
|
||||
expect(await store.value('b')).toBe(789);
|
||||
}));
|
||||
|
||||
it('should set and get values with the right type', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
await store.setValue('string', 'something');
|
||||
await store.setValue('int', 123);
|
||||
expect(await store.value('string')).toBe('something');
|
||||
expect(await store.value('int')).toBe(123);
|
||||
}));
|
||||
|
||||
it('should increment values', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
await store.setValue('int', 1);
|
||||
const newValue = await store.incValue('int');
|
||||
|
||||
expect(newValue).toBe(2);
|
||||
expect(await store.value('int')).toBe(2);
|
||||
|
||||
expect(await store.incValue('int2')).toBe(1);
|
||||
expect(await store.countKeys()).toBe(2);
|
||||
}));
|
||||
|
||||
it('should handle non-existent values', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
expect(await store.value('nope')).toBe(null);
|
||||
}));
|
||||
|
||||
it('should delete values', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
await store.setValue('int', 1);
|
||||
expect(await store.countKeys()).toBe(1);
|
||||
await store.deleteValue('int');
|
||||
expect(await store.countKeys()).toBe(0);
|
||||
|
||||
await store.deleteValue('int'); // That should not throw
|
||||
}));
|
||||
|
||||
it('should increment in an atomic way', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
await store.setValue('int', 0);
|
||||
|
||||
const promises = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
promises.push(store.incValue('int'));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
expect(await store.value('int')).toBe(20);
|
||||
}));
|
||||
|
||||
it('should search by prefix', asyncTest(async () => {
|
||||
const store = setupStore();
|
||||
await store.setValue('testing:1', 1);
|
||||
await store.setValue('testing:2', 2);
|
||||
|
||||
const results = await store.searchByPrefix('testing:');
|
||||
expect(results.length).toBe(2);
|
||||
|
||||
const numbers = results.map(r => r.value).sort();
|
||||
expect(numbers[0]).toBe(1);
|
||||
expect(numbers[1]).toBe(2);
|
||||
}));
|
||||
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const { asyncTest, resourceService, decryptionWorker, encryptionService, loadEncryptionMasterKey, allSyncTargetItemsEncrypted, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
@@ -15,6 +15,7 @@ const fs = require('fs-extra');
|
||||
const ArrayUtils = require('lib/ArrayUtils');
|
||||
const ObjectUtils = require('lib/ObjectUtils');
|
||||
const { shim } = require('lib/shim.js');
|
||||
const SearchEngine = require('lib/services/SearchEngine');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
@@ -37,6 +38,7 @@ describe('services_ResourceService', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await setupDatabaseAndSynchronizer(2);
|
||||
await switchClient(1);
|
||||
done();
|
||||
});
|
||||
@@ -146,4 +148,69 @@ describe('services_ResourceService', function() {
|
||||
expect(before.last_seen_time).toBe(after.last_seen_time);
|
||||
}));
|
||||
|
||||
it('should not delete resources that are associated with an encrypted note', asyncTest(async () => {
|
||||
// https://github.com/laurent22/joplin/issues/1433
|
||||
//
|
||||
// Client 1 and client 2 have E2EE setup.
|
||||
//
|
||||
// - Client 1 creates note N1 and add resource R1 to it
|
||||
// - Client 1 syncs
|
||||
// - Client 2 syncs and get N1
|
||||
// - Client 2 add resource R2 to N1
|
||||
// - Client 2 syncs
|
||||
// - Client 1 syncs
|
||||
// - Client 1 runs resource indexer - but because N1 hasn't been decrypted yet, it found that R1 is no longer associated with any note
|
||||
// - Client 1 decrypts notes, but too late
|
||||
//
|
||||
// Eventually R1 is deleted because service thinks that it was at some point associated with a note, but no longer.
|
||||
|
||||
const masterKey = await loadEncryptionMasterKey();
|
||||
await encryptionService().enableEncryption(masterKey, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg'); // R1
|
||||
await resourceService().indexNoteResources();
|
||||
await synchronizer().start();
|
||||
expect(await allSyncTargetItemsEncrypted()).toBe(true);
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await synchronizer().start();
|
||||
await encryptionService().enableEncryption(masterKey, '123456');
|
||||
await encryptionService().loadMasterKeysFromSettings();
|
||||
await decryptionWorker().start();
|
||||
{
|
||||
const n1 = await Note.load(note1.id);
|
||||
await shim.attachFileToNote(n1, __dirname + '/../tests/support/photo.jpg'); // R2
|
||||
}
|
||||
await synchronizer().start();
|
||||
|
||||
await switchClient(1);
|
||||
|
||||
await synchronizer().start();
|
||||
await resourceService().indexNoteResources();
|
||||
await resourceService().deleteOrphanResources(0); // Previously, R1 would be deleted here because it's not indexed
|
||||
expect((await Resource.all()).length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should double-check if the resource is still linked before deleting it', asyncTest(async () => {
|
||||
SearchEngine.instance().setDb(db()); // /!\ Note that we use the global search engine here, which we shouldn't but will work for now
|
||||
|
||||
let folder1 = await Folder.save({ title: "folder1" });
|
||||
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
|
||||
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
|
||||
await resourceService().indexNoteResources();
|
||||
const bodyWithResource = note1.body;
|
||||
await Note.save({ id: note1.id, body: '' });
|
||||
await resourceService().indexNoteResources();
|
||||
await Note.save({ id: note1.id, body: bodyWithResource });
|
||||
await SearchEngine.instance().syncTables();
|
||||
await resourceService().deleteOrphanResources(0);
|
||||
|
||||
expect((await Resource.all()).length).toBe(1); // It should not have deleted the resource
|
||||
const nr = (await NoteResource.all())[0];
|
||||
expect(!!nr.is_associated).toBe(true); // And it should have fixed the situation by re-indexing the note content
|
||||
}));
|
||||
|
||||
});
|
420
CliClient/tests/services_Revision.js
Normal file
@@ -0,0 +1,420 @@
|
||||
require('app-module-path').addPath(__dirname);
|
||||
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const NoteTag = require('lib/models/NoteTag.js');
|
||||
const ItemChange = require('lib/models/ItemChange.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const Revision = require('lib/models/Revision.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const RevisionService = require('lib/services/RevisionService.js');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
describe('services_Revision', function() {
|
||||
|
||||
beforeEach(async (done) => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
Setting.setValue('revisionService.intervalBetweenRevisions', 0)
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create diff and rebuild notes', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v1 = await Note.save({ title: '', author: 'testing' });
|
||||
await service.collectRevisions();
|
||||
await Note.save({ id: n1_v1.id, title: 'hello', author: 'testing' });
|
||||
await service.collectRevisions();
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome', author: '' });
|
||||
await service.collectRevisions();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
expect(revisions[1].parent_id).toBe(revisions[0].id);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('hello');
|
||||
expect(rev1.author).toBe('testing');
|
||||
|
||||
const rev2 = await service.revisionNote(revisions, 1);
|
||||
expect(rev2.title).toBe('hello welcome');
|
||||
expect(rev2.author).toBe('');
|
||||
|
||||
await time.sleep(0.5);
|
||||
|
||||
await service.deleteOldRevisions(400);
|
||||
const revisions2 = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions2.length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should delete old revisions (1 note, 2 rev)', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await service.collectRevisions();
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id)).length).toBe(2);
|
||||
|
||||
await service.deleteOldRevisions(1000);
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('hello welcome');
|
||||
}));
|
||||
|
||||
it('should delete old revisions (1 note, 3 rev)', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'one' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'one two' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v3 = await Note.save({ id: n1_v1.id, title: 'one two three' });
|
||||
await service.collectRevisions();
|
||||
|
||||
{
|
||||
await service.deleteOldRevisions(2000);
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('one two');
|
||||
|
||||
const rev2 = await service.revisionNote(revisions, 1);
|
||||
expect(rev2.title).toBe('one two three');
|
||||
}
|
||||
|
||||
{
|
||||
await service.deleteOldRevisions(1000);
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('one two three');
|
||||
}
|
||||
}));
|
||||
|
||||
it('should delete old revisions (2 notes, 2 rev)', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'note 1' });
|
||||
const n2_v0 = await Note.save({ title: '' });
|
||||
const n2_v1 = await Note.save({ id: n2_v0.id, title: 'note 2' });
|
||||
await service.collectRevisions();
|
||||
await time.sleep(1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'note 1 (v2)' });
|
||||
const n2_v2 = await Note.save({ id: n2_v1.id, title: 'note 2 (v2)' });
|
||||
await service.collectRevisions();
|
||||
|
||||
expect((await Revision.all()).length).toBe(4);
|
||||
|
||||
await service.deleteOldRevisions(1000);
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('note 1 (v2)');
|
||||
}
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n2_v1.id);
|
||||
expect(revisions.length).toBe(1);
|
||||
const rev1 = await service.revisionNote(revisions, 0);
|
||||
expect(rev1.title).toBe('note 2 (v2)');
|
||||
}
|
||||
}));
|
||||
|
||||
it('should handle conflicts', asyncTest(async () => {
|
||||
const service = new RevisionService();
|
||||
|
||||
// A conflict happens in this case:
|
||||
// - Device 1 creates note1 (rev1)
|
||||
// - Device 2 syncs and get note1
|
||||
// - Device 1 modifies note1 (rev2)
|
||||
// - Device 2 modifies note1 (rev3)
|
||||
// When reconstructing the notes based on the revisions, we need to make sure it follow the right
|
||||
// "path". For example, to reconstruct the note at rev2 it would be:
|
||||
// rev1 => rev2
|
||||
// To reconstruct the note at rev3 it would be:
|
||||
// rev1 => rev3
|
||||
// And not, for example, rev1 => rev2 => rev3
|
||||
|
||||
const n1_v1 = await Note.save({ title: 'hello' });
|
||||
const noteId = n1_v1.id;
|
||||
const rev1 = await service.createNoteRevision_(n1_v1);
|
||||
const n1_v2 = await Note.save({ id: noteId, title: 'hello Paul' });
|
||||
const rev2 = await service.createNoteRevision_(n1_v2, rev1.id);
|
||||
const n1_v3 = await Note.save({ id: noteId, title: 'hello John' });
|
||||
const rev3 = await service.createNoteRevision_(n1_v3, rev1.id);
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(revisions.length).toBe(3);
|
||||
expect(revisions[1].parent_id).toBe(rev1.id);
|
||||
expect(revisions[2].parent_id).toBe(rev1.id);
|
||||
|
||||
const revNote1 = await service.revisionNote(revisions, 0);
|
||||
const revNote2 = await service.revisionNote(revisions, 1);
|
||||
const revNote3 = await service.revisionNote(revisions, 2);
|
||||
expect(revNote1.title).toBe('hello');
|
||||
expect(revNote2.title).toBe('hello Paul');
|
||||
expect(revNote3.title).toBe('hello John');
|
||||
}));
|
||||
|
||||
it('should create a revision for notes that are older than a given interval', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
const noteId = n1.id;
|
||||
|
||||
await sleep(0.1);
|
||||
|
||||
// Set the interval in such a way that the note is considered an old one.
|
||||
Setting.setValue('revisionService.oldNoteInterval', 50);
|
||||
|
||||
// A revision is created the first time a note is overwritten with new content, and
|
||||
// if this note doesn't already have an existing revision.
|
||||
// This is mostly to handle old notes that existed before the revision service. If these
|
||||
// old notes are changed, there's a chance it's accidental or due to some bug, so we
|
||||
// want to preserve a revision just in case.
|
||||
|
||||
{
|
||||
await Note.save({ id: noteId, title: 'hello 2' });
|
||||
await revisionService().collectRevisions(); // Rev for old note created + Rev for new note
|
||||
const all = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(all.length).toBe(2);
|
||||
const revNote1 = await revisionService().revisionNote(all, 0);
|
||||
const revNote2 = await revisionService().revisionNote(all, 1);
|
||||
expect(revNote1.title).toBe('hello');
|
||||
expect(revNote2.title).toBe('hello 2');
|
||||
}
|
||||
|
||||
// If the note is saved a third time, we don't automatically create a revision. One
|
||||
// will be created x minutes later when the service collects revisions.
|
||||
|
||||
{
|
||||
await Note.save({ id: noteId, title: 'hello 3' });
|
||||
const all = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(all.length).toBe(2);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should create a revision for notes that get deleted (recyle bin)', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
const noteId = n1.id;
|
||||
|
||||
await Note.delete(noteId);
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
const all = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
|
||||
expect(all.length).toBe(1);
|
||||
const rev1 = await revisionService().revisionNote(all, 0);
|
||||
expect(rev1.title).toBe('hello');
|
||||
}));
|
||||
|
||||
it('should not create a revision for notes that get deleted if there is already a revision', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
await revisionService().collectRevisions();
|
||||
const noteId = n1.id;
|
||||
await Note.save({ id: noteId, title: 'hello Paul' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1.id)).length).toBe(1);
|
||||
|
||||
await Note.delete(noteId);
|
||||
|
||||
// At this point there is no need to create a new revision for the deleted note
|
||||
// because we already have the latest version as REV 1
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
expect((await Revision.allByType(BaseModel.TYPE_NOTE, n1.id)).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should not create a revision for new note the first time they are saved', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' });
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(0);
|
||||
}
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
{
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(0);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should abort collecting revisions when one of them is encrypted', asyncTest(async () => {
|
||||
const n1 = await Note.save({ title: 'hello' }); // CHANGE 1
|
||||
await revisionService().collectRevisions();
|
||||
await Note.save({ id: n1.id, title: 'hello Ringo' }); // CHANGE 2
|
||||
await revisionService().collectRevisions();
|
||||
await Note.save({ id: n1.id, title: 'hello George' }); // CHANGE 3
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, n1.id);
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
const encryptedRevId = revisions[0].id;
|
||||
|
||||
// Simulate receiving an encrypted revision
|
||||
await Revision.save({ id: encryptedRevId, encryption_applied: 1 });
|
||||
await Note.save({ id: n1.id, title: 'hello Paul' }); // CHANGE 4
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
// Although change 4 is a note update, check that it has not been processed
|
||||
// by the collector, due to one of the revisions being encrypted.
|
||||
expect(await ItemChange.lastChangeId()).toBe(4);
|
||||
expect(Setting.value('revisionService.lastProcessedChangeId')).toBe(3);
|
||||
|
||||
// Simulate the revision being decrypted by DecryptionService
|
||||
await Revision.save({ id: encryptedRevId, encryption_applied: 0 });
|
||||
|
||||
await revisionService().collectRevisions();
|
||||
|
||||
// Now that the revision has been decrypted, all the changes can be processed
|
||||
expect(await ItemChange.lastChangeId()).toBe(4);
|
||||
expect(Setting.value('revisionService.lastProcessedChangeId')).toBe(4);
|
||||
}));
|
||||
|
||||
it('should not delete old revisions if one of them is still encrypted (1)', asyncTest(async () => {
|
||||
// Test case 1: Two revisions and the first one is encrypted.
|
||||
// Calling deleteOldRevisions() with low TTL, which means all revisions
|
||||
// should be deleted, but they won't be due to the encrypted one.
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
await time.sleep(0.1);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
await time.sleep(0.1);
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
const revisions = await Revision.all();
|
||||
await Revision.save({ id: revisions[0].id, encryption_applied: 1 });
|
||||
|
||||
await revisionService().deleteOldRevisions(0);
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
await Revision.save({ id: revisions[0].id, encryption_applied: 0 });
|
||||
|
||||
await revisionService().deleteOldRevisions(0);
|
||||
expect((await Revision.all()).length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not delete old revisions if one of them is still encrypted (2)', asyncTest(async () => {
|
||||
// Test case 2: Two revisions and the first one is encrypted.
|
||||
// Calling deleteOldRevisions() with higher TTL, which means the oldest
|
||||
// revision should be deleted, but it won't be due to the encrypted one.
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
await time.sleep(0.5);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
const revisions = await Revision.all();
|
||||
await Revision.save({ id: revisions[0].id, encryption_applied: 1 });
|
||||
|
||||
await revisionService().deleteOldRevisions(500);
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
}));
|
||||
|
||||
it('should not delete old revisions if one of them is still encrypted (3)', asyncTest(async () => {
|
||||
// Test case 2: Two revisions and the second one is encrypted.
|
||||
// Calling deleteOldRevisions() with higher TTL, which means the oldest
|
||||
// revision should be deleted, but it won't be due to the encrypted one.
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
await time.sleep(0.5);
|
||||
const n1_v2 = await Note.save({ id: n1_v1.id, title: 'hello welcome' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
const revisions = await Revision.all();
|
||||
await Revision.save({ id: revisions[1].id, encryption_applied: 1 });
|
||||
|
||||
await revisionService().deleteOldRevisions(500);
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
await Revision.save({ id: revisions[1].id, encryption_applied: 0 });
|
||||
|
||||
await revisionService().deleteOldRevisions(500);
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should not create a revision if the note has not changed', asyncTest(async () => {
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
|
||||
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // Note has not changed (except its timestamp) so don't create a revision
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should preserve user update time', asyncTest(async () => {
|
||||
// user_updated_time is kind of tricky and can be changed automatically in various
|
||||
// places so make sure it is saved correctly with the revision
|
||||
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
expect((await Revision.all()).length).toBe(1);
|
||||
|
||||
const userUpdatedTime = Date.now() - 1000 * 60 * 60;
|
||||
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello', updated_time: Date.now(), user_updated_time: userUpdatedTime }, { autoTimestamp: false });
|
||||
await revisionService().collectRevisions(); // Only the user timestamp has changed, but that needs to be saved
|
||||
|
||||
const revisions = await Revision.all();
|
||||
expect(revisions.length).toBe(2);
|
||||
|
||||
const revNote = await revisionService().revisionNote(revisions, 1);
|
||||
expect(revNote.user_updated_time).toBe(userUpdatedTime);
|
||||
}));
|
||||
|
||||
it('should not create a revision if there is already a recent one', asyncTest(async () => {
|
||||
const n1_v0 = await Note.save({ title: '' });
|
||||
const n1_v1 = await Note.save({ id: n1_v0.id, title: 'hello' });
|
||||
await revisionService().collectRevisions(); // REV 1
|
||||
|
||||
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello 2' });
|
||||
await revisionService().collectRevisions(); // REV 2
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
|
||||
Setting.setValue('revisionService.intervalBetweenRevisions', 1000);
|
||||
|
||||
const n1_v3 = await Note.save({ id: n1_v0.id, title: 'hello 3' });
|
||||
await revisionService().collectRevisions(); // No rev because there's already a rev that is less than 1000 ms old
|
||||
|
||||
expect((await Revision.all()).length).toBe(2);
|
||||
}));
|
||||
|
||||
});
|
@@ -111,6 +111,58 @@ describe('services_SearchEngine', function() {
|
||||
expect(rows[4].id).toBe(n5.id);
|
||||
}));
|
||||
|
||||
it('should order search results by relevance (last updated first)', asyncTest(async () => {
|
||||
let rows;
|
||||
|
||||
const n1 = await Note.save({ title: "abcd" });
|
||||
await sleep(0.1);
|
||||
const n2 = await Note.save({ title: "abcd" });
|
||||
await sleep(0.1);
|
||||
const n3 = await Note.save({ title: "abcd" });
|
||||
await sleep(0.1);
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('abcd');
|
||||
|
||||
expect(rows[0].id).toBe(n3.id);
|
||||
expect(rows[1].id).toBe(n2.id);
|
||||
expect(rows[2].id).toBe(n1.id);
|
||||
|
||||
await Note.save({ id: n1.id, title: "abcd" });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('abcd');
|
||||
expect(rows[0].id).toBe(n1.id);
|
||||
expect(rows[1].id).toBe(n3.id);
|
||||
expect(rows[2].id).toBe(n2.id);
|
||||
}));
|
||||
|
||||
it('should order search results by relevance (completed to-dos last)', asyncTest(async () => {
|
||||
let rows;
|
||||
|
||||
const n1 = await Note.save({ title: "abcd", is_todo: 1 });
|
||||
await sleep(0.1);
|
||||
const n2 = await Note.save({ title: "abcd", is_todo: 1 });
|
||||
await sleep(0.1);
|
||||
const n3 = await Note.save({ title: "abcd", is_todo: 1 });
|
||||
await sleep(0.1);
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('abcd');
|
||||
|
||||
expect(rows[0].id).toBe(n3.id);
|
||||
expect(rows[1].id).toBe(n2.id);
|
||||
expect(rows[2].id).toBe(n1.id);
|
||||
|
||||
await Note.save({ id: n3.id, todo_completed: Date.now() });
|
||||
|
||||
await engine.syncTables();
|
||||
rows = await engine.search('abcd');
|
||||
expect(rows[0].id).toBe(n2.id);
|
||||
expect(rows[1].id).toBe(n1.id);
|
||||
expect(rows[2].id).toBe(n3.id);
|
||||
}));
|
||||
|
||||
it('should supports various query types', asyncTest(async () => {
|
||||
let rows;
|
||||
|
||||
@@ -204,6 +256,23 @@ describe('services_SearchEngine', function() {
|
||||
expect((await engine.search('말')).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should support field restricted queries with Chinese characters', asyncTest(async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: "你好", body: "我是法国人" });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
expect((await engine.search('title:你好*')).length).toBe(1);
|
||||
expect((await engine.search('body:你好')).length).toBe(0);
|
||||
expect((await engine.search('title:你好 body:法国人')).length).toBe(1);
|
||||
expect((await engine.search('title:你好 body:bla')).length).toBe(0);
|
||||
expect((await engine.search('title:你好 我是')).length).toBe(1);
|
||||
expect((await engine.search('title:bla 我是')).length).toBe(0);
|
||||
|
||||
// For non-alpha char, only the first field is looked at, the following ones are ignored
|
||||
expect((await engine.search('title:你好 title:hello')).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should parse normal query strings', asyncTest(async () => {
|
||||
let rows;
|
||||
|
||||
@@ -213,6 +282,7 @@ describe('services_SearchEngine', function() {
|
||||
['title:abcd efgh', { _: ['efgh'], title: ['abcd'] }],
|
||||
['title:abcd', { title: ['abcd'] }],
|
||||
['"abcd efgh"', { _: ['abcd efgh'] }],
|
||||
['title:abcd title:efgh', { title: ['abcd', 'efgh'] }],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
@@ -221,37 +291,36 @@ describe('services_SearchEngine', function() {
|
||||
const expected = t[1];
|
||||
const actual = engine.parseQuery(input);
|
||||
|
||||
expect(JSON.stringify(actual.terms._)).toBe(JSON.stringify(expected._));
|
||||
expect(JSON.stringify(actual.terms.title)).toBe(JSON.stringify(expected.title));
|
||||
expect(JSON.stringify(actual.terms.body)).toBe(JSON.stringify(expected.body));
|
||||
const _Values = actual.terms._ ? actual.terms._.map(v => v.value) : undefined;
|
||||
const titleValues = actual.terms.title ? actual.terms.title.map(v => v.value) : undefined;
|
||||
const bodyValues = actual.terms.body ? actual.terms.body.map(v => v.value) : undefined;
|
||||
|
||||
expect(JSON.stringify(_Values)).toBe(JSON.stringify(expected._), 'Test case (_) ' + i);
|
||||
expect(JSON.stringify(titleValues)).toBe(JSON.stringify(expected.title), 'Test case (title) ' + i);
|
||||
expect(JSON.stringify(bodyValues)).toBe(JSON.stringify(expected.body), 'Test case (body) ' + i);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should parse query strings with wildcards', asyncTest(async () => {
|
||||
it('should handle queries with special characters', asyncTest(async () => {
|
||||
let rows;
|
||||
|
||||
const testCases = [
|
||||
['do*', ['do', 'dog', 'domino'], [] ],
|
||||
// "*" is a wildcard only when used at the end (to searhc for documents with the specified prefix)
|
||||
// If it's at the beginning, it's ignored, if it's in the middle, it's treated as a litteral "*".
|
||||
['*an*', ['an', 'anneau'], ['piano', 'plan'] ],
|
||||
['no*no', ['no*no'], ['nonono'] ],
|
||||
['did-not-match', 'did-not-match'],
|
||||
['does match', 'does match'],
|
||||
];
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const t = testCases[i];
|
||||
const input = t[0];
|
||||
const shouldMatch = t[1];
|
||||
const shouldNotMatch = t[2];
|
||||
const regex = new RegExp(engine.parseQuery(input).terms._[0].value, 'gmi');
|
||||
const content = t[0];
|
||||
const query = t[1];
|
||||
|
||||
for (let j = 0; j < shouldMatch.length; j++) {
|
||||
const r = shouldMatch[j].match(regex);
|
||||
expect(!!r).toBe(true, '"' + input + '" should match "' + shouldMatch[j] + '"');
|
||||
}
|
||||
const n = await Note.save({ title: content });
|
||||
await engine.syncTables();
|
||||
rows = await engine.search(query);
|
||||
expect(rows.length).toBe(1);
|
||||
|
||||
await Note.delete(n.id);
|
||||
}
|
||||
|
||||
expect(engine.parseQuery('*').termCount).toBe(0);
|
||||
}));
|
||||
|
||||
});
|
@@ -5,9 +5,10 @@ const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synch
|
||||
const markdownUtils = require('lib/markdownUtils.js');
|
||||
const Api = require('lib/services/rest/Api');
|
||||
const Folder = require('lib/models/Folder');
|
||||
const Resource = require('lib/models/Resource');
|
||||
const Note = require('lib/models/Note');
|
||||
const Tag = require('lib/models/Tag');
|
||||
const Resource = require('lib/models/Resource');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
||||
|
||||
@@ -156,6 +157,25 @@ describe('services_rest_Api', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should preserve user timestamps when creating notes', async (done) => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: "mon carnet" });
|
||||
|
||||
const updatedTime = Date.now() - 1000;
|
||||
const createdTime = Date.now() - 10000;
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
parent_id: f.id,
|
||||
user_updated_time: updatedTime,
|
||||
user_created_time: createdTime,
|
||||
}));
|
||||
|
||||
expect(response.user_updated_time).toBe(updatedTime);
|
||||
expect(response.user_created_time).toBe(createdTime);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create notes with supplied ID', async (done) => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: "mon carnet" });
|
||||
@@ -170,6 +190,50 @@ describe('services_rest_Api', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create todos', async (done) => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: "stuff to do" });
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing',
|
||||
parent_id: f.id,
|
||||
is_todo: 1
|
||||
}));
|
||||
expect(response.is_todo).toBe(1);
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing 2',
|
||||
parent_id: f.id,
|
||||
is_todo: 0
|
||||
}));
|
||||
expect(response.is_todo).toBe(0);
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing 3',
|
||||
parent_id: f.id,
|
||||
}));
|
||||
expect(response.is_todo).toBeUndefined();
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing 4',
|
||||
parent_id: f.id,
|
||||
is_todo: '1'
|
||||
}));
|
||||
expect(response.is_todo).toBe(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create folders with supplied ID', async (done) => {
|
||||
const response = await api.route('POST', 'folders', null, JSON.stringify({
|
||||
id: '12345678123456781234567812345678',
|
||||
title: 'from api',
|
||||
}));
|
||||
|
||||
expect(response.id).toBe('12345678123456781234567812345678');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create notes with images', async (done) => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: "mon carnet" });
|
||||
@@ -189,6 +253,28 @@ describe('services_rest_Api', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should delete resources', async (done) => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: "mon carnet" });
|
||||
|
||||
response = await api.route('POST', 'notes', null, JSON.stringify({
|
||||
title: 'testing image',
|
||||
parent_id: f.id,
|
||||
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="
|
||||
}));
|
||||
|
||||
const resource = (await Resource.all())[0];
|
||||
|
||||
const filePath = Resource.fullPath(resource);
|
||||
expect(await shim.fsDriver().exists(filePath)).toBe(true);
|
||||
|
||||
await api.route('DELETE', 'resources/' + resource.id);
|
||||
expect(await shim.fsDriver().exists(filePath)).toBe(false);
|
||||
expect(!(await Resource.load(resource.id))).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create notes from HTML', async (done) => {
|
||||
let response = null;
|
||||
const f = await Folder.save({ title: "mon carnet" });
|
||||
@@ -286,4 +372,4 @@ describe('services_rest_Api', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|