Compare commits
592 Commits
ios-v13.4.
...
server-v3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a3d572498 | ||
|
|
848a2c986a | ||
|
|
fc61a2bc6a | ||
|
|
f9d58742c0 | ||
|
|
5ba8cefe7c | ||
|
|
74484f194e | ||
|
|
eae569aff8 | ||
|
|
8734bc8467 | ||
|
|
612d09d16f | ||
|
|
eb2e9419b9 | ||
|
|
17935458e6 | ||
|
|
a69a5d98ee | ||
|
|
48c9c1112c | ||
|
|
a6585a67d0 | ||
|
|
959e1522d4 | ||
|
|
8605e5aad5 | ||
|
|
88af5208f5 | ||
|
|
bef73dbbf5 | ||
|
|
b23c50cc7d | ||
|
|
3e90a9392d | ||
|
|
e2a32c5993 | ||
|
|
759761086d | ||
|
|
ca29ed94cc | ||
|
|
f815933ad0 | ||
|
|
67af879d38 | ||
|
|
2e310e0f79 | ||
|
|
e63041766f | ||
|
|
93997277b6 | ||
|
|
4afac412ce | ||
|
|
b79bf11680 | ||
|
|
10d727f183 | ||
|
|
50e2dc7749 | ||
|
|
5108fe5b24 | ||
|
|
3536a68cfe | ||
|
|
d94d057f1d | ||
|
|
8ec11bddc2 | ||
|
|
4813c79b35 | ||
|
|
7778a68764 | ||
|
|
503e748ca8 | ||
|
|
b6297b609e | ||
|
|
31d37b30b0 | ||
|
|
0ccd7e474d | ||
|
|
046cfece32 | ||
|
|
0280bb80b9 | ||
|
|
8a61f4ec54 | ||
|
|
d7dd16aac1 | ||
|
|
e1ed573c33 | ||
|
|
b6c8347549 | ||
|
|
b150d6453d | ||
|
|
9feba9345d | ||
|
|
7fa3a3b545 | ||
|
|
fed2438bc3 | ||
|
|
31cb404854 | ||
|
|
dba3a3f68f | ||
|
|
14f8f51cd1 | ||
|
|
2240cf77b5 | ||
|
|
599f7a24ce | ||
|
|
f177563c4a | ||
|
|
a0bdc1fa9b | ||
|
|
f566e5c336 | ||
|
|
87d07eff4a | ||
|
|
3caf41984f | ||
|
|
7a31f1f156 | ||
|
|
090c1d9706 | ||
|
|
5e2b79557c | ||
|
|
74fa2a6eb9 | ||
|
|
791668455e | ||
|
|
91aedc5efa | ||
|
|
6b2d9ba5ec | ||
|
|
d8920840f2 | ||
|
|
bf571c5961 | ||
|
|
a7b22edbc4 | ||
|
|
f4904d8155 | ||
|
|
fab633bbb4 | ||
|
|
cda4073bfc | ||
|
|
903edb8fa2 | ||
|
|
f3409600e1 | ||
|
|
9f36b44842 | ||
|
|
6f41234db3 | ||
|
|
2feebf504e | ||
|
|
3312e96b0d | ||
|
|
af5108d702 | ||
|
|
0f4877f263 | ||
|
|
46c22fffb9 | ||
|
|
ae5bc1b849 | ||
|
|
907da6caa9 | ||
|
|
57a4a687d1 | ||
|
|
865d39d657 | ||
|
|
00aecd63d4 | ||
|
|
bd569b9d8d | ||
|
|
ad4a8aa76d | ||
|
|
c67dcebbbe | ||
|
|
0e135adbe2 | ||
|
|
43e83e7cee | ||
|
|
d1dcc6ced5 | ||
|
|
8425f195f8 | ||
|
|
055177f726 | ||
|
|
1674df2c0f | ||
|
|
29fa117d36 | ||
|
|
f08eaae7ed | ||
|
|
9573bb6af7 | ||
|
|
cb6bafcac6 | ||
|
|
d89aae5371 | ||
|
|
0b0ffe06d4 | ||
|
|
2ab720ff87 | ||
|
|
b9b07790d7 | ||
|
|
3dca34952b | ||
|
|
5be124b54a | ||
|
|
51dd0d3fdc | ||
|
|
7955f15298 | ||
|
|
fdf6091006 | ||
|
|
bb1c5792cc | ||
|
|
75544c943c | ||
|
|
db9967d4fd | ||
|
|
07a66ca62c | ||
|
|
3e3dc4392c | ||
|
|
57504a1795 | ||
|
|
9e9d2699b5 | ||
|
|
4a0d9220ba | ||
|
|
86a7771d5b | ||
|
|
d792a6b3a9 | ||
|
|
e8a083b7bd | ||
|
|
41ed6ab364 | ||
|
|
b587e9ad37 | ||
|
|
e3f9fafcdf | ||
|
|
c0ba743d70 | ||
|
|
523660006d | ||
|
|
aef9429f21 | ||
|
|
58e2bba1ed | ||
|
|
cee44bcdc3 | ||
|
|
9a120bc0d5 | ||
|
|
d1415a318c | ||
|
|
d701b9b1bd | ||
|
|
f8fe143809 | ||
|
|
e626db3b8c | ||
|
|
9e0491ef2f | ||
|
|
053bd91984 | ||
|
|
c76059cf7f | ||
|
|
6d6bc78d53 | ||
|
|
8855495822 | ||
|
|
3491fea313 | ||
|
|
66f5e2fbc3 | ||
|
|
3640bf8ae7 | ||
|
|
977edf6e5d | ||
|
|
e8f067a0b2 | ||
|
|
f971e2aa4c | ||
|
|
b15b92d161 | ||
|
|
1c5f66b5a9 | ||
|
|
1f77357c7d | ||
|
|
aaeb5db3c7 | ||
|
|
996a0894ae | ||
|
|
66fa3fc808 | ||
|
|
dab55daf95 | ||
|
|
7f1c31e03f | ||
|
|
0a8255f091 | ||
|
|
9f3e6650a9 | ||
|
|
4a17da3df5 | ||
|
|
2c4f0d4d8c | ||
|
|
9c1c2fb0d4 | ||
|
|
2332e4bf62 | ||
|
|
a488ac1b27 | ||
|
|
6daa41ca66 | ||
|
|
cc9517f1a2 | ||
|
|
c53d18e068 | ||
|
|
200a471e55 | ||
|
|
c21d37bd91 | ||
|
|
e36cd0e60b | ||
|
|
871f55bf11 | ||
|
|
22c9fed663 | ||
|
|
ea362d7a82 | ||
|
|
9ae9347f89 | ||
|
|
ae8bb902f9 | ||
|
|
90eeec23de | ||
|
|
fe8ad1fa74 | ||
|
|
dfc0a96567 | ||
|
|
474fd094c4 | ||
|
|
937d8fa4f7 | ||
|
|
45c9844616 | ||
|
|
12b8ef5a54 | ||
|
|
18f72c224e | ||
|
|
7ca3aaa83f | ||
|
|
04b1443e5a | ||
|
|
c461741778 | ||
|
|
2865b0a803 | ||
|
|
21e49be22f | ||
|
|
fef761cbab | ||
|
|
c15a353dc2 | ||
|
|
ffb32766c1 | ||
|
|
038908550e | ||
|
|
42f59134ae | ||
|
|
fc0014c0b5 | ||
|
|
42d8df3036 | ||
|
|
1fad9ca1cc | ||
|
|
ae289be77a | ||
|
|
7f6bfe9c6e | ||
|
|
ead4001b7a | ||
|
|
7b95ef72a0 | ||
|
|
a4556bf598 | ||
|
|
8d6268dc92 | ||
|
|
7ffcbdf60a | ||
|
|
76989ddc45 | ||
|
|
1db1254617 | ||
|
|
9810bffddc | ||
|
|
b25e18107b | ||
|
|
edc5fe5d1b | ||
|
|
7ffb44b3a4 | ||
|
|
32f4c33140 | ||
|
|
1a7b09c91c | ||
|
|
e5bf8e0e58 | ||
|
|
94725c533c | ||
|
|
359c92b64f | ||
|
|
8f8b8ad943 | ||
|
|
dd2f329fd5 | ||
|
|
813f594cb4 | ||
|
|
0e0ce49867 | ||
|
|
e485d318b7 | ||
|
|
4e82d81df1 | ||
|
|
d5dbda201b | ||
|
|
831258506b | ||
|
|
67f3329ecb | ||
|
|
ed7e6751f0 | ||
|
|
35e69486d3 | ||
|
|
918c8830e0 | ||
|
|
c3b4a4b955 | ||
|
|
44a14fabbd | ||
|
|
49399cd1fa | ||
|
|
2eb70be937 | ||
|
|
fc4cd2e942 | ||
|
|
cd6e457dc5 | ||
|
|
3ef138c9fe | ||
|
|
2e9bf3a4e5 | ||
|
|
547ceea4b0 | ||
|
|
776ff5e7ea | ||
|
|
2b3bac0d43 | ||
|
|
4e21643bbe | ||
|
|
e48efe2e8d | ||
|
|
5f6382fbc0 | ||
|
|
3d5d82081a | ||
|
|
cff96b1306 | ||
|
|
98c5a9c096 | ||
|
|
e92430b3ed | ||
|
|
848d1bfe64 | ||
|
|
a386283530 | ||
|
|
6101031269 | ||
|
|
2fc3431f46 | ||
|
|
361fa2c768 | ||
|
|
d9d9946faf | ||
|
|
f4a0a2466b | ||
|
|
dbf225d6ad | ||
|
|
4773a3831c | ||
|
|
6a19690581 | ||
|
|
b7a771d58d | ||
|
|
e3daefb81a | ||
|
|
b4253dace8 | ||
|
|
fcf3be1be1 | ||
|
|
99aebbad81 | ||
|
|
81b695a2a9 | ||
|
|
2dbba27357 | ||
|
|
8713cd2fd8 | ||
|
|
d0fc4ea21b | ||
|
|
8bd62800ef | ||
|
|
00f9e932e6 | ||
|
|
b8b55e4a55 | ||
|
|
ef5be2ded3 | ||
|
|
00702dde00 | ||
|
|
2a6af9bed9 | ||
|
|
c26fe0960b | ||
|
|
ab9d36fc08 | ||
|
|
28eb53bd9f | ||
|
|
3097c3e589 | ||
|
|
08371ef718 | ||
|
|
561716efea | ||
|
|
0d457d1bde | ||
|
|
8c11f17c93 | ||
|
|
f7a90ee1d2 | ||
|
|
8822409f7c | ||
|
|
cd3e7f485a | ||
|
|
8d42b01d4f | ||
|
|
2c37197641 | ||
|
|
c2c37b3741 | ||
|
|
3e770300dc | ||
|
|
683291d5df | ||
|
|
d239035417 | ||
|
|
5ef37d9de0 | ||
|
|
1111bde017 | ||
|
|
468cf00d77 | ||
|
|
3c5b41b992 | ||
|
|
5f66c51dba | ||
|
|
bfeaa67ec4 | ||
|
|
032dfa949d | ||
|
|
348fd0333f | ||
|
|
51c4d6d6ef | ||
|
|
09d77a65e8 | ||
|
|
d1aec4a9f7 | ||
|
|
cab1525589 | ||
|
|
a52f3fea9e | ||
|
|
dfbd5eb8ed | ||
|
|
3131f36033 | ||
|
|
dc5b2cfa21 | ||
|
|
cad0f35fcc | ||
|
|
38ea92ff57 | ||
|
|
830deada22 | ||
|
|
38cd4033ea | ||
|
|
7e703ed405 | ||
|
|
02900752d9 | ||
|
|
3b0cc08e6b | ||
|
|
091e9813b5 | ||
|
|
e61e5ac32a | ||
|
|
414970c9a1 | ||
|
|
d4ed49ff23 | ||
|
|
8751d5d152 | ||
|
|
2e846fe15d | ||
|
|
e54b7696d9 | ||
|
|
553c61d628 | ||
|
|
6a15db3a36 | ||
|
|
6f1d0a4b90 | ||
|
|
33b995672c | ||
|
|
8ee46bb4e7 | ||
|
|
b35d9a64cf | ||
|
|
64ef74dd01 | ||
|
|
53035839a5 | ||
|
|
af5287de99 | ||
|
|
45a7554774 | ||
|
|
b06ffe3d25 | ||
|
|
53ea51b758 | ||
|
|
820acdc1f0 | ||
|
|
ef0a79666e | ||
|
|
d096a90c0e | ||
|
|
191775310e | ||
|
|
4fc351b861 | ||
|
|
396decd26c | ||
|
|
01f8fa7bef | ||
|
|
c40856ac7e | ||
|
|
d869cce413 | ||
|
|
a83e8311d8 | ||
|
|
aa884fcb39 | ||
|
|
be2a4c3e24 | ||
|
|
520eec555b | ||
|
|
1281fdb9d2 | ||
|
|
6029353fd1 | ||
|
|
8d1d1be79e | ||
|
|
fd180ae0b4 | ||
|
|
6fdfd6eae6 | ||
|
|
cd5bb575c8 | ||
|
|
2df56530ae | ||
|
|
7987137470 | ||
|
|
a1dcd2fd8f | ||
|
|
7826dc064a | ||
|
|
8961a4a10d | ||
|
|
eedf083bfd | ||
|
|
fed580ae18 | ||
|
|
d4aa1f8f8d | ||
|
|
738e749d51 | ||
|
|
8fe818c0b0 | ||
|
|
e603452fad | ||
|
|
3827637b54 | ||
|
|
1da7c54e5f | ||
|
|
e24ebffba6 | ||
|
|
97fa85a3f7 | ||
|
|
defe36bba1 | ||
|
|
e5bd77836a | ||
|
|
8f5e628303 | ||
|
|
6850c8128b | ||
|
|
8a797fdf23 | ||
|
|
1ae550c0aa | ||
|
|
e7e0529f52 | ||
|
|
2381e44c7f | ||
|
|
a59e975f73 | ||
|
|
2d703b6292 | ||
|
|
b8db70f707 | ||
|
|
c91513b6b5 | ||
|
|
a57ada97ef | ||
|
|
f036869f53 | ||
|
|
d8677a70dd | ||
|
|
15839a19fd | ||
|
|
8f1d55c1fc | ||
|
|
98c18711f7 | ||
|
|
24ff4612fb | ||
|
|
f832eb38ff | ||
|
|
91dc23c23f | ||
|
|
d1913493ab | ||
|
|
fd2b22ed68 | ||
|
|
14b56f19df | ||
|
|
0b082a985b | ||
|
|
53dcac22d0 | ||
|
|
2c721a76b7 | ||
|
|
b68cfd6d9e | ||
|
|
affebedc4b | ||
|
|
a714ef4807 | ||
|
|
596f99aad3 | ||
|
|
c530d35b36 | ||
|
|
5a5c734e2a | ||
|
|
f7eb483d9a | ||
|
|
3a1b36d594 | ||
|
|
7f3c7e807c | ||
|
|
a50fc02b32 | ||
|
|
63702e9e34 | ||
|
|
92c67aab4e | ||
|
|
91535870a2 | ||
|
|
d4bb277417 | ||
|
|
90f87d1496 | ||
|
|
b07752b3ab | ||
|
|
98effef4c5 | ||
|
|
32a919eb81 | ||
|
|
e124fd5c9f | ||
|
|
c5f9290402 | ||
|
|
c80cdadc99 | ||
|
|
b9ba747327 | ||
|
|
5631e1d57b | ||
|
|
740a5628dd | ||
|
|
d96dcef109 | ||
|
|
0a758561f3 | ||
|
|
33b889ca38 | ||
|
|
fa78ea0173 | ||
|
|
6705712f80 | ||
|
|
4986b1f084 | ||
|
|
2785b7f7d9 | ||
|
|
f04831406e | ||
|
|
fdffc81834 | ||
|
|
6f113df2d6 | ||
|
|
8b8b6fbe36 | ||
|
|
1ef8fd529b | ||
|
|
7aaad4e7f3 | ||
|
|
9547a459cb | ||
|
|
b0497bfa07 | ||
|
|
be1d092cab | ||
|
|
517669ee27 | ||
|
|
72fc97116f | ||
|
|
77ca6b3447 | ||
|
|
b227d337d0 | ||
|
|
a6e671d45b | ||
|
|
47c82a7e75 | ||
|
|
bafa1576f2 | ||
|
|
48956df439 | ||
|
|
4716065295 | ||
|
|
f801bbfb27 | ||
|
|
4a043f68ad | ||
|
|
cac93e9f9c | ||
|
|
e1e5c9aeb0 | ||
|
|
382cb257ab | ||
|
|
711d214741 | ||
|
|
6f375be8b9 | ||
|
|
a118615e06 | ||
|
|
912bf7463f | ||
|
|
cfc29832a2 | ||
|
|
737fd132e3 | ||
|
|
9fc76f4e4c | ||
|
|
981f15d85c | ||
|
|
a59594db3b | ||
|
|
8c8190e2e9 | ||
|
|
d7e7ff77e8 | ||
|
|
e33c142c5a | ||
|
|
97d3a8243d | ||
|
|
f1716a3edb | ||
|
|
1436f5867d | ||
|
|
d754b8fe0c | ||
|
|
4f58055cc1 | ||
|
|
98697e1db4 | ||
|
|
8ac65a08c1 | ||
|
|
2b86d83290 | ||
|
|
09cafe99d1 | ||
|
|
6fce844cbf | ||
|
|
52de8c071f | ||
|
|
537543cc8a | ||
|
|
ff16453299 | ||
|
|
210deec495 | ||
|
|
e96baea005 | ||
|
|
ae24b91f25 | ||
|
|
f2e5118bf5 | ||
|
|
72698ec573 | ||
|
|
68abc27c6a | ||
|
|
1acb3d0726 | ||
|
|
5bf97dc3b8 | ||
|
|
e0e04fbc91 | ||
|
|
625cd1221c | ||
|
|
110d5bde2d | ||
|
|
93a85b3207 | ||
|
|
ff305f42fd | ||
|
|
99ba854ee1 | ||
|
|
0795c67354 | ||
|
|
38b368e997 | ||
|
|
f9ffe6c4e6 | ||
|
|
5adc0170fc | ||
|
|
f54c364b4d | ||
|
|
9f541b9b9d | ||
|
|
bd0af08c57 | ||
|
|
ac06c6750d | ||
|
|
23b07094b7 | ||
|
|
7eefc016de | ||
|
|
c002be76cd | ||
|
|
2cd29aaaea | ||
|
|
4cb6b01c71 | ||
|
|
2d0f02cb8a | ||
|
|
91c79b9488 | ||
|
|
fc516d05b3 | ||
|
|
2769c9586c | ||
|
|
fd15d5a6d3 | ||
|
|
7237d7faa7 | ||
|
|
3025d62568 | ||
|
|
e9a9f68568 | ||
|
|
5b5dcf34a1 | ||
|
|
9e8500c148 | ||
|
|
4f1999f921 | ||
|
|
6ee9571069 | ||
|
|
10663b1494 | ||
|
|
f25db9bbd7 | ||
|
|
44ac261304 | ||
|
|
eac995a209 | ||
|
|
15c973e885 | ||
|
|
1762f9485f | ||
|
|
7777f8428f | ||
|
|
948aa9db4f | ||
|
|
fdde04ee85 | ||
|
|
f77a20f5d5 | ||
|
|
d43aa2a3e6 | ||
|
|
04d5ce13c2 | ||
|
|
3b764ba06a | ||
|
|
5492ce55fa | ||
|
|
f6b3f9860c | ||
|
|
88f687ba6a | ||
|
|
1f0a98999f | ||
|
|
69135c3bea | ||
|
|
c27d542a4b | ||
|
|
bd1c2534c5 | ||
|
|
72513b520c | ||
|
|
ec0f9ef9bc | ||
|
|
818bc3218a | ||
|
|
82760a5b6a | ||
|
|
5ba9a16cfd | ||
|
|
68fc91fdc7 | ||
|
|
bdc4687327 | ||
|
|
3a9f57e13f | ||
|
|
b72c48c693 | ||
|
|
f1e42f3bac | ||
|
|
93c908286d | ||
|
|
4eb8777ed0 | ||
|
|
5e1909cee0 | ||
|
|
2e7b312415 | ||
|
|
1ae72235fc | ||
|
|
7735a59fc1 | ||
|
|
41d6e912a7 | ||
|
|
4c2fae8423 | ||
|
|
b72c134890 | ||
|
|
58a9c229bb | ||
|
|
d8c203bb8a | ||
|
|
9020c07825 | ||
|
|
e884da8312 | ||
|
|
d134ea8bfe | ||
|
|
faa44468f3 | ||
|
|
85585d16d2 | ||
|
|
b9c5b8f187 | ||
|
|
da8e638359 | ||
|
|
6482ab5a4e | ||
|
|
ec74abe754 | ||
|
|
859bc8d88e | ||
|
|
56ed471a2f | ||
|
|
650594ecea | ||
|
|
3e9bb914e5 | ||
|
|
f75e911a4e | ||
|
|
78fb07d4c7 | ||
|
|
6390ef43ed | ||
|
|
78c5c4d7c3 | ||
|
|
0d1d50768b | ||
|
|
57093b35ea | ||
|
|
cba5cf660b | ||
|
|
0024722c79 | ||
|
|
bc2832e78f | ||
|
|
424cc96d36 | ||
|
|
56fd5d828f | ||
|
|
03843b087a | ||
|
|
b179509dd3 | ||
|
|
f6851314d2 | ||
|
|
eaec45cb3f | ||
|
|
9be954496c | ||
|
|
ac289c5198 | ||
|
|
98ef5e619b | ||
|
|
62faa48aac | ||
|
|
5daa7a1f4c | ||
|
|
32be071601 | ||
|
|
0dc63dd306 | ||
|
|
78ed58187a | ||
|
|
86f2a3a7d0 | ||
|
|
5b106d4827 | ||
|
|
3bf2eb0399 | ||
|
|
8302afda19 | ||
|
|
ba970ac7a5 | ||
|
|
89018e497f | ||
|
|
53a05eb781 | ||
|
|
7637915bed | ||
|
|
d5dd55a813 | ||
|
|
e80a0c39f8 | ||
|
|
357199658f |
@@ -9,6 +9,7 @@ API_KEY=random-string
|
||||
QUEUE_TTL=900000
|
||||
QUEUE_RETRY_COUNT=2
|
||||
QUEUE_MAINTENANCE_INTERVAL=30000
|
||||
IMAGE_MAX_DIMENSION=400
|
||||
|
||||
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:latest
|
||||
# Fullpath to images folder e.g.:
|
||||
|
||||
@@ -6,6 +6,7 @@ _releases/
|
||||
*.min.js
|
||||
**/commands/index.ts
|
||||
**/node_modules/
|
||||
**/abcjs-basic-min.js
|
||||
packages/generator-joplin/generators/app/templates/api/
|
||||
Assets/
|
||||
docs/
|
||||
@@ -90,12 +91,13 @@ plugin_types/
|
||||
readme/
|
||||
packages/react-native-vosk/lib/
|
||||
packages/lib/countable/Countable.js
|
||||
packages/onenote-converter/pkg/onenote_converter.js
|
||||
packages/onenote-converter/renderer/pkg/*
|
||||
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/app.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/cli-integration-tests.test.js
|
||||
packages/app-cli/app/command-apidoc.js
|
||||
packages/app-cli/app/command-attach.js
|
||||
packages/app-cli/app/command-batch.js
|
||||
@@ -163,8 +165,6 @@ packages/app-desktop/app.reducer.js
|
||||
packages/app-desktop/app.js
|
||||
packages/app-desktop/bridge.js
|
||||
packages/app-desktop/checkForUpdates.js
|
||||
packages/app-desktop/commands/convertNoteToMarkdown.test.js
|
||||
packages/app-desktop/commands/convertNoteToMarkdown.js
|
||||
packages/app-desktop/commands/copyDevCommand.js
|
||||
packages/app-desktop/commands/copyToClipboard.js
|
||||
packages/app-desktop/commands/editProfileConfig.js
|
||||
@@ -205,7 +205,6 @@ packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
|
||||
packages/app-desktop/gui/ConversionNotification/ConversionNotification.js
|
||||
packages/app-desktop/gui/Dialog.js
|
||||
packages/app-desktop/gui/DialogButtonRow.js
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
|
||||
@@ -269,6 +268,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useKeymap.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVisiblePaneChange.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useSyncEditorValue.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
@@ -279,6 +279,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useCursorPositioning.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
|
||||
@@ -320,6 +321,7 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useInitialCursorLocation.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
||||
@@ -420,10 +422,11 @@ packages/app-desktop/gui/Sidebar/Sidebar.js
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js
|
||||
packages/app-desktop/gui/Sidebar/commands/index.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useFocusHandler.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnItemClick.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnRenderListWrapper.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnSidebarKeyDownHandler.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSelectedSidebarIndex.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSelectedSidebarIndexes.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSidebarCommandHandler.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSidebarListData.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/utils/toggleHeader.js
|
||||
@@ -464,6 +467,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/editAlarm.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/importFrom.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/index.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/linkToNote.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.js
|
||||
@@ -506,6 +510,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleSideBar.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleVisiblePanes.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/types.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/appDialogs.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/showFolderPicker.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/usePrintToCallback.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/useSyncDialogState.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/useWindowCommands.js
|
||||
@@ -557,6 +562,7 @@ packages/app-desktop/integration-tests/util/evaluateWithRetry.js
|
||||
packages/app-desktop/integration-tests/util/extendedExpect.js
|
||||
packages/app-desktop/integration-tests/util/getImageSourceSize.js
|
||||
packages/app-desktop/integration-tests/util/getMainWindow.js
|
||||
packages/app-desktop/integration-tests/util/mockClipboard.js
|
||||
packages/app-desktop/integration-tests/util/retryOnFailure.js
|
||||
packages/app-desktop/integration-tests/util/setDarkMode.js
|
||||
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
|
||||
@@ -603,6 +609,7 @@ packages/app-desktop/tools/generateLatestArm64Yml.js
|
||||
packages/app-desktop/tools/githubReleasesUtils.js
|
||||
packages/app-desktop/tools/modifyReleaseAssets.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/tools/resolveSourceMap.js
|
||||
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
|
||||
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
@@ -676,6 +683,8 @@ packages/app-mobile/components/ExtendedWebView/index.js
|
||||
packages/app-mobile/components/ExtendedWebView/index.web.js
|
||||
packages/app-mobile/components/ExtendedWebView/types.js
|
||||
packages/app-mobile/components/ExtendedWebView/utils/useCss.js
|
||||
packages/app-mobile/components/FeedbackBanner.test.js
|
||||
packages/app-mobile/components/FeedbackBanner.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/Icon.js
|
||||
packages/app-mobile/components/IconButton.js
|
||||
@@ -694,6 +703,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||
@@ -722,6 +732,8 @@ packages/app-mobile/components/SearchInput.js
|
||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||
packages/app-mobile/components/SideMenu.js
|
||||
packages/app-mobile/components/SideMenuContentNote.js
|
||||
packages/app-mobile/components/SyncWizard/JoplinCloudIcon.js
|
||||
packages/app-mobile/components/SyncWizard/SyncWizard.js
|
||||
packages/app-mobile/components/TagEditor.test.js
|
||||
packages/app-mobile/components/TagEditor.js
|
||||
packages/app-mobile/components/TextInput.js
|
||||
@@ -738,6 +750,7 @@ packages/app-mobile/components/base-screen.js
|
||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||
packages/app-mobile/components/biometrics/biometricAuthenticate.js
|
||||
packages/app-mobile/components/biometrics/sensorInfo.js
|
||||
packages/app-mobile/components/buttons/CardButton.js
|
||||
packages/app-mobile/components/buttons/FloatingActionButton.js
|
||||
packages/app-mobile/components/buttons/LabelledIconButton.js
|
||||
packages/app-mobile/components/buttons/MultiTouchableOpacity.js
|
||||
@@ -813,7 +826,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButto
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/mockRepositoryApiConstructor.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/newRepoApi.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/pluginServiceSetup.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/openWebsiteForPlugin.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginCallbacks.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.js
|
||||
@@ -839,6 +851,8 @@ packages/app-mobile/components/screens/NoteTagsDialog.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.js
|
||||
packages/app-mobile/components/screens/Notes/Notes.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchBar.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.test.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.js
|
||||
packages/app-mobile/components/screens/SearchScreen/index.js
|
||||
packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
|
||||
@@ -901,14 +915,13 @@ packages/app-mobile/services/AlarmServiceDriver.web.js
|
||||
packages/app-mobile/services/BackButtonService.js
|
||||
packages/app-mobile/services/commands/stateToWhenClauseContext.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.web.js
|
||||
packages/app-mobile/services/e2ee/crypto.js
|
||||
packages/app-mobile/services/plugins/PlatformImplementation.js
|
||||
packages/app-mobile/services/profiles/index.js
|
||||
packages/app-mobile/services/voiceTyping/VoiceTyping.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.android.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.test.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.js
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
@@ -922,6 +935,7 @@ packages/app-mobile/utils/ShareUtils.test.js
|
||||
packages/app-mobile/utils/ShareUtils.js
|
||||
packages/app-mobile/utils/TlsUtils.js
|
||||
packages/app-mobile/utils/appDefaultState.js
|
||||
packages/app-mobile/utils/appReducer.js
|
||||
packages/app-mobile/utils/autodetectTheme.js
|
||||
packages/app-mobile/utils/buildStartupTasks.js
|
||||
packages/app-mobile/utils/checkPermissions.js
|
||||
@@ -961,6 +975,7 @@ packages/app-mobile/utils/pickDocument.js
|
||||
packages/app-mobile/utils/polyfills/bufferPolyfill.js
|
||||
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
|
||||
packages/app-mobile/utils/polyfills/index.js
|
||||
packages/app-mobile/utils/polyfills/index.web.js
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/shareFile.js
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
@@ -971,6 +986,7 @@ packages/app-mobile/utils/shim-init-react/shimInitShared.js
|
||||
packages/app-mobile/utils/testing/createMockReduxStore.js
|
||||
packages/app-mobile/utils/testing/getWebViewDomById.js
|
||||
packages/app-mobile/utils/testing/getWebViewWindowById.js
|
||||
packages/app-mobile/utils/testing/mockPluginServiceSetup.js
|
||||
packages/app-mobile/utils/testing/setupGlobalStore.js
|
||||
packages/app-mobile/utils/testing/testingLibrary.js
|
||||
packages/app-mobile/utils/types.js
|
||||
@@ -992,6 +1008,7 @@ packages/editor/CodeMirror/CodeMirrorControl.js
|
||||
packages/editor/CodeMirror/configFromSettings.js
|
||||
packages/editor/CodeMirror/createEditor.test.js
|
||||
packages/editor/CodeMirror/createEditor.js
|
||||
packages/editor/CodeMirror/editorCommands/cutOrCopyText.js
|
||||
packages/editor/CodeMirror/editorCommands/duplicateLine.test.js
|
||||
packages/editor/CodeMirror/editorCommands/duplicateLine.js
|
||||
packages/editor/CodeMirror/editorCommands/editorCommands.js
|
||||
@@ -1034,6 +1051,7 @@ packages/editor/CodeMirror/extensions/rendering/addFormattingClasses.js
|
||||
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.test.js
|
||||
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.js
|
||||
packages/editor/CodeMirror/extensions/rendering/renderingExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceBackslashEscapes.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
|
||||
@@ -1042,6 +1060,7 @@ packages/editor/CodeMirror/extensions/rendering/types.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.test.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.js
|
||||
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
|
||||
packages/editor/CodeMirror/getScrollFraction.js
|
||||
@@ -1088,29 +1107,40 @@ packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||
packages/editor/CodeMirror/utils/setupVim.js
|
||||
packages/editor/ProseMirror/commands.test.js
|
||||
packages/editor/ProseMirror/commands.js
|
||||
packages/editor/CodeMirror/vendor/announceSearchMatch.js
|
||||
packages/editor/ProseMirror/commands/commands.test.js
|
||||
packages/editor/ProseMirror/commands/commands.js
|
||||
packages/editor/ProseMirror/commands/focusEditor.js
|
||||
packages/editor/ProseMirror/createEditor.js
|
||||
packages/editor/ProseMirror/index.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.js
|
||||
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/postProcessRenderedHtml.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/showCreateEditablePrompt.test.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/showCreateEditablePrompt.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/utils/createEditorDialog.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/utils/postProcessRenderedHtml.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
|
||||
packages/editor/ProseMirror/plugins/keymapPlugin.js
|
||||
packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
|
||||
packages/editor/ProseMirror/plugins/listPlugin.js
|
||||
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
|
||||
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
|
||||
packages/editor/ProseMirror/plugins/searchPlugin.js
|
||||
packages/editor/ProseMirror/plugins/tablePlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createExternalEditorPlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createFloatingButtonPlugin.js
|
||||
packages/editor/ProseMirror/schema.js
|
||||
packages/editor/ProseMirror/styles.js
|
||||
packages/editor/ProseMirror/testing/createTestEditor.js
|
||||
packages/editor/ProseMirror/testing/createTestEditorWithSerializer.js
|
||||
packages/editor/ProseMirror/testing/mockEditorApi.js
|
||||
packages/editor/ProseMirror/types.js
|
||||
packages/editor/ProseMirror/utils/SelectableNodeView.js
|
||||
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
|
||||
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
|
||||
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
|
||||
@@ -1118,9 +1148,11 @@ packages/editor/ProseMirror/utils/dom/createButton.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextArea.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextNode.js
|
||||
packages/editor/ProseMirror/utils/dom/createUniqueId.js
|
||||
packages/editor/ProseMirror/utils/dom/showModal.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
|
||||
packages/editor/ProseMirror/utils/forEachHeading.js
|
||||
packages/editor/ProseMirror/utils/insertRenderedMarkdown.js
|
||||
packages/editor/ProseMirror/utils/jumpToHash.js
|
||||
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
|
||||
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
|
||||
@@ -1128,8 +1160,15 @@ packages/editor/ProseMirror/utils/postprocessEditorOutput.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.js
|
||||
packages/editor/ProseMirror/utils/sanitizeHtml.js
|
||||
packages/editor/ProseMirror/utils/selectFirstInstanceOfNode.js
|
||||
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
|
||||
packages/editor/ProseMirror/vendor/changedDescendants.js
|
||||
packages/editor/ProseMirror/vendor/icons/addColumnRight.js
|
||||
packages/editor/ProseMirror/vendor/icons/addRowBelow.js
|
||||
packages/editor/ProseMirror/vendor/icons/icon.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeColumn.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeRow.js
|
||||
packages/editor/ProseMirror/vendor/icons/types.js
|
||||
packages/editor/ProseMirror/vendor/splitBlockAs.js
|
||||
packages/editor/SelectionFormatting.js
|
||||
packages/editor/events.js
|
||||
@@ -1203,6 +1242,8 @@ packages/lib/callbackUrlUtils.js
|
||||
packages/lib/clipperUtils.js
|
||||
packages/lib/commands/convertHtmlToMarkdown.test.js
|
||||
packages/lib/commands/convertHtmlToMarkdown.js
|
||||
packages/lib/commands/convertNoteToMarkdown.test.js
|
||||
packages/lib/commands/convertNoteToMarkdown.js
|
||||
packages/lib/commands/deleteNote.js
|
||||
packages/lib/commands/historyBackward.js
|
||||
packages/lib/commands/historyForward.js
|
||||
@@ -1331,6 +1372,7 @@ packages/lib/models/utils/getCanBeCollapsedFolderIds.js
|
||||
packages/lib/models/utils/getCollator.js
|
||||
packages/lib/models/utils/getConflictFolderId.js
|
||||
packages/lib/models/utils/isItemId.js
|
||||
packages/lib/models/utils/isJoplinServerVariant.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/onFolderDrop.test.js
|
||||
packages/lib/models/utils/onFolderDrop.js
|
||||
@@ -1366,6 +1408,7 @@ packages/lib/services/KeymapService_keysRegExp.js
|
||||
packages/lib/services/KvStore.js
|
||||
packages/lib/services/MigrationService.js
|
||||
packages/lib/services/NavService.js
|
||||
packages/lib/services/NotePositionService.js
|
||||
packages/lib/services/PostMessageService.js
|
||||
packages/lib/services/ReportService.test.js
|
||||
packages/lib/services/ReportService.js
|
||||
@@ -1381,6 +1424,7 @@ packages/lib/services/UndoRedoService.js
|
||||
packages/lib/services/WhenClause.test.js
|
||||
packages/lib/services/WhenClause.js
|
||||
packages/lib/services/commands/MenuUtils.js
|
||||
packages/lib/services/commands/ToolbarButtonUtils.test.js
|
||||
packages/lib/services/commands/ToolbarButtonUtils.js
|
||||
packages/lib/services/commands/commandsToMarkdownTable.js
|
||||
packages/lib/services/commands/focusEditorIfEditorCommand.js
|
||||
@@ -1398,20 +1442,26 @@ packages/lib/services/database/migrations/45.js
|
||||
packages/lib/services/database/migrations/46.js
|
||||
packages/lib/services/database/migrations/47.js
|
||||
packages/lib/services/database/migrations/48.js
|
||||
packages/lib/services/database/migrations/49.js
|
||||
packages/lib/services/database/migrations/index.js
|
||||
packages/lib/services/database/sqlStringToLines.js
|
||||
packages/lib/services/database/types.js
|
||||
packages/lib/services/debug/populateDatabase.js
|
||||
packages/lib/services/e2ee/EncryptionService.test.js
|
||||
packages/lib/services/e2ee/EncryptionService.js
|
||||
packages/lib/services/e2ee/RSA.node.js
|
||||
packages/lib/services/e2ee/crypto.test.js
|
||||
packages/lib/services/e2ee/crypto.js
|
||||
packages/lib/services/e2ee/cryptoShared.js
|
||||
packages/lib/services/e2ee/cryptoTestUtils.js
|
||||
packages/lib/services/e2ee/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk.js
|
||||
packages/lib/services/e2ee/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/RSA.node.js
|
||||
packages/lib/services/e2ee/ppk/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk/ppk.js
|
||||
packages/lib/services/e2ee/ppk/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/LongDataWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/StringToBufferWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/WebCryptoRsa.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.test.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.js
|
||||
packages/lib/services/e2ee/types.js
|
||||
packages/lib/services/e2ee/utils.test.js
|
||||
packages/lib/services/e2ee/utils.js
|
||||
@@ -1516,6 +1566,7 @@ packages/lib/services/plugins/utils/createViewHandle.js
|
||||
packages/lib/services/plugins/utils/executeSandboxCall.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorView.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
|
||||
packages/lib/services/plugins/utils/getPluginHelpUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
||||
@@ -1607,6 +1658,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
packages/lib/services/synchronizer/handleConflictAction.test.js
|
||||
packages/lib/services/synchronizer/migrations/1.js
|
||||
packages/lib/services/synchronizer/migrations/2.js
|
||||
packages/lib/services/synchronizer/migrations/3.js
|
||||
@@ -1720,6 +1772,7 @@ packages/plugin-repo-cli/lib/gitCompareUrl.test.js
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js
|
||||
packages/plugin-repo-cli/lib/searchPlugins.js
|
||||
packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.js
|
||||
@@ -1740,6 +1793,7 @@ packages/renderer/MdToHtml/createEventHandlingAttrs.js
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js
|
||||
packages/renderer/MdToHtml/linkReplacement.js
|
||||
packages/renderer/MdToHtml/renderMedia.js
|
||||
packages/renderer/MdToHtml/rules/abc.js
|
||||
packages/renderer/MdToHtml/rules/checkbox.js
|
||||
packages/renderer/MdToHtml/rules/code_inline.js
|
||||
packages/renderer/MdToHtml/rules/fence.js
|
||||
@@ -1782,14 +1836,18 @@ packages/tools/fuzzer/Client.js
|
||||
packages/tools/fuzzer/ClientPool.js
|
||||
packages/tools/fuzzer/Server.js
|
||||
packages/tools/fuzzer/constants.js
|
||||
packages/tools/fuzzer/doRandomAction.js
|
||||
packages/tools/fuzzer/model/FolderRecord.js
|
||||
packages/tools/fuzzer/sync-fuzzer.js
|
||||
packages/tools/fuzzer/types.js
|
||||
packages/tools/fuzzer/utils/ProgressBar.js
|
||||
packages/tools/fuzzer/utils/SeededRandom.js
|
||||
packages/tools/fuzzer/utils/getNumberProperty.js
|
||||
packages/tools/fuzzer/utils/getProperty.js
|
||||
packages/tools/fuzzer/utils/getStringProperty.js
|
||||
packages/tools/fuzzer/utils/logDiffDebug.js
|
||||
packages/tools/fuzzer/utils/openDebugSession.js
|
||||
packages/tools/fuzzer/utils/randomString.js
|
||||
packages/tools/fuzzer/utils/retryWithCount.js
|
||||
packages/tools/generate-database-types.js
|
||||
packages/tools/generate-images.js
|
||||
@@ -1835,6 +1893,8 @@ packages/tools/updateMarkdownDoc.js
|
||||
packages/tools/utils/discourse.test.js
|
||||
packages/tools/utils/discourse.js
|
||||
packages/tools/utils/loadSponsors.js
|
||||
packages/tools/utils/parsePluralLocalizationForm.js
|
||||
packages/tools/utils/parsePlurallLocalizationForm.test.js
|
||||
packages/tools/utils/translation.js
|
||||
packages/tools/validateFilenames.js
|
||||
packages/tools/website/build.js
|
||||
|
||||
40
.github/workflows/build-android.yml
vendored
@@ -21,23 +21,53 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '24'
|
||||
cache: 'yarn'
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install Yarn
|
||||
run: |
|
||||
corepack enable
|
||||
|
||||
- name: Install
|
||||
run: yarn install
|
||||
env:
|
||||
SKIP_ONENOTE_CONVERTER_BUILD: 1
|
||||
|
||||
- name: Free disk space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
|
||||
- name: Assemble Android Release
|
||||
run: |
|
||||
cd packages/app-mobile/android
|
||||
sed -i -- 's/signingConfig signingConfigs.release/signingConfig signingConfigs.debug/' app/build.gradle
|
||||
./gradlew assembleRelease
|
||||
|
||||
|
||||
- name: Verify alignment
|
||||
run: |
|
||||
cd packages/app-mobile/android/app
|
||||
APK_FILE="./build/outputs/apk/release/app-release.apk"
|
||||
if test ! -f "$APK_FILE" ; then
|
||||
echo "APK file not found."
|
||||
exit 1
|
||||
else
|
||||
echo "APK file found at: $APK_FILE"
|
||||
fi
|
||||
|
||||
BUILD_TOOLS_PATH="$ANDROID_HOME/build-tools/"
|
||||
if test ! -d "$BUILD_TOOLS_PATH" ; then
|
||||
echo "Build tools not found at $BUILD_TOOLS_PATH ($ANDROID_HOME, $BUILD_TOOLS_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
# The build-tools/ directory contains different subdirectories
|
||||
# for each build tools version. As a result, there may be multiple
|
||||
# zipalign tools. Select the most recent (biggest two-digit version number):
|
||||
ZIPALIGN_PATH="$(find $BUILD_TOOLS_PATH -name "zipalign" -print | sort | tail -n1)"
|
||||
if test ! -x "$ZIPALIGN_PATH" ; then
|
||||
echo "zipalign not found (searching in $BUILD_TOOLS_PATH, candidate: $ZIPALIGN_PATH)"
|
||||
exit 1
|
||||
fi
|
||||
"$ZIPALIGN_PATH" -c -P 16 -v 4 "$APK_FILE"
|
||||
|
||||
6
.github/workflows/build-macos-m1.yml
vendored
@@ -9,11 +9,9 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: olegtarasov/get-tag@v2.1.4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
# We need to pin the version to 18.15, because 18.16+ fails with this error:
|
||||
# https://github.com/facebook/react-native/issues/36440
|
||||
node-version: '18.20.8'
|
||||
node-version: '24'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install Yarn
|
||||
|
||||
54
.github/workflows/github-actions-main.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
matrix:
|
||||
# Do not use unbuntu-latest because it causes `The operation was canceled` failures:
|
||||
# https://github.com/actions/runner-images/issues/6709
|
||||
os: [macos-13, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
|
||||
os: [macos-15-intel, ubuntu-22.04, windows-2025, ubuntu-22.04-arm]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -31,6 +31,16 @@ jobs:
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin
|
||||
|
||||
- name: Free disk space
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
docker system prune -af || true
|
||||
docker builder prune -af || true
|
||||
sudo rm -rf /var/lib/docker/tmp/* || true
|
||||
|
||||
# Login to Docker only if we're on a server release tag. If we run this on
|
||||
# a pull request it will fail because the PR doesn't have access to
|
||||
# secrets
|
||||
@@ -40,6 +50,22 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# - name: Test Windows app signing
|
||||
# if: runner.os == 'Windows'
|
||||
# env:
|
||||
# GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
# IS_CONTINUOUS_INTEGRATION: 1
|
||||
# BUILD_SEQUENCIAL: 1
|
||||
# SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }}
|
||||
# SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }}
|
||||
# SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }}
|
||||
# SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }}
|
||||
# SIGN_APPLICATION: 1
|
||||
# # To ensure that the operations stop on failure, all commands
|
||||
# # should be on one line with "&&" in between.
|
||||
# run: |
|
||||
# yarn install && cd packages/app-desktop && yarn dist
|
||||
|
||||
- name: Run tests, build and publish Linux and macOS apps
|
||||
if: runner.os == 'Linux' || runner.os == 'macOs'
|
||||
env:
|
||||
@@ -61,11 +87,14 @@ jobs:
|
||||
- name: Build and publish Windows app
|
||||
if: runner.os == 'Windows' && startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
IS_CONTINUOUS_INTEGRATION: 1
|
||||
BUILD_SEQUENCIAL: 1
|
||||
SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }}
|
||||
SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }}
|
||||
SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }}
|
||||
SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }}
|
||||
SIGN_APPLICATION: 1
|
||||
# To ensure that the operations stop on failure, all commands
|
||||
# should be on one line with "&&" in between.
|
||||
run: |
|
||||
@@ -118,9 +147,19 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '24'
|
||||
|
||||
- name: Free disk space
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /opt/ghc || true
|
||||
docker system prune -af || true
|
||||
docker builder prune -af || true
|
||||
sudo rm -rf /var/lib/docker/tmp/* || true
|
||||
|
||||
- name: Install Yarn
|
||||
run: |
|
||||
@@ -149,7 +188,7 @@ jobs:
|
||||
- name: Check HTTP request
|
||||
run: |
|
||||
# Need to pass environment variables:
|
||||
docker run -p 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
|
||||
docker run --env MAX_TIME_DRIFT=0 --publish 22300:22300 joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js --env dev &
|
||||
|
||||
# Wait for server to start
|
||||
sleep 120
|
||||
@@ -175,5 +214,4 @@ jobs:
|
||||
if [[ "$actual_body" != "$expected_body" ]]; then
|
||||
echo 'Failed while checking the body response after request to /api/ping'
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
fi
|
||||
@@ -51,9 +51,9 @@ runs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18.20.8'
|
||||
node-version: '24'
|
||||
# Disable the cache on ARM runners. For now, we don't run "yarn install" on these
|
||||
# environments and this breaks actions/setup-node.
|
||||
# See https://github.com/laurent22/joplin/commit/47d0d3eb9e89153a609fb5441344da10904c6308#commitcomment-159577783.
|
||||
|
||||
91
.gitignore
vendored
@@ -69,6 +69,7 @@ docs/**/*.mustache
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/app.js
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/cli-integration-tests.test.js
|
||||
packages/app-cli/app/command-apidoc.js
|
||||
packages/app-cli/app/command-attach.js
|
||||
packages/app-cli/app/command-batch.js
|
||||
@@ -136,8 +137,6 @@ packages/app-desktop/app.reducer.js
|
||||
packages/app-desktop/app.js
|
||||
packages/app-desktop/bridge.js
|
||||
packages/app-desktop/checkForUpdates.js
|
||||
packages/app-desktop/commands/convertNoteToMarkdown.test.js
|
||||
packages/app-desktop/commands/convertNoteToMarkdown.js
|
||||
packages/app-desktop/commands/copyDevCommand.js
|
||||
packages/app-desktop/commands/copyToClipboard.js
|
||||
packages/app-desktop/commands/editProfileConfig.js
|
||||
@@ -178,7 +177,6 @@ packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
|
||||
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js
|
||||
packages/app-desktop/gui/ConversionNotification/ConversionNotification.js
|
||||
packages/app-desktop/gui/Dialog.js
|
||||
packages/app-desktop/gui/DialogButtonRow.js
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
|
||||
@@ -242,6 +240,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useKeymap.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useRefocusOnVisiblePaneChange.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/useSyncEditorValue.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/PlainEditor/PlainEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
@@ -252,6 +251,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/shouldPasteResources.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useCursorPositioning.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useEditDialogEventListeners.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHandler.js
|
||||
@@ -293,6 +293,7 @@ packages/app-desktop/gui/NoteEditor/utils/useEffectiveNoteId.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFolder.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.test.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useFormNote.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useInitialCursorLocation.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useMessageHandler.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteEditor/utils/usePluginEditorView.test.js
|
||||
@@ -393,10 +394,11 @@ packages/app-desktop/gui/Sidebar/Sidebar.js
|
||||
packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js
|
||||
packages/app-desktop/gui/Sidebar/commands/index.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useFocusHandler.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnItemClick.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnRenderListWrapper.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useOnSidebarKeyDownHandler.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSelectedSidebarIndex.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSelectedSidebarIndexes.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSidebarCommandHandler.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/useSidebarListData.js
|
||||
packages/app-desktop/gui/Sidebar/hooks/utils/toggleHeader.js
|
||||
@@ -437,6 +439,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/editAlarm.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/hideModalMessage.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/importFrom.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/index.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/linkToNote.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.js
|
||||
@@ -479,6 +482,7 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleSideBar.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleVisiblePanes.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/types.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/appDialogs.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/showFolderPicker.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/usePrintToCallback.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/useSyncDialogState.js
|
||||
packages/app-desktop/gui/WindowCommandsAndDialogs/utils/useWindowCommands.js
|
||||
@@ -530,6 +534,7 @@ packages/app-desktop/integration-tests/util/evaluateWithRetry.js
|
||||
packages/app-desktop/integration-tests/util/extendedExpect.js
|
||||
packages/app-desktop/integration-tests/util/getImageSourceSize.js
|
||||
packages/app-desktop/integration-tests/util/getMainWindow.js
|
||||
packages/app-desktop/integration-tests/util/mockClipboard.js
|
||||
packages/app-desktop/integration-tests/util/retryOnFailure.js
|
||||
packages/app-desktop/integration-tests/util/setDarkMode.js
|
||||
packages/app-desktop/integration-tests/util/setFilePickerResponse.js
|
||||
@@ -576,6 +581,7 @@ packages/app-desktop/tools/generateLatestArm64Yml.js
|
||||
packages/app-desktop/tools/githubReleasesUtils.js
|
||||
packages/app-desktop/tools/modifyReleaseAssets.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/tools/resolveSourceMap.js
|
||||
packages/app-desktop/utils/7zip/getPathToExecutable7Zip.js
|
||||
packages/app-desktop/utils/7zip/pathToBundled7Zip.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
@@ -649,6 +655,8 @@ packages/app-mobile/components/ExtendedWebView/index.js
|
||||
packages/app-mobile/components/ExtendedWebView/index.web.js
|
||||
packages/app-mobile/components/ExtendedWebView/types.js
|
||||
packages/app-mobile/components/ExtendedWebView/utils/useCss.js
|
||||
packages/app-mobile/components/FeedbackBanner.test.js
|
||||
packages/app-mobile/components/FeedbackBanner.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/Icon.js
|
||||
packages/app-mobile/components/IconButton.js
|
||||
@@ -667,6 +675,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
|
||||
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownEditor.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||
@@ -695,6 +704,8 @@ packages/app-mobile/components/SearchInput.js
|
||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||
packages/app-mobile/components/SideMenu.js
|
||||
packages/app-mobile/components/SideMenuContentNote.js
|
||||
packages/app-mobile/components/SyncWizard/JoplinCloudIcon.js
|
||||
packages/app-mobile/components/SyncWizard/SyncWizard.js
|
||||
packages/app-mobile/components/TagEditor.test.js
|
||||
packages/app-mobile/components/TagEditor.js
|
||||
packages/app-mobile/components/TextInput.js
|
||||
@@ -711,6 +722,7 @@ packages/app-mobile/components/base-screen.js
|
||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||
packages/app-mobile/components/biometrics/biometricAuthenticate.js
|
||||
packages/app-mobile/components/biometrics/sensorInfo.js
|
||||
packages/app-mobile/components/buttons/CardButton.js
|
||||
packages/app-mobile/components/buttons/FloatingActionButton.js
|
||||
packages/app-mobile/components/buttons/LabelledIconButton.js
|
||||
packages/app-mobile/components/buttons/MultiTouchableOpacity.js
|
||||
@@ -786,7 +798,6 @@ packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButto
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/WrappedPluginStates.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/mockRepositoryApiConstructor.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/newRepoApi.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/testUtils/pluginServiceSetup.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/openWebsiteForPlugin.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginCallbacks.js
|
||||
packages/app-mobile/components/screens/ConfigScreen/plugins/utils/usePluginItem.js
|
||||
@@ -812,6 +823,8 @@ packages/app-mobile/components/screens/NoteTagsDialog.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.test.js
|
||||
packages/app-mobile/components/screens/Notes/NewNoteButton.js
|
||||
packages/app-mobile/components/screens/Notes/Notes.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchBar.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.test.js
|
||||
packages/app-mobile/components/screens/SearchScreen/SearchResults.js
|
||||
packages/app-mobile/components/screens/SearchScreen/index.js
|
||||
packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js
|
||||
@@ -874,14 +887,13 @@ packages/app-mobile/services/AlarmServiceDriver.web.js
|
||||
packages/app-mobile/services/BackButtonService.js
|
||||
packages/app-mobile/services/commands/stateToWhenClauseContext.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.js
|
||||
packages/app-mobile/services/e2ee/RSA.react-native.web.js
|
||||
packages/app-mobile/services/e2ee/crypto.js
|
||||
packages/app-mobile/services/plugins/PlatformImplementation.js
|
||||
packages/app-mobile/services/profiles/index.js
|
||||
packages/app-mobile/services/voiceTyping/VoiceTyping.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.android.js
|
||||
packages/app-mobile/services/voiceTyping/utils/unzip.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.android.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.test.js
|
||||
packages/app-mobile/services/voiceTyping/whisper.js
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
@@ -895,6 +907,7 @@ packages/app-mobile/utils/ShareUtils.test.js
|
||||
packages/app-mobile/utils/ShareUtils.js
|
||||
packages/app-mobile/utils/TlsUtils.js
|
||||
packages/app-mobile/utils/appDefaultState.js
|
||||
packages/app-mobile/utils/appReducer.js
|
||||
packages/app-mobile/utils/autodetectTheme.js
|
||||
packages/app-mobile/utils/buildStartupTasks.js
|
||||
packages/app-mobile/utils/checkPermissions.js
|
||||
@@ -934,6 +947,7 @@ packages/app-mobile/utils/pickDocument.js
|
||||
packages/app-mobile/utils/polyfills/bufferPolyfill.js
|
||||
packages/app-mobile/utils/polyfills/crypto-polyfill/index.js
|
||||
packages/app-mobile/utils/polyfills/index.js
|
||||
packages/app-mobile/utils/polyfills/index.web.js
|
||||
packages/app-mobile/utils/setupNotifications.js
|
||||
packages/app-mobile/utils/shareFile.js
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
@@ -944,6 +958,7 @@ packages/app-mobile/utils/shim-init-react/shimInitShared.js
|
||||
packages/app-mobile/utils/testing/createMockReduxStore.js
|
||||
packages/app-mobile/utils/testing/getWebViewDomById.js
|
||||
packages/app-mobile/utils/testing/getWebViewWindowById.js
|
||||
packages/app-mobile/utils/testing/mockPluginServiceSetup.js
|
||||
packages/app-mobile/utils/testing/setupGlobalStore.js
|
||||
packages/app-mobile/utils/testing/testingLibrary.js
|
||||
packages/app-mobile/utils/types.js
|
||||
@@ -965,6 +980,7 @@ packages/editor/CodeMirror/CodeMirrorControl.js
|
||||
packages/editor/CodeMirror/configFromSettings.js
|
||||
packages/editor/CodeMirror/createEditor.test.js
|
||||
packages/editor/CodeMirror/createEditor.js
|
||||
packages/editor/CodeMirror/editorCommands/cutOrCopyText.js
|
||||
packages/editor/CodeMirror/editorCommands/duplicateLine.test.js
|
||||
packages/editor/CodeMirror/editorCommands/duplicateLine.js
|
||||
packages/editor/CodeMirror/editorCommands/editorCommands.js
|
||||
@@ -1007,6 +1023,7 @@ packages/editor/CodeMirror/extensions/rendering/addFormattingClasses.js
|
||||
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.test.js
|
||||
packages/editor/CodeMirror/extensions/rendering/renderBlockImages.js
|
||||
packages/editor/CodeMirror/extensions/rendering/renderingExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceBackslashEscapes.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceBulletLists.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceCheckboxes.js
|
||||
packages/editor/CodeMirror/extensions/rendering/replaceDividers.js
|
||||
@@ -1015,6 +1032,7 @@ packages/editor/CodeMirror/extensions/rendering/types.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeBlockReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/makeInlineReplaceExtension.js
|
||||
packages/editor/CodeMirror/extensions/rendering/utils/nodeIntersectsSelection.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.test.js
|
||||
packages/editor/CodeMirror/extensions/searchExtension.js
|
||||
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
|
||||
packages/editor/CodeMirror/getScrollFraction.js
|
||||
@@ -1061,29 +1079,40 @@ packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
|
||||
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
|
||||
packages/editor/CodeMirror/utils/markdown/toggleCheckboxAt.js
|
||||
packages/editor/CodeMirror/utils/setupVim.js
|
||||
packages/editor/ProseMirror/commands.test.js
|
||||
packages/editor/ProseMirror/commands.js
|
||||
packages/editor/CodeMirror/vendor/announceSearchMatch.js
|
||||
packages/editor/ProseMirror/commands/commands.test.js
|
||||
packages/editor/ProseMirror/commands/commands.js
|
||||
packages/editor/ProseMirror/commands/focusEditor.js
|
||||
packages/editor/ProseMirror/createEditor.js
|
||||
packages/editor/ProseMirror/index.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/detailsPlugin.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/imagePlugin.js
|
||||
packages/editor/ProseMirror/plugins/inputRulesPlugin.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/createEditorDialog.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/joplinEditablePlugin.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/postProcessRenderedHtml.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/showCreateEditablePrompt.test.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/showCreateEditablePrompt.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/utils/createEditorDialog.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditablePlugin/utils/postProcessRenderedHtml.js
|
||||
packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
|
||||
packages/editor/ProseMirror/plugins/keymapPlugin.js
|
||||
packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
|
||||
packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
|
||||
packages/editor/ProseMirror/plugins/listPlugin.js
|
||||
packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
|
||||
packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
|
||||
packages/editor/ProseMirror/plugins/searchPlugin.js
|
||||
packages/editor/ProseMirror/plugins/tablePlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createExternalEditorPlugin.js
|
||||
packages/editor/ProseMirror/plugins/utils/createFloatingButtonPlugin.js
|
||||
packages/editor/ProseMirror/schema.js
|
||||
packages/editor/ProseMirror/styles.js
|
||||
packages/editor/ProseMirror/testing/createTestEditor.js
|
||||
packages/editor/ProseMirror/testing/createTestEditorWithSerializer.js
|
||||
packages/editor/ProseMirror/testing/mockEditorApi.js
|
||||
packages/editor/ProseMirror/types.js
|
||||
packages/editor/ProseMirror/utils/SelectableNodeView.js
|
||||
packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
|
||||
packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
|
||||
packages/editor/ProseMirror/utils/computeSelectionFormatting.js
|
||||
@@ -1091,9 +1120,11 @@ packages/editor/ProseMirror/utils/dom/createButton.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextArea.js
|
||||
packages/editor/ProseMirror/utils/dom/createTextNode.js
|
||||
packages/editor/ProseMirror/utils/dom/createUniqueId.js
|
||||
packages/editor/ProseMirror/utils/dom/showModal.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
|
||||
packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
|
||||
packages/editor/ProseMirror/utils/forEachHeading.js
|
||||
packages/editor/ProseMirror/utils/insertRenderedMarkdown.js
|
||||
packages/editor/ProseMirror/utils/jumpToHash.js
|
||||
packages/editor/ProseMirror/utils/makeLinksClickableInElement.js
|
||||
packages/editor/ProseMirror/utils/postprocessEditorOutput.test.js
|
||||
@@ -1101,8 +1132,15 @@ packages/editor/ProseMirror/utils/postprocessEditorOutput.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
|
||||
packages/editor/ProseMirror/utils/preprocessEditorInput.js
|
||||
packages/editor/ProseMirror/utils/sanitizeHtml.js
|
||||
packages/editor/ProseMirror/utils/selectFirstInstanceOfNode.js
|
||||
packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
|
||||
packages/editor/ProseMirror/vendor/changedDescendants.js
|
||||
packages/editor/ProseMirror/vendor/icons/addColumnRight.js
|
||||
packages/editor/ProseMirror/vendor/icons/addRowBelow.js
|
||||
packages/editor/ProseMirror/vendor/icons/icon.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeColumn.js
|
||||
packages/editor/ProseMirror/vendor/icons/removeRow.js
|
||||
packages/editor/ProseMirror/vendor/icons/types.js
|
||||
packages/editor/ProseMirror/vendor/splitBlockAs.js
|
||||
packages/editor/SelectionFormatting.js
|
||||
packages/editor/events.js
|
||||
@@ -1176,6 +1214,8 @@ packages/lib/callbackUrlUtils.js
|
||||
packages/lib/clipperUtils.js
|
||||
packages/lib/commands/convertHtmlToMarkdown.test.js
|
||||
packages/lib/commands/convertHtmlToMarkdown.js
|
||||
packages/lib/commands/convertNoteToMarkdown.test.js
|
||||
packages/lib/commands/convertNoteToMarkdown.js
|
||||
packages/lib/commands/deleteNote.js
|
||||
packages/lib/commands/historyBackward.js
|
||||
packages/lib/commands/historyForward.js
|
||||
@@ -1304,6 +1344,7 @@ packages/lib/models/utils/getCanBeCollapsedFolderIds.js
|
||||
packages/lib/models/utils/getCollator.js
|
||||
packages/lib/models/utils/getConflictFolderId.js
|
||||
packages/lib/models/utils/isItemId.js
|
||||
packages/lib/models/utils/isJoplinServerVariant.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/onFolderDrop.test.js
|
||||
packages/lib/models/utils/onFolderDrop.js
|
||||
@@ -1339,6 +1380,7 @@ packages/lib/services/KeymapService_keysRegExp.js
|
||||
packages/lib/services/KvStore.js
|
||||
packages/lib/services/MigrationService.js
|
||||
packages/lib/services/NavService.js
|
||||
packages/lib/services/NotePositionService.js
|
||||
packages/lib/services/PostMessageService.js
|
||||
packages/lib/services/ReportService.test.js
|
||||
packages/lib/services/ReportService.js
|
||||
@@ -1354,6 +1396,7 @@ packages/lib/services/UndoRedoService.js
|
||||
packages/lib/services/WhenClause.test.js
|
||||
packages/lib/services/WhenClause.js
|
||||
packages/lib/services/commands/MenuUtils.js
|
||||
packages/lib/services/commands/ToolbarButtonUtils.test.js
|
||||
packages/lib/services/commands/ToolbarButtonUtils.js
|
||||
packages/lib/services/commands/commandsToMarkdownTable.js
|
||||
packages/lib/services/commands/focusEditorIfEditorCommand.js
|
||||
@@ -1371,20 +1414,26 @@ packages/lib/services/database/migrations/45.js
|
||||
packages/lib/services/database/migrations/46.js
|
||||
packages/lib/services/database/migrations/47.js
|
||||
packages/lib/services/database/migrations/48.js
|
||||
packages/lib/services/database/migrations/49.js
|
||||
packages/lib/services/database/migrations/index.js
|
||||
packages/lib/services/database/sqlStringToLines.js
|
||||
packages/lib/services/database/types.js
|
||||
packages/lib/services/debug/populateDatabase.js
|
||||
packages/lib/services/e2ee/EncryptionService.test.js
|
||||
packages/lib/services/e2ee/EncryptionService.js
|
||||
packages/lib/services/e2ee/RSA.node.js
|
||||
packages/lib/services/e2ee/crypto.test.js
|
||||
packages/lib/services/e2ee/crypto.js
|
||||
packages/lib/services/e2ee/cryptoShared.js
|
||||
packages/lib/services/e2ee/cryptoTestUtils.js
|
||||
packages/lib/services/e2ee/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk.js
|
||||
packages/lib/services/e2ee/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/RSA.node.js
|
||||
packages/lib/services/e2ee/ppk/ppk.test.js
|
||||
packages/lib/services/e2ee/ppk/ppk.js
|
||||
packages/lib/services/e2ee/ppk/ppkTestUtils.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/LongDataWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/StringToBufferWrapper.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/WebCryptoRsa.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.test.js
|
||||
packages/lib/services/e2ee/ppk/webCrypto/buildRsaCryptoProvider.js
|
||||
packages/lib/services/e2ee/types.js
|
||||
packages/lib/services/e2ee/utils.test.js
|
||||
packages/lib/services/e2ee/utils.js
|
||||
@@ -1489,6 +1538,7 @@ packages/lib/services/plugins/utils/createViewHandle.js
|
||||
packages/lib/services/plugins/utils/executeSandboxCall.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorView.js
|
||||
packages/lib/services/plugins/utils/getActivePluginEditorViews.js
|
||||
packages/lib/services/plugins/utils/getPluginHelpUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.test.js
|
||||
packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
|
||||
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
||||
@@ -1580,6 +1630,7 @@ packages/lib/services/synchronizer/Synchronizer.sharing.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tags.test.js
|
||||
packages/lib/services/synchronizer/Synchronizer.tools.test.js
|
||||
packages/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
|
||||
packages/lib/services/synchronizer/handleConflictAction.test.js
|
||||
packages/lib/services/synchronizer/migrations/1.js
|
||||
packages/lib/services/synchronizer/migrations/2.js
|
||||
packages/lib/services/synchronizer/migrations/3.js
|
||||
@@ -1693,6 +1744,7 @@ packages/plugin-repo-cli/lib/gitCompareUrl.test.js
|
||||
packages/plugin-repo-cli/lib/gitCompareUrl.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.test.js
|
||||
packages/plugin-repo-cli/lib/overrideUtils.js
|
||||
packages/plugin-repo-cli/lib/searchPlugins.js
|
||||
packages/plugin-repo-cli/lib/types.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.test.js
|
||||
packages/plugin-repo-cli/lib/updateReadme.js
|
||||
@@ -1713,6 +1765,7 @@ packages/renderer/MdToHtml/createEventHandlingAttrs.js
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js
|
||||
packages/renderer/MdToHtml/linkReplacement.js
|
||||
packages/renderer/MdToHtml/renderMedia.js
|
||||
packages/renderer/MdToHtml/rules/abc.js
|
||||
packages/renderer/MdToHtml/rules/checkbox.js
|
||||
packages/renderer/MdToHtml/rules/code_inline.js
|
||||
packages/renderer/MdToHtml/rules/fence.js
|
||||
@@ -1755,14 +1808,18 @@ packages/tools/fuzzer/Client.js
|
||||
packages/tools/fuzzer/ClientPool.js
|
||||
packages/tools/fuzzer/Server.js
|
||||
packages/tools/fuzzer/constants.js
|
||||
packages/tools/fuzzer/doRandomAction.js
|
||||
packages/tools/fuzzer/model/FolderRecord.js
|
||||
packages/tools/fuzzer/sync-fuzzer.js
|
||||
packages/tools/fuzzer/types.js
|
||||
packages/tools/fuzzer/utils/ProgressBar.js
|
||||
packages/tools/fuzzer/utils/SeededRandom.js
|
||||
packages/tools/fuzzer/utils/getNumberProperty.js
|
||||
packages/tools/fuzzer/utils/getProperty.js
|
||||
packages/tools/fuzzer/utils/getStringProperty.js
|
||||
packages/tools/fuzzer/utils/logDiffDebug.js
|
||||
packages/tools/fuzzer/utils/openDebugSession.js
|
||||
packages/tools/fuzzer/utils/randomString.js
|
||||
packages/tools/fuzzer/utils/retryWithCount.js
|
||||
packages/tools/generate-database-types.js
|
||||
packages/tools/generate-images.js
|
||||
@@ -1808,6 +1865,8 @@ packages/tools/updateMarkdownDoc.js
|
||||
packages/tools/utils/discourse.test.js
|
||||
packages/tools/utils/discourse.js
|
||||
packages/tools/utils/loadSponsors.js
|
||||
packages/tools/utils/parsePluralLocalizationForm.js
|
||||
packages/tools/utils/parsePlurallLocalizationForm.test.js
|
||||
packages/tools/utils/translation.js
|
||||
packages/tools/validateFilenames.js
|
||||
packages/tools/website/build.js
|
||||
|
||||
36
.yarn/patches/depd-npm-1.1.2-b0c8414da7.patch
Normal file
@@ -0,0 +1,36 @@
|
||||
# Patch to remove eval. This allows using depd in an environment with
|
||||
# a strict Content-Security-Policy.
|
||||
# Ref: https://github.com/dougwilson/nodejs-depd/pull/33
|
||||
diff --git a/index.js b/index.js
|
||||
index d758d3c8f58a60bf27ef377ad77639bf10ce7854..2bad40d4eeba553d3bcfb206873eac059067ae3b 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -399,19 +399,20 @@ function wrapfunction (fn, message) {
|
||||
throw new TypeError('argument fn must be a function')
|
||||
}
|
||||
|
||||
- var args = createArgumentsString(fn.length)
|
||||
- var deprecate = this // eslint-disable-line no-unused-vars
|
||||
var stack = getStack()
|
||||
var site = callSiteLocation(stack[1])
|
||||
|
||||
site.name = fn.name
|
||||
|
||||
- // eslint-disable-next-line no-eval
|
||||
- var deprecatedfn = eval('(function (' + args + ') {\n' +
|
||||
- '"use strict"\n' +
|
||||
- 'log.call(deprecate, message, site)\n' +
|
||||
- 'return fn.apply(this, arguments)\n' +
|
||||
- '})')
|
||||
+ var deprecatedfn
|
||||
+ var self = this
|
||||
+ deprecatedfn = function () {
|
||||
+ 'use strict'
|
||||
+ log.call(self, message, site)
|
||||
+ return fn.apply(this, arguments)
|
||||
+ }
|
||||
+ Object.defineProperty(deprecatedfn, 'length', { value: fn.length })
|
||||
+ Object.defineProperty(deprecatedfn, 'name', { value: fn.name })
|
||||
|
||||
return deprecatedfn
|
||||
}
|
||||
35
.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch
Normal file
@@ -0,0 +1,35 @@
|
||||
# Patch to remove eval. This allows using depd in an environment with
|
||||
# a strict Content-Security-Policy.
|
||||
# Ref: https://github.com/dougwilson/nodejs-depd/pull/33
|
||||
diff --git a/index.js b/index.js
|
||||
index 1bf2fcfdeffc984e5ad792eec08744c29d4a4590..1b24aa2414458bc651abfdded81b103c131efeaa 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -415,19 +415,19 @@ function wrapfunction (fn, message) {
|
||||
throw new TypeError('argument fn must be a function')
|
||||
}
|
||||
|
||||
- var args = createArgumentsString(fn.length)
|
||||
var stack = getStack()
|
||||
var site = callSiteLocation(stack[1])
|
||||
|
||||
site.name = fn.name
|
||||
|
||||
- // eslint-disable-next-line no-new-func
|
||||
- var deprecatedfn = new Function('fn', 'log', 'deprecate', 'message', 'site',
|
||||
- '"use strict"\n' +
|
||||
- 'return function (' + args + ') {' +
|
||||
- 'log.call(deprecate, message, site)\n' +
|
||||
- 'return fn.apply(this, arguments)\n' +
|
||||
- '}')(fn, log, this, message, site)
|
||||
+ var self = this
|
||||
+ var deprecatedfn = function () {
|
||||
+ 'use strict'
|
||||
+ log.call(self, message, site)
|
||||
+ return fn.apply(this, arguments)
|
||||
+ }
|
||||
+ Object.defineProperty(deprecatedfn, 'length', { value: fn.length })
|
||||
+ Object.defineProperty(deprecatedfn, 'name', { value: fn.name })
|
||||
|
||||
return deprecatedfn
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
diff --git a/android/build.gradle b/android/build.gradle
|
||||
index 6afcbbf0cc8ca2d69dd78077d61e59a90b2136bb..9f8d72b4ec5b2b3d290975d6a255917c95300854 100644
|
||||
--- a/android/build.gradle
|
||||
+++ b/android/build.gradle
|
||||
@@ -67,19 +67,19 @@ repositories {
|
||||
}
|
||||
|
||||
// Generate UUIDs for each models contained in android/src/main/assets/
|
||||
-tasks.register('genUUID') {
|
||||
- doLast {
|
||||
- fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
|
||||
- if (fileDetails.directory) {
|
||||
- def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
|
||||
- def ofile = file("$odir/uuid")
|
||||
- mkdir odir
|
||||
- ofile.text = UUID.randomUUID().toString()
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-}
|
||||
-preBuild.dependsOn genUUID
|
||||
+// tasks.register('genUUID') {
|
||||
+// doLast {
|
||||
+// fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
|
||||
+// if (fileDetails.directory) {
|
||||
+// def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
|
||||
+// def ofile = file("$odir/uuid")
|
||||
+// mkdir odir
|
||||
+// ofile.text = UUID.randomUUID().toString()
|
||||
+// }
|
||||
+// }
|
||||
+// }
|
||||
+// }
|
||||
+// preBuild.dependsOn genUUID
|
||||
|
||||
def kotlin_version = getExtOrDefault('kotlinVersion')
|
||||
|
||||
diff --git a/android/src/main/java/com/reactnativevosk/VoskModule.kt b/android/src/main/java/com/reactnativevosk/VoskModule.kt
|
||||
index 0e2b6595b1b2cf1ee01c6c64239c4b0ea37fce19..5a8539b9cce8951967640dba755e29a4e3ff404a 100644
|
||||
--- a/android/src/main/java/com/reactnativevosk/VoskModule.kt
|
||||
+++ b/android/src/main/java/com/reactnativevosk/VoskModule.kt
|
||||
@@ -19,13 +19,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
||||
return "Vosk"
|
||||
}
|
||||
|
||||
+ @ReactMethod
|
||||
+ fun addListener(type: String?) {
|
||||
+ // Keep: Required for RN built in Event Emitter Calls.
|
||||
+ }
|
||||
+
|
||||
+ @ReactMethod
|
||||
+ fun removeListeners(type: Int?) {
|
||||
+ // Keep: Required for RN built in Event Emitter Calls.
|
||||
+ }
|
||||
+
|
||||
override fun onResult(hypothesis: String) {
|
||||
// Get text data from string object
|
||||
val text = getHypothesisText(hypothesis)
|
||||
|
||||
// Stop recording if data found
|
||||
if (text != null && text.isNotEmpty()) {
|
||||
- cleanRecognizer();
|
||||
+ // Don't auto-stop the recogniser - we want to do that when the user
|
||||
+ // presses on "stop" only.
|
||||
+ // cleanRecognizer();
|
||||
sendEvent("onResult", text)
|
||||
}
|
||||
}
|
||||
@@ -93,12 +105,11 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
||||
@ReactMethod
|
||||
fun loadModel(path: String, promise: Promise) {
|
||||
cleanModel();
|
||||
- StorageService.unpack(context, path, "models",
|
||||
- { model: Model? ->
|
||||
- this.model = model
|
||||
- promise.resolve("Model successfully loaded")
|
||||
- }
|
||||
- ) { e: IOException ->
|
||||
+
|
||||
+ try {
|
||||
+ this.model = Model(path);
|
||||
+ promise.resolve("Model successfully loaded")
|
||||
+ } catch (e: IOException) {
|
||||
this.model = null
|
||||
promise.reject(e)
|
||||
}
|
||||
@@ -153,6 +164,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
||||
cleanRecognizer();
|
||||
}
|
||||
|
||||
+ @ReactMethod
|
||||
+ fun stopOnly() {
|
||||
+ if (speechService != null) {
|
||||
+ speechService!!.stop()
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @ReactMethod
|
||||
+ fun cleanup() {
|
||||
+ if (speechService != null) {
|
||||
+ speechService!!.shutdown();
|
||||
+ speechService = null
|
||||
+ }
|
||||
+ if (recognizer != null) {
|
||||
+ recognizer!!.close();
|
||||
+ recognizer = null;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
@ReactMethod
|
||||
fun unload() {
|
||||
cleanRecognizer();
|
||||
diff --git a/lib/typescript/index.d.ts b/lib/typescript/index.d.ts
|
||||
index 441e41cc402cca3a60b34978ef4fea976076259c..a173acebb4b314402550442ad471e0f7c706e3c4 100644
|
||||
--- a/lib/typescript/index.d.ts
|
||||
+++ b/lib/typescript/index.d.ts
|
||||
@@ -10,6 +10,8 @@ export default class Vosk {
|
||||
currentRegisteredEvents: EmitterSubscription[];
|
||||
start: (grammar?: string[] | null) => Promise<String>;
|
||||
stop: () => void;
|
||||
+ stopOnly: () => void;
|
||||
+ cleanup: () => void;
|
||||
unload: () => void;
|
||||
onResult: (onResult: (e: VoskEvent) => void) => EventSubscription;
|
||||
onFinalResult: (onFinalResult: (e: VoskEvent) => void) => EventSubscription;
|
||||
diff --git a/package.json b/package.json
|
||||
index 707eddb8d68007f93071ac659c5b087c935c5f01..90ebe20f224eeec472c377df1fef9b15f2ff8200 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -11,12 +11,9 @@
|
||||
"src",
|
||||
"lib",
|
||||
"android",
|
||||
- "ios",
|
||||
"cpp",
|
||||
- "react-native-vosk.podspec",
|
||||
"!lib/typescript/example",
|
||||
"!android/build",
|
||||
- "!ios/build",
|
||||
"!**/__tests__",
|
||||
"!**/__fixtures__",
|
||||
"!**/__mocks__"
|
||||
diff --git a/react-native-vosk.podspec b/react-native-vosk.podspec
|
||||
deleted file mode 100644
|
||||
index e3d41b90c5eef890c7a5108aaf16ac07d34a698b..0000000000000000000000000000000000000000
|
||||
--- a/react-native-vosk.podspec
|
||||
+++ /dev/null
|
||||
@@ -1,41 +0,0 @@
|
||||
-require "json"
|
||||
-
|
||||
-package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
||||
-folly_version = '2021.06.28.00-v2'
|
||||
-folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
||||
-
|
||||
-Pod::Spec.new do |s|
|
||||
- s.name = "react-native-vosk"
|
||||
- s.version = package["version"]
|
||||
- s.summary = package["description"]
|
||||
- s.homepage = package["homepage"]
|
||||
- s.license = package["license"]
|
||||
- s.authors = package["author"]
|
||||
-
|
||||
- s.platforms = { :ios => "10.0" }
|
||||
- s.source = { :git => "https://github.com/riderodd/react-native-vosk.git", :tag => "#{s.version}" }
|
||||
-
|
||||
- s.source_files = "ios/**/*.{h,m,mm,swift}"
|
||||
- s.resource_bundles = { 'Vosk' => ['ios/Vosk/*'] }
|
||||
-
|
||||
- s.dependency "React-Core"
|
||||
- s.frameworks = "Accelerate"
|
||||
- s.library = "c++"
|
||||
- s.vendored_frameworks = "ios/libvosk.xcframework"
|
||||
- s.requires_arc = true
|
||||
-
|
||||
- # Don't install the dependencies when we run `pod install` in the old architecture.
|
||||
- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
||||
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
||||
- s.pod_target_xcconfig = {
|
||||
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
||||
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
||||
- }
|
||||
-
|
||||
- s.dependency "React-Codegen"
|
||||
- s.dependency "RCT-Folly", folly_version
|
||||
- s.dependency "RCTRequired"
|
||||
- s.dependency "RCTTypeSafety"
|
||||
- s.dependency "ReactCommon/turbomodule/core"
|
||||
- end
|
||||
-end
|
||||
diff --git a/src/index.tsx b/src/index.tsx
|
||||
index d9f90c921d89b1b4d85e145443ed3376546a368a..29e4068dbd7500828a73145bd25497a52c9bf638 100644
|
||||
--- a/src/index.tsx
|
||||
+++ b/src/index.tsx
|
||||
@@ -69,6 +69,15 @@ export default class Vosk {
|
||||
VoskModule.stop();
|
||||
};
|
||||
|
||||
+ stopOnly = () => {
|
||||
+ VoskModule.stopOnly();
|
||||
+ };
|
||||
+
|
||||
+ cleanup = () => {
|
||||
+ this.cleanListeners();
|
||||
+ VoskModule.cleanup();
|
||||
+ };
|
||||
+
|
||||
unload = () => {
|
||||
this.cleanListeners();
|
||||
VoskModule.unload();
|
||||
BIN
Assets/WebsiteAssets/images/md_plugins/abc/PeacherineRag.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
Assets/WebsiteAssets/images/md_plugins/abc/Tablature.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-mobile-rte.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-note-history.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-publish-notes.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-scan-notebook.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
Assets/WebsiteAssets/images/news/20250922-tag-editor.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/DoMyEssay.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/NotGamStop.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/PokiesLab.png
Normal file
|
After Width: | Height: | Size: 378 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/Pokiesman.png
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/SocialKings.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Assets/WebsiteAssets/images/sponsors/WriteMyEssay.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
@@ -1,4 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 28 Apr 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 28 Apr 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.3]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 22 Sep 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 3.4]]></title><description><![CDATA[<p>Joplin 3.4 includes many bug fixes and improvements, with a focus on the mobile app.</p>
|
||||
<h2>Mobile<a name="mobile" href="#mobile" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Rich Text Editor<a name="rich-text-editor" href="#rich-text-editor" class="heading-anchor">🔗</a></h3>
|
||||
<p>The mobile app now includes a beta <a href="https://joplinapp.org/help/apps/rich_text_editor">Rich Text Editor</a>! The new editor renders formatting/math/images within the editor:</p>
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-mobile-rte.png" width="400" alt="screenshot: Mobile Rich Text Editor editing the welcome notes. Images, headings, etc are rendering."/>
|
||||
<p>To try it, 1) open a note in the default Markdown editor 2) open the note actions menu (the three vertical dots) for the note and 3) click “Edit as Rich Text”.</p>
|
||||
<p>Be aware that this editor is still in active development and <a href="https://github.com/laurent22/joplin/issues/12840">has a number of known limitations and issues</a>. The Rich Text editor is based on <a href="https://prosemirror.net/">ProseMirror</a> and will behave differently from the desktop Rich Text Editor in many cases.</p>
|
||||
<h3>Support for publishing notes with Joplin Cloud and Server<a name="support-for-publishing-notes-with-joplin-cloud-and-server" href="#support-for-publishing-notes-with-joplin-cloud-and-server" class="heading-anchor">🔗</a></h3>
|
||||
<p>It's now possible to <a href="https://joplinapp.org/help/apps/publish_note">publish notes</a> from the mobile app! To do so, open the “Properties” menu for a note, then click “Publish/unpublish”:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-publish-notes.png" alt="screenshot: A Publish/unpublish note action is shown in the "Note properties" sidebar, just below a "Previous versions" button"></p>
|
||||
<p>Next, in the “publish note” dialog, click “Copy shareable link”. Notes can later be unpublished by clicking "Unpublish" in the publication dialog.</p>
|
||||
<h3>Viewing note history<a name="viewing-note-history" href="#viewing-note-history" class="heading-anchor">🔗</a></h3>
|
||||
<p>It is now possible to view and restore previous note versions from the mobile app. Like the "publish note" feature, previous note versions can be accessed from the note properties menu.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-note-history.png" alt="screenshot: The note history page"></p>
|
||||
<p>As on desktop, the note history feature can be configured from the “Note History” tab in settings.</p>
|
||||
<h3>Updated tag dialog<a name="updated-tag-dialog" href="#updated-tag-dialog" class="heading-anchor">🔗</a></h3>
|
||||
<p>The tag dialog has been redesigned, with a new UI for adding, removing, and creating new tags:<br>
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-tag-editor.png" width="500" alt="screenshot: Tag dialog now consists of three sections: Added tags, Add new tags, Actions."/></p>
|
||||
<h3>Android: Improved voice typing<a name="android-improved-voice-typing" href="#android-improved-voice-typing" class="heading-anchor">🔗</a></h3>
|
||||
<p>The voice typing feature on Android has been updated with <a href="https://github.com/laurent22/joplin/pull/12404">improved silence detection</a> and a new “<a href="https://github.com/laurent22/joplin/pull/12370">custom glossary</a>” setting. Voice typing also now <a href="https://github.com/laurent22/joplin/pull/12352">defaults to a more accurate (but somewhat slower) model</a>.</p>
|
||||
<h3>Quickly creating a note from multiple photos<a name="quickly-creating-a-note-from-multiple-photos" href="#quickly-creating-a-note-from-multiple-photos" class="heading-anchor">🔗</a></h3>
|
||||
<p>A “scan notebook” action has been added to the “New note” menu:</p>
|
||||
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20250922-scan-notebook.png" width="500"/>
|
||||
<p>This action allows quickly creating a new note with multiple pictures taken from the camera.</p>
|
||||
<h2>Desktop<a name="desktop" href="#desktop" class="heading-anchor">🔗</a></h2>
|
||||
<h3>More Markdown Editor settings<a name="more-markdown-editor-settings" href="#more-markdown-editor-settings" class="heading-anchor">🔗</a></h3>
|
||||
<p>The "Note" tab in settings now includes new settings for the Markdown editor, including:</p>
|
||||
<ul>
|
||||
<li>An option to render headers, lists, and certain other formatting within the editor.</li>
|
||||
<li>An option to render images in the editor.</li>
|
||||
</ul>
|
||||
<p>When enabled, these settings bring the Markdown editor closer to the Rich Text Editor, without <a href="https://joplinapp.org/help/apps/rich_text_editor">some of the Rich Text Editor's limitations</a>.</p>
|
||||
<p>These settings are also available on mobile.</p>
|
||||
<h3>Smaller application size and faster startup<a name="smaller-application-size-and-faster-startup" href="#smaller-application-size-and-faster-startup" class="heading-anchor">🔗</a></h3>
|
||||
<p>We've made the desktop application roughly 33% smaller! In addition to faster application startup, this means that the desktop app should be faster to download take up less space.</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Joplin version</th>
|
||||
<th>Previous size (v3.3.13)</th>
|
||||
<th>New size (v3.4.12)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Joplin for MacOS (ARM)</td>
|
||||
<td>211 MB</td>
|
||||
<td>141 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Joplin for Windows (installer)</td>
|
||||
<td>321 MB</td>
|
||||
<td>219 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Joplin for Windows (portable)</td>
|
||||
<td>320 MB</td>
|
||||
<td>219 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Joplin for Linux (AppImage)</td>
|
||||
<td>219 MB</td>
|
||||
<td>147 MB</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Terminal app<a name="terminal-app" href="#terminal-app" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Collapsible folders<a name="collapsible-folders" href="#collapsible-folders" class="heading-anchor">🔗</a></h3>
|
||||
<p>The <a href="https://joplinapp.org/help/apps/terminal/">terminal application</a> now supports expanding and collapsing folders by pressing <kbd>z</kbd>. For additional information, see <a href="https://github.com/laurent22/joplin/pull/12718">the original pull request</a>.</p>
|
||||
<h3>Managing shared notebooks and published notes<a name="managing-shared-notebooks-and-published-notes" href="#managing-shared-notebooks-and-published-notes" class="heading-anchor">🔗</a></h3>
|
||||
<p>New commands have been added to the terminal app, including <code>publish</code>, <code>unpublish</code>, and <code>share</code>. This allows the terminal app to manage shared folders and published notes.</p>
|
||||
<h2>Bug fixes<a name="bug-fixes" href="#bug-fixes" class="heading-anchor">🔗</a></h2>
|
||||
<p>For the full list of changes, see <a href="https://joplinapp.org/help/about/changelog/desktop/">the desktop changelog</a> and <a href="https://joplinapp.org/help/about/changelog/android/">the mobile changelog</a>.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20250922-release-3-4</link><guid isPermaLink="false">20250922-release-3-4</guid><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[What's new in Joplin 3.3]]></title><description><![CDATA[<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Accessibility improvements<a name="accessibility-improvements" href="#accessibility-improvements" class="heading-anchor">🔗</a></h3>
|
||||
<p>The Joplin 3.3 release introduces significant accessibility enhancements designed to make the application more inclusive and user-friendly. Users can now benefit from improved keyboard navigation, thanks to newly added shortcuts and clearer labels that streamline interaction across the interface. We've also added a "go to viewer" menu item that moves focus from the note editor to the note viewer. Focus is moved to the location in the viewer corresponding to the location of the cursor in the editor.</p>
|
||||
<p>Screen reader support has been bolstered, ensuring elements like the note list and sidebar are easier to toggle and interact with. These updates make the application more usable for individuals relying on assistive technologies.</p>
|
||||
@@ -446,10 +519,4 @@ sys 0m38.013s</p>
|
||||
<p>Unfortunately we cannot publish the Android version because it is based on a framework version that Google does not accept. To upgrade the app a lot of changes are needed and another round of pre-releases, and therefore there will not be a 2.9 version for Google Play. You may however download the official APK directly from there: <a href="https://github.com/laurent22/joplin-android/releases/tag/android-v2.9.8">Android 2.9 Official Release</a></p>
|
||||
<p>This is the reality of app stores in general - small developers being imposed never ending new requirements by all-powerful companies, and by the time a version is finally ready we can't even publish it because yet more requirements are in place.</p>
|
||||
<p>For the record the current 2.9 app works perfectly fine. It targets Android 11, which is only 2 years old and is still supported (and installed on millions of phones). Google requires us to target Android 12 which only came out last year.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20221216-release-2-9</link><guid isPermaLink="false">20221216-release-2-9</guid><pubDate>Fri, 16 Dec 2022 00:00:00 GMT</pubDate><twitter-text>What's new in Joplin 2.9</twitter-text></item><item><title><![CDATA[Joplin is hiring!]]></title><description><![CDATA[<p>Joplin is an open source note-taking app. Capture your thoughts and securely access them from any device.</p>
|
||||
<p>We are looking to hire two JavaScript software developers to work on the desktop, mobile, and server applications. All those are built using modern technologies, including React, React Native and Electron with a strong focus on test units.</p>
|
||||
<p>You need to demonstrate some experience with at least some of these technologies, and willing to learn more and touch various different projects.</p>
|
||||
<p>You will be part of a small team, so you will have an opportunity for a high-impact role, targeting hundreds of thousands of users.</p>
|
||||
<p>If you're interested please contact us at job-AT-joplin.cloud</p>
|
||||
<p>No agencies please.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20221209-job</link><guid isPermaLink="false">20221209-job</guid><pubDate>Fri, 09 Dec 2022 00:00:00 GMT</pubDate><twitter-text>Joplin is hiring!</twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20221216-release-2-9</link><guid isPermaLink="false">20221216-release-2-9</guid><pubDate>Fri, 16 Dec 2022 00:00:00 GMT</pubDate><twitter-text>What's new in Joplin 2.9</twitter-text></item></channel></rss>
|
||||
@@ -2,7 +2,7 @@
|
||||
# Build stage
|
||||
# =============================================================================
|
||||
|
||||
FROM node:18 AS builder
|
||||
FROM node:24 AS builder
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
@@ -58,16 +58,29 @@ RUN --mount=type=cache,target=/build/.yarn/cache --mount=type=cache,target=/buil
|
||||
# from a smaller base image.
|
||||
# =============================================================================
|
||||
|
||||
FROM node:18-slim
|
||||
FROM node:24-slim
|
||||
|
||||
ARG user=joplin
|
||||
RUN useradd --create-home --shell /bin/bash $user
|
||||
|
||||
# Install PM2 and set home directory. Setting the PM2 data dir so modules/config persist regardless
|
||||
# of user home.
|
||||
RUN npm i -g pm2@5.4.3 && mkdir -p /opt/pm2 && chown -R $user:$user /opt/pm2
|
||||
ENV PM2_HOME=/opt/pm2
|
||||
|
||||
USER $user
|
||||
|
||||
COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages
|
||||
COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini
|
||||
|
||||
# We download a specific version of the plugin to prevent pm2 from fetching the latest, since it may
|
||||
# not have been properly audited (that fact was used to spread malware at some point). Ref:
|
||||
# https://github.com/laurent22/joplin/issues/12754
|
||||
RUN pm2 install https://registry.npmjs.org/pm2-logrotate/-/pm2-logrotate-3.0.0.tgz \
|
||||
&& pm2 set pm2-logrotate:max_size 100MB \
|
||||
&& pm2 set pm2-logrotate:retain 5 \
|
||||
&& pm2 set pm2-logrotate:compress true
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV RUNNING_IN_DOCKER=1
|
||||
EXPOSE ${APP_PORT}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:18-bullseye
|
||||
FROM node:24-bullseye
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
|
||||
@@ -67,6 +67,45 @@ showHelp() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Accepts two versions in symver (a.b.c).
|
||||
# Echos -1 if the first version is less than the second,
|
||||
# 0 if they're equal,
|
||||
# 1 if the first version is greater than second.
|
||||
compareVersions() {
|
||||
V_MAJOR1=$(echo "$1"|cut -d. -f1)
|
||||
V_MAJOR2=$(echo "$2"|cut -d. -f1)
|
||||
|
||||
if [[ $V_MAJOR1 -lt $V_MAJOR2 ]] ; then
|
||||
echo -1
|
||||
return
|
||||
elif [[ $V_MAJOR1 -gt $V_MAJOR2 ]] ; then
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
V_MINOR1=$(echo "$1"|cut -d. -f2)
|
||||
V_MINOR2=$(echo "$2"|cut -d. -f2)
|
||||
|
||||
if [[ $V_MINOR1 -lt $V_MINOR2 ]] ; then
|
||||
echo -1
|
||||
return
|
||||
elif [[ $V_MINOR1 -gt $V_MINOR2 ]] ; then
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
V_PATCH1=$(echo "$1"|cut -d. -f3)
|
||||
V_PATCH2=$(echo "$2"|cut -d. -f3)
|
||||
|
||||
if [[ $V_PATCH1 -lt $V_PATCH2 ]] ; then
|
||||
echo -1
|
||||
elif [[ $V_PATCH1 -gt $V_PATCH2 ]] ; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
#-----------------------------------------------------
|
||||
# Setup Download Helper: DL
|
||||
#-----------------------------------------------------
|
||||
@@ -258,6 +297,15 @@ fi
|
||||
if [[ $DESKTOP =~ .*gnome.*|.*kde.*|.*xfce.*|.*mate.*|.*lxqt.*|.*unity.*|.*x-cinnamon.*|.*deepin.*|.*pantheon.*|.*lxde.*|.*i3.*|.*sway.* ]] || [[ `command -v update-desktop-database` ]]; then
|
||||
DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
||||
DESKTOP_FILE_LOCATION="$DATA_HOME/applications"
|
||||
|
||||
# Only later versions of Joplin default to Wayland
|
||||
IS_WAYLAND_BY_DEFAULT=$(compareVersions "$RELEASE_VERSION" "3.5.6")
|
||||
# Joplin has a different startup WM class on Wayland and X11:
|
||||
STARTUP_WM_CLASS=Joplin
|
||||
if [[ $XDG_SESSION_TYPE != "x11" && $IS_WAYLAND_BY_DEFAULT == "1" ]]; then
|
||||
STARTUP_WM_CLASS=@joplin/app-desktop
|
||||
fi
|
||||
|
||||
# Only delete the desktop file if it will be replaced
|
||||
rm -f "$DESKTOP_FILE_LOCATION/appimagekit-joplin.desktop"
|
||||
|
||||
@@ -272,7 +320,9 @@ Name=Joplin
|
||||
Comment=Joplin for Desktop
|
||||
Exec=env APPIMAGELAUNCHER_DISABLE=TRUE "${INSTALL_DIR}/Joplin.AppImage" ${SANDBOXPARAM} %u
|
||||
Icon=joplin
|
||||
StartupWMClass=Joplin
|
||||
# This will be different between Wayland and X11. On Wayland, the startup
|
||||
# WM class is "@joplin/app-desktop". On X11, it's "Joplin".
|
||||
StartupWMClass=${STARTUP_WM_CLASS}
|
||||
Type=Application
|
||||
Categories=Office;
|
||||
MimeType=x-scheme-handler/joplin;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- DONATELINKS -->
|
||||
[](https://www.paypal.com/donate/?business=E8JMYD2LQ8MMA&no_recurring=0&item_name=I+rely+on+donations+to+maintain+and+improve+the+Joplin+open+source+project.+Thank+you+for+your+help+-+it+makes+a+difference%21¤cy_code=EUR) [](https://github.com/sponsors/laurent22/) [](https://www.patreon.com/joplin) [](https://joplinapp.org/donate/#donations)
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=WQCERTSSLCC7U) [](https://github.com/sponsors/laurent22/) [](https://www.patreon.com/joplin) [](https://joplinapp.org/donate/#donations)
|
||||
<!-- DONATELINKS -->
|
||||
|
||||
<img width="64" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/LinuxIcons/256x256.png" align="left" style="margin-right:15px"/>
|
||||
@@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
|
||||
# Sponsors
|
||||
|
||||
<!-- SPONSORS-ORG -->
|
||||
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://essayservice.com"><img title="quick and reliable service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="quick and reliable service to write my paper for me"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://damangameplay.in"><img title="Daman Game" width="256" src="https://joplinapp.org/images/sponsors/DamanGame.png" alt="Daman Game"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a>
|
||||
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://citricsheep.com"><img title="Citric Sheep" width="256" src="https://joplinapp.org/images/sponsors/CitricSheep.png"/></a> <a href="https://sorted.travel/?utm_source=joplinapp"><img title="Sorted Travel" width="256" src="https://joplinapp.org/images/sponsors/SortedTravel.png"/></a> <a href="https://celebian.com"><img title="Celebian" width="256" src="https://joplinapp.org/images/sponsors/Celebian.png"/></a> <a href="https://bestkru.com"><img title="BestKru" width="256" src="https://joplinapp.org/images/sponsors/BestKru.png"/></a> <a href="https://www.socialfollowers.uk/buy-tiktok-followers/"><img title="Social Followers" width="256" src="https://joplinapp.org/images/sponsors/SocialFollowers.png"/></a> <a href="https://stormlikes.com/"><img title="Stormlikes" width="256" src="https://joplinapp.org/images/sponsors/Stormlikes.png"/></a> <a href="https://route4me.com"><img title="Route4Me" width="256" src="https://joplinapp.org/images/sponsors/Route4Me.png"/></a> <a href="https://topagency.webflow.io"><img title="WebDesignAgency" width="256" src="https://joplinapp.org/images/sponsors/WebDesignAgency.png" alt="topagency"/></a> <a href="https://www.slotozilla.com/nz/no-deposit-bonus"><img title="casino without making any upfront cost" width="256" src="https://joplinapp.org/images/sponsors/Slotozilla.png" alt="casino without making any upfront cost"/></a> <a href="https://writepaper.com/"><img title="best service to write my paper for me" width="256" src="https://joplinapp.org/images/sponsors/WritePaper.png" alt="best service to write my paper for me"/></a> <a href="https://paperwriter.com/"><img title="high-quality paper writing service PaperWriter" width="256" src="https://joplinapp.org/images/sponsors/PaperWriter.png" alt="high-quality paper writing service PaperWriter"/></a> <a href="https://www.bestetf.net/"><img title="BestETF" width="256" src="https://joplinapp.org/images/sponsors/BestEtf.png" alt="BestETF"/></a> <a href="https://freespinny.io/free-spins-no-deposit/"><img title="Freespinny.io Free Spins Bonus site" width="256" src="https://joplinapp.org/images/sponsors/Freespinny.png" alt="Freespinny.io Free Spins Bonus site"/></a> <a href="https://essayshark.com"><img title="EssayShark - essay writers for hire" width="256" src="https://joplinapp.org/images/sponsors/EssayShark.png" alt="EssayShark - essay writers for hire"/></a> <a href="https://pokieslab1.com/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/PokiesLab.png" alt="Australian Real Money Pokies"/></a> <a href="https://pokiesman1.net/real-money-pokies/"><img title="Australian Real Money Pokies" width="256" src="https://joplinapp.org/images/sponsors/Pokiesman.png" alt="Australian Real Money Pokies"/></a> <a href="https://domyessay.com"><img title="Essay writers DoMyEssay are dedicated to providing top-notch, custom-written papers that meet your academic requirements" width="256" src="https://joplinapp.org/images/sponsors/DoMyEssay.png" alt="DoMyEssay"/></a> <a href="https://essaypro.com/"><img title="best essay writing service" width="256" src="https://joplinapp.org/images/sponsors/EssayPro.png" alt="best essay writing service"/></a> <a href="https://socialkings.online"><img title="Boost your reach and buy real followers" width="256" src="https://joplinapp.org/images/sponsors/SocialKings.png" alt="Boost your reach and buy real followers"/></a> <a href="https://uk.notgamstop.com/bonuses/free-spins-no-deposit-no-gamstop/"><img title="free spins no deposit at NotGamstop" width="256" src="https://joplinapp.org/images/sponsors/NotGamStop.jpg" alt="free spins no deposit at NotGamstop"/></a> <a href="https://www.writemyessay.com/"><img title="writing service for students WriteMyEssay" width="256" src="https://joplinapp.org/images/sponsors/WriteMyEssay.png" alt="writing service for students WriteMyEssay"/></a> <a href="https://essayservice.com/"><img title="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines" width="256" src="https://joplinapp.org/images/sponsors/EssayService.png" alt="For those in need of immediate academic assistance, EssayService offers a fast and reliable service to write my essay for me now, ensuring high-quality results within tight deadlines"/></a>
|
||||
<!-- SPONSORS-ORG -->
|
||||
|
||||
* * *
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"vips.dev": {
|
||||
"platforms": ["aarch64-darwin"],
|
||||
},
|
||||
"nodejs": "23.10.0",
|
||||
"nodejs": "24.4.1",
|
||||
"pkg-config": "latest",
|
||||
"darwin.apple_sdk.frameworks.Foundation": { // satisfies missing CoreText/CoreText.h
|
||||
// https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/darwin/apple-sdk/default.nix
|
||||
@@ -22,7 +22,7 @@
|
||||
"version": "latest",
|
||||
"excluded_platforms": ["aarch64-darwin", "x86_64-darwin"],
|
||||
},
|
||||
"git": "2.48.1",
|
||||
"git": "2.50.1",
|
||||
},
|
||||
"shell": {
|
||||
"init_hook": [
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
# SLAVE_POSTGRES_PORT=5433
|
||||
# SLAVE_POSTGRES_HOST=localhost
|
||||
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
|
||||
postgresql-master:
|
||||
image: 'bitnami/postgresql:17.3.0'
|
||||
image: 'bitnamilegacy/postgresql:17.5.0'
|
||||
ports:
|
||||
- '5432:5432'
|
||||
environment:
|
||||
@@ -38,7 +36,7 @@ services:
|
||||
- POSTGRESQL_EXTRA_FLAGS=-c work_mem=100000 -c log_statement=all
|
||||
|
||||
postgresql-slave:
|
||||
image: 'bitnami/postgresql:17.3.0'
|
||||
image: 'bitnamilegacy/postgresql:17.5.0'
|
||||
ports:
|
||||
- '5433:5432'
|
||||
depends_on:
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# This compose file can be used in development to run both the database and app
|
||||
# within Docker.
|
||||
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
# - This would typically be mapped to port to 443 (TLS) with a reverse proxy.
|
||||
# - If Joplin Server does not need to be accessible over the internet, the port can be mapped to 22300.
|
||||
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
transcribe-network:
|
||||
|
||||
21
package.json
@@ -76,32 +76,31 @@
|
||||
"cspell": "5.21.2",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-interactive": "10.8.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-jest": "27.9.0",
|
||||
"eslint-plugin-promise": "6.6.0",
|
||||
"eslint-plugin-react": "7.37.4",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"execa": "5.1.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "11.0.2",
|
||||
"glob": "11.0.3",
|
||||
"gulp": "4.0.2",
|
||||
"husky": "9.1.7",
|
||||
"lerna": "3.22.1",
|
||||
"lint-staged": "15.5.2",
|
||||
"lint-staged": "16.1.6",
|
||||
"madge": "8.0.0",
|
||||
"npm-package-json-lint": "8.0.0",
|
||||
"typescript": "5.8.2"
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"eslint-plugin-github": "4.10.2",
|
||||
"http-server": "14.1.1",
|
||||
"node-gyp": "11.2.0",
|
||||
"node-gyp": "11.3.0",
|
||||
"nodemon": "3.1.10"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"resolutions": {
|
||||
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
|
||||
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
|
||||
"eslint": "patch:eslint@8.57.1#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
|
||||
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch",
|
||||
"nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch",
|
||||
@@ -118,6 +117,12 @@
|
||||
"pdfjs-dist@*": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
|
||||
"pdfjs-dist@3.11.174": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch",
|
||||
"canvas@npm:^2.11.2": "link:./.yarn/joplin-empty-package/",
|
||||
"node-gyp@npm:^9.0.0": "11.2.0"
|
||||
"node-gyp@npm:^9.0.0": "11.2.0",
|
||||
"depd@npm:^2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:~2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:~1.1.2": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:2.0.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:^1.1.2": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch",
|
||||
"depd@npm:^1.1.0": "patch:depd@npm%3A2.0.0#~/.yarn/patches/depd-npm-2.0.0-b6c51a4b43.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,8 +417,10 @@ class Application extends BaseApplication {
|
||||
if (argv.length) {
|
||||
this.gui_ = this.dummyGui();
|
||||
|
||||
const initialFolder = await Folder.load(Setting.value('activeFolderId'));
|
||||
await this.switchCurrentFolder(initialFolder);
|
||||
await this.applySettingsSideEffects();
|
||||
await this.refreshCurrentFolder();
|
||||
|
||||
try {
|
||||
await this.execCommand(argv);
|
||||
} catch (error) {
|
||||
@@ -432,6 +434,7 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
await Setting.saveAll();
|
||||
await this.database_.close();
|
||||
|
||||
// Need to call exit() explicitly, otherwise Node wait for any timeout to complete
|
||||
// https://stackoverflow.com/questions/18050095
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const Logger = require('@joplin/utils/Logger').default;
|
||||
const { dirname } = require('@joplin/lib/path-utils');
|
||||
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
|
||||
const JoplinDatabase = require('@joplin/lib/JoplinDatabase').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
const baseDir = `${dirname(__dirname)}/tests/cli-integration`;
|
||||
const joplinAppPath = `${__dirname}/main.js`;
|
||||
|
||||
const logger = new Logger();
|
||||
logger.addTarget('console');
|
||||
logger.setLevel(Logger.LEVEL_ERROR);
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget('console');
|
||||
dbLogger.setLevel(Logger.LEVEL_INFO);
|
||||
|
||||
const db = new JoplinDatabase(new DatabaseDriverNode());
|
||||
db.setLogger(dbLogger);
|
||||
|
||||
function createClient(id) {
|
||||
return {
|
||||
id: id,
|
||||
profileDir: `${baseDir}/client${id}`,
|
||||
};
|
||||
}
|
||||
|
||||
const client = createClient(1);
|
||||
|
||||
function execCommand(client, command) {
|
||||
const exePath = `node ${joplinAppPath}`;
|
||||
const cmd = `${exePath} --update-geolocation-disabled --env dev --profile ${client.profileDir} ${command}`;
|
||||
logger.info(`${client.id}: ${command}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
logger.error(stderr);
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(stdout.trim());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assertTrue(v) {
|
||||
if (!v) throw new Error(sprintf('Expected "true", got "%s"."', v));
|
||||
process.stdout.write('.');
|
||||
}
|
||||
|
||||
function assertFalse(v) {
|
||||
if (v) throw new Error(sprintf('Expected "false", got "%s"."', v));
|
||||
process.stdout.write('.');
|
||||
}
|
||||
|
||||
function assertEquals(expected, real) {
|
||||
if (expected !== real) throw new Error(sprintf('Expecting "%s", got "%s"', expected, real));
|
||||
process.stdout.write('.');
|
||||
}
|
||||
|
||||
async function clearDatabase() {
|
||||
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
|
||||
}
|
||||
|
||||
const testUnits = {};
|
||||
|
||||
testUnits.testFolders = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
|
||||
let folders = await Folder.all();
|
||||
assertEquals(1, folders.length);
|
||||
assertEquals('nb1', folders[0].title);
|
||||
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
assertEquals(1, folders.length);
|
||||
assertEquals('nb1', folders[0].title);
|
||||
|
||||
await execCommand(client, 'rm -r -f nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
assertEquals(0, folders.length);
|
||||
};
|
||||
|
||||
testUnits.testNotes = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote n1');
|
||||
|
||||
let notes = await Note.all();
|
||||
assertEquals(1, notes.length);
|
||||
assertEquals('n1', notes[0].title);
|
||||
|
||||
await execCommand(client, 'rm -f n1');
|
||||
notes = await Note.all();
|
||||
assertEquals(0, notes.length);
|
||||
|
||||
await execCommand(client, 'mknote n1');
|
||||
await execCommand(client, 'mknote n2');
|
||||
|
||||
notes = await Note.all();
|
||||
assertEquals(2, notes.length);
|
||||
|
||||
await execCommand(client, 'rm -f \'blabla*\'');
|
||||
|
||||
notes = await Note.all();
|
||||
assertEquals(2, notes.length);
|
||||
|
||||
await execCommand(client, 'rm -f \'n*\'');
|
||||
|
||||
notes = await Note.all();
|
||||
assertEquals(0, notes.length);
|
||||
};
|
||||
|
||||
testUnits.testCat = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote mynote');
|
||||
|
||||
const folder = await Folder.loadByTitle('nb1');
|
||||
const note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');
|
||||
|
||||
let r = await execCommand(client, 'cat mynote');
|
||||
assertTrue(r.indexOf('mynote') >= 0);
|
||||
assertFalse(r.indexOf(note.id) >= 0);
|
||||
|
||||
r = await execCommand(client, 'cat -v mynote');
|
||||
assertTrue(r.indexOf(note.id) >= 0);
|
||||
};
|
||||
|
||||
testUnits.testConfig = async () => {
|
||||
await execCommand(client, 'config editor vim');
|
||||
await Setting.load();
|
||||
assertEquals('vim', Setting.value('editor'));
|
||||
|
||||
await execCommand(client, 'config editor subl');
|
||||
await Setting.load();
|
||||
assertEquals('subl', Setting.value('editor'));
|
||||
|
||||
const r = await execCommand(client, 'config');
|
||||
assertTrue(r.indexOf('editor') >= 0);
|
||||
assertTrue(r.indexOf('subl') >= 0);
|
||||
};
|
||||
|
||||
testUnits.testCp = async () => {
|
||||
await execCommand(client, 'mkbook nb2');
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote n1');
|
||||
|
||||
await execCommand(client, 'cp n1');
|
||||
|
||||
const f1 = await Folder.loadByTitle('nb1');
|
||||
const f2 = await Folder.loadByTitle('nb2');
|
||||
let notes = await Note.previews(f1.id);
|
||||
|
||||
assertEquals(2, notes.length);
|
||||
|
||||
await execCommand(client, 'cp n1 nb2');
|
||||
const notesF1 = await Note.previews(f1.id);
|
||||
assertEquals(2, notesF1.length);
|
||||
notes = await Note.previews(f2.id);
|
||||
assertEquals(1, notes.length);
|
||||
assertEquals(notesF1[0].title, notes[0].title);
|
||||
};
|
||||
|
||||
testUnits.testLs = async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote note1');
|
||||
await execCommand(client, 'mknote note2');
|
||||
const r = await execCommand(client, 'ls');
|
||||
|
||||
assertTrue(r.indexOf('note1') >= 0);
|
||||
assertTrue(r.indexOf('note2') >= 0);
|
||||
};
|
||||
|
||||
testUnits.testMv = async () => {
|
||||
await execCommand(client, 'mkbook nb2');
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote n1');
|
||||
await execCommand(client, 'mv n1 nb2');
|
||||
|
||||
const f1 = await Folder.loadByTitle('nb1');
|
||||
const f2 = await Folder.loadByTitle('nb2');
|
||||
let notes1 = await Note.previews(f1.id);
|
||||
let notes2 = await Note.previews(f2.id);
|
||||
|
||||
assertEquals(0, notes1.length);
|
||||
assertEquals(1, notes2.length);
|
||||
|
||||
await execCommand(client, 'mknote note1');
|
||||
await execCommand(client, 'mknote note2');
|
||||
await execCommand(client, 'mknote note3');
|
||||
await execCommand(client, 'mknote blabla');
|
||||
await execCommand(client, 'mv \'note*\' nb2');
|
||||
|
||||
notes1 = await Note.previews(f1.id);
|
||||
notes2 = await Note.previews(f2.id);
|
||||
|
||||
assertEquals(1, notes1.length);
|
||||
assertEquals(4, notes2.length);
|
||||
};
|
||||
|
||||
async function main() {
|
||||
await fs.remove(baseDir);
|
||||
|
||||
logger.info(await execCommand(client, 'version'));
|
||||
|
||||
await db.open({ name: `${client.profileDir}/database.sqlite` });
|
||||
BaseModel.setDb(db);
|
||||
await Setting.load();
|
||||
|
||||
let onlyThisTest = 'testMv';
|
||||
onlyThisTest = '';
|
||||
|
||||
for (const n in testUnits) {
|
||||
if (!testUnits.hasOwnProperty(n)) continue;
|
||||
if (onlyThisTest && n !== onlyThisTest) continue;
|
||||
|
||||
await clearDatabase();
|
||||
const testName = n.substr(4).toLowerCase();
|
||||
process.stdout.write(`${testName}: `);
|
||||
await testUnits[n]();
|
||||
console.info('');
|
||||
}
|
||||
}
|
||||
|
||||
main(process.argv).catch(error => {
|
||||
console.info('');
|
||||
logger.error(error);
|
||||
});
|
||||
270
packages/app-cli/app/cli-integration-tests.test.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import Logger, { TargetType } from '@joplin/utils/Logger';
|
||||
import { dirname } from '@joplin/lib/path-utils';
|
||||
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
|
||||
import JoplinDatabase from '@joplin/lib/JoplinDatabase';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { node } from 'execa';
|
||||
import { splitCommandString } from '@joplin/utils';
|
||||
const nodeSqlite = require('sqlite3');
|
||||
const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils');
|
||||
const { default: shimInitCli } = require('./utils/shimInitCli');
|
||||
|
||||
const baseDir = `${dirname(__dirname)}/tests/cli-integration`;
|
||||
const joplinAppPath = `${__dirname}/main.js`;
|
||||
|
||||
shimInitCli({ nodeSqlite, appVersion: () => require('../package.json').version, keytar: null });
|
||||
require('@joplin/lib/testing/test-utils');
|
||||
|
||||
|
||||
interface Client {
|
||||
id: number;
|
||||
profileDir: string;
|
||||
}
|
||||
|
||||
function createClient(id: number): Client {
|
||||
return {
|
||||
id: id,
|
||||
profileDir: `${baseDir}/client${id}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function execCommand(client: Client, command: string) {
|
||||
const result = await node(
|
||||
joplinAppPath,
|
||||
['--update-geolocation-disabled', '--env', 'dev', '--profile', client.profileDir, ...splitCommandString(command)],
|
||||
);
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(`Command failed: ${command}:\nstderr: ${result.stderr}\nstdout: ${result.stdout}`);
|
||||
}
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async function clearDatabase(db: JoplinDatabase) {
|
||||
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
|
||||
}
|
||||
|
||||
|
||||
describe('cli-integration-tests', () => {
|
||||
let client: Client;
|
||||
let db: JoplinDatabase;
|
||||
|
||||
beforeAll(async () => {
|
||||
await fs.remove(baseDir);
|
||||
await fs.mkdir(baseDir);
|
||||
|
||||
client = createClient(1);
|
||||
// Initialize the database by running a client command and exiting.
|
||||
await execCommand(client, 'version');
|
||||
|
||||
const dbLogger = new Logger();
|
||||
dbLogger.addTarget(TargetType.Console);
|
||||
dbLogger.setLevel(Logger.LEVEL_WARN);
|
||||
|
||||
db = new JoplinDatabase(new DatabaseDriverNode());
|
||||
db.setLogger(dbLogger);
|
||||
|
||||
await db.open({ name: `${client.profileDir}/database.sqlite` });
|
||||
BaseModel.setDb(db);
|
||||
Setting.setConstant('rootProfileDir', client.profileDir);
|
||||
Setting.setConstant('profileDir', client.profileDir);
|
||||
await loadKeychainServiceAndSettings([]);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await clearDatabase(db);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'version',
|
||||
'help',
|
||||
])('should run command %j without crashing', async (command) => {
|
||||
await execCommand(client, command);
|
||||
});
|
||||
|
||||
it('should support the \'ls\' command', async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote note1');
|
||||
await execCommand(client, 'mknote note2');
|
||||
const r = await execCommand(client, 'ls');
|
||||
|
||||
expect(r.indexOf('note1') >= 0).toBe(true);
|
||||
expect(r.indexOf('note2') >= 0).toBe(true);
|
||||
});
|
||||
|
||||
it('should support the \'mv\' command', async () => {
|
||||
await execCommand(client, 'mkbook nb2');
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote n1');
|
||||
await execCommand(client, 'mv n1 nb2');
|
||||
|
||||
const f1 = await Folder.loadByTitle('nb1');
|
||||
const f2 = await Folder.loadByTitle('nb2');
|
||||
let notes1 = await Note.previews(f1.id);
|
||||
let notes2 = await Note.previews(f2.id);
|
||||
|
||||
expect(notes1.length).toBe(0);
|
||||
expect(notes2.length).toBe(1);
|
||||
|
||||
await execCommand(client, 'mknote note1');
|
||||
await execCommand(client, 'mknote note2');
|
||||
await execCommand(client, 'mknote note3');
|
||||
await execCommand(client, 'mknote blabla');
|
||||
|
||||
notes1 = await Note.previews(f1.id);
|
||||
notes2 = await Note.previews(f2.id);
|
||||
|
||||
expect(notes1.length).toBe(4);
|
||||
expect(notes2.length).toBe(1);
|
||||
|
||||
await execCommand(client, 'mv \'note*\' nb2');
|
||||
|
||||
notes2 = await Note.previews(f2.id);
|
||||
notes1 = await Note.previews(f1.id);
|
||||
|
||||
expect(notes1.length).toBe(1);
|
||||
expect(notes2.length).toBe(4);
|
||||
});
|
||||
|
||||
it('should support the \'use\' command', async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mkbook nb2');
|
||||
await execCommand(client, 'mknote n1');
|
||||
await execCommand(client, 'mknote n2');
|
||||
|
||||
const f1 = await Folder.loadByTitle('nb1');
|
||||
const f2 = await Folder.loadByTitle('nb2');
|
||||
let notes1 = await Note.previews(f1.id);
|
||||
let notes2 = await Note.previews(f2.id);
|
||||
|
||||
expect(notes1.length).toBe(0);
|
||||
expect(notes2.length).toBe(2);
|
||||
|
||||
await execCommand(client, 'use nb1');
|
||||
await execCommand(client, 'mknote note2');
|
||||
await execCommand(client, 'mknote note3');
|
||||
|
||||
notes1 = await Note.previews(f1.id);
|
||||
notes2 = await Note.previews(f2.id);
|
||||
|
||||
expect(notes1.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should support creating and removing folders', async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
|
||||
let folders = await Folder.all();
|
||||
expect(folders.length).toBe(1);
|
||||
expect(folders[0].title).toBe('nb1');
|
||||
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
expect(folders.length).toBe(2);
|
||||
expect(folders[0].title).toBe('nb1');
|
||||
expect(folders[1].title).toBe('nb1');
|
||||
|
||||
await execCommand(client, 'rmbook -p -f nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
expect(folders.length).toBe(1);
|
||||
|
||||
await execCommand(client, 'rmbook -p -f nb1');
|
||||
|
||||
folders = await Folder.all();
|
||||
expect(folders.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should support creating and removing notes', async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote n1');
|
||||
|
||||
let notes = await Note.all();
|
||||
expect(notes.length).toBe(1);
|
||||
expect(notes[0].title).toBe('n1');
|
||||
|
||||
await execCommand(client, 'rmnote -p -f n1');
|
||||
notes = await Note.all();
|
||||
expect(notes.length).toBe(0);
|
||||
|
||||
await execCommand(client, 'mknote n1');
|
||||
await execCommand(client, 'mknote n2');
|
||||
|
||||
notes = await Note.all();
|
||||
expect(notes.length).toBe(2);
|
||||
|
||||
// Should fail to delete a non-existent note
|
||||
let failed = false;
|
||||
try {
|
||||
await execCommand(client, 'rmnote -f \'blabla*\'');
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
}
|
||||
expect(failed).toBe(true);
|
||||
|
||||
notes = await Note.all();
|
||||
expect(notes.length).toBe(2);
|
||||
|
||||
await execCommand(client, 'rmnote -f -p \'n*\'');
|
||||
|
||||
notes = await Note.all();
|
||||
expect(notes.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should support listing the contents of notes', async () => {
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote mynote');
|
||||
|
||||
const folder = await Folder.loadByTitle('nb1');
|
||||
const note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote');
|
||||
|
||||
let r = await execCommand(client, 'cat mynote');
|
||||
expect(r).toContain('mynote');
|
||||
expect(r).not.toContain(note.id);
|
||||
|
||||
r = await execCommand(client, 'cat -v mynote');
|
||||
expect(r).toContain(note.id);
|
||||
});
|
||||
|
||||
it('should support changing settings with config', async () => {
|
||||
await execCommand(client, 'config editor vim');
|
||||
await Setting.reset();
|
||||
await Setting.load();
|
||||
expect(Setting.value('editor')).toBe('vim');
|
||||
|
||||
await execCommand(client, 'config editor subl');
|
||||
await Setting.reset();
|
||||
await Setting.load();
|
||||
expect(Setting.value('editor')).toBe('subl');
|
||||
|
||||
const r = await execCommand(client, 'config');
|
||||
expect(r.indexOf('editor') >= 0).toBe(true);
|
||||
expect(r.indexOf('subl') >= 0).toBe(true);
|
||||
});
|
||||
|
||||
it('should support copying folders with cp', async () => {
|
||||
await execCommand(client, 'mkbook nb2');
|
||||
await execCommand(client, 'mkbook nb1');
|
||||
await execCommand(client, 'mknote n1');
|
||||
|
||||
await execCommand(client, 'cp n1');
|
||||
|
||||
const f1 = await Folder.loadByTitle('nb1');
|
||||
const f2 = await Folder.loadByTitle('nb2');
|
||||
let notes = await Note.previews(f1.id);
|
||||
|
||||
expect(notes.length).toBe(2);
|
||||
|
||||
await execCommand(client, 'cp n1 nb2');
|
||||
const notesF1 = await Note.previews(f1.id);
|
||||
expect(notesF1.length).toBe(2);
|
||||
notes = await Note.previews(f2.id);
|
||||
expect(notes.length).toBe(1);
|
||||
expect(notes[0].title).toBe(notesF1[0].title);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
public override async action() {
|
||||
this.stdout(versionInfo(require('./package.json'), {}).message);
|
||||
this.stdout(versionInfo(require('../package.json'), {}).message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { afterEachCleanUp } = require('@joplin/lib/testing/test-utils.js');
|
||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
const { default: shimInitCli } = require('./app/utils/shimInitCli');
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const sharp = require('sharp');
|
||||
const nodeSqlite = require('sqlite3');
|
||||
@@ -13,7 +13,7 @@ try {
|
||||
keytar = null;
|
||||
}
|
||||
|
||||
shimInit({ sharp, keytar, nodeSqlite });
|
||||
shimInitCli({ sharp, nodeSqlite, appVersion: () => require('./package.json').version, keytar });
|
||||
|
||||
global.afterEach(async () => {
|
||||
await afterEachCleanUp();
|
||||
|
||||
@@ -35,15 +35,15 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.1",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~3.4",
|
||||
"@joplin/renderer": "~3.4",
|
||||
"@joplin/utils": "~3.4",
|
||||
"@joplin/lib": "~3.5",
|
||||
"@joplin/renderer": "~3.5",
|
||||
"@joplin/utils": "~3.5",
|
||||
"aws-sdk": "2.1340.0",
|
||||
"chalk": "4.1.2",
|
||||
"compare-version": "0.1.2",
|
||||
@@ -57,7 +57,7 @@
|
||||
"proper-lockfile": "4.1.2",
|
||||
"redux": "4.2.1",
|
||||
"server-destroy": "1.0.1",
|
||||
"sharp": "0.34.2",
|
||||
"sharp": "0.34.3",
|
||||
"sprintf-js": "1.1.3",
|
||||
"sqlite3": "5.1.6",
|
||||
"string-padding": "1.0.2",
|
||||
@@ -70,14 +70,14 @@
|
||||
"yargs-parser": "21.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~3.4",
|
||||
"@joplin/tools": "~3.5",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "18.19.103",
|
||||
"@types/node": "18.19.130",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.7.0",
|
||||
"temp": "0.9.4",
|
||||
"typescript": "5.8.2"
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('MarkupToHtml', () => {
|
||||
pluginAssets: [],
|
||||
};
|
||||
|
||||
expect(await service.render(MarkupLanguage.Html, testString, {}, {})).toMatchObject(expectedOutput);
|
||||
expect(await service.render(MarkupLanguage.Markdown, testString, {}, {})).toMatchObject(expectedOutput);
|
||||
expect(await service.render(MarkupLanguage.Html, testString, {}, { })).toMatchObject(expectedOutput);
|
||||
expect(await service.render(MarkupLanguage.Markdown, testString, {}, { })).toMatchObject(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<en-media style="--en-viewerProps:{};" type="image/jpeg" hash="e2d4887c5a32ab1686276c7c5ae733ef" width="1.125in" />
|
||||
</div>
|
||||
<div>
|
||||
<br />
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -0,0 +1,8 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<img src=":/e2d4887c5a32ab1686276c7c5ae733ef" style="--en-viewerProps:{};" type="image/jpeg" hash="e2d4887c5a32ab1686276c7c5ae733ef" width="108" alt="attachment-image" />
|
||||
</div>
|
||||
<div>
|
||||
<br/>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,6 +1,8 @@
|
||||
<en-note>
|
||||
<div><a href=":/21ca2b948f222a38802940ec7e2e5de3" hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1">attachment-1</a></div>
|
||||
<div>
|
||||
<br>
|
||||
<a href=':/21ca2b948f222a38802940ec7e2e5de3' hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1"> attachment-1</a>
|
||||
</div>
|
||||
<div>
|
||||
<br/>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,16 +1,11 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<p>For example, consider an exported Evernote list with todo checkboxes like this:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<div><input checked="checked" type="checkbox" onclick="return false;">Foo</div>
|
||||
</li>
|
||||
<li>
|
||||
<div><input type="checkbox" onclick="return false;"><b>Bar</b></div>
|
||||
</li>
|
||||
<li>
|
||||
<div><input type="checkbox" onclick="return false;"><i>Baz</i></div>
|
||||
</li>
|
||||
<li><div><input checked="checked" type="checkbox" onclick="return false;" />Foo</div></li>
|
||||
<li><div><input type="checkbox" onclick="return false;" /><b>Bar</b></div></li>
|
||||
<li><div><input type="checkbox" onclick="return false;" /><i>Baz</i></div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,19 +1,11 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<p>In Evernote a checklist is not the same as a list with checkboxes.</p>
|
||||
<ul style="--en-todo:true;">
|
||||
<li style="--en-checked:false;">
|
||||
<input type="checkbox" onclick="return false;">
|
||||
<div>One</div>
|
||||
</li>
|
||||
<li style="--en-checked:true;">
|
||||
<input checked="checked" type="checkbox" onclick="return false;">
|
||||
<div>Two</div>
|
||||
</li>
|
||||
<li style="--en-checked:false;">
|
||||
<input type="checkbox" onclick="return false;">
|
||||
<div>Three</div>
|
||||
</li>
|
||||
|
||||
<ul STYLE="--en-todo:true;">
|
||||
<li STYLE="--en-checked:false;"> <input type="checkbox" onclick="return false;" /><div>One</div></li>
|
||||
<li STYLE="--en-checked:true;"> <input checked="checked" type="checkbox" onclick="return false;" /><div>Two</div>
|
||||
</li><li STYLE="--en-checked:false;"> <input type="checkbox" onclick="return false;" /><div>Three</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -1,12 +1 @@
|
||||
<en-note>
|
||||
<div>
|
||||
<audio controls="" preload="none" style="width:480px;">
|
||||
<source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4">
|
||||
<p>Your browser does not support HTML5 audio.</p>
|
||||
</audio>
|
||||
<p><a href=":/9168ee833d03c5ea7c730ac6673978c1">audio test</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
</div>
|
||||
</en-note>
|
||||
<en-note><div><audio controls preload="none" style="width:480px;"><source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4" /><p>Your browser does not support HTML5 audio.</p></audio><p><a href=":/9168ee833d03c5ea7c730ac6673978c1">audio test</a></p></div><div><br/></div></en-note>
|
||||
@@ -1,12 +1 @@
|
||||
<en-note>
|
||||
<div><input type="checkbox" onclick="return false;">This is a test</div>
|
||||
<div><input type="checkbox" onclick="return false;">A test for <span style="font-weight: bold;">bold</span></div>
|
||||
<div>
|
||||
<input type="checkbox" onclick="return false;">A test for <i>italic</i>
|
||||
<br>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
</div>
|
||||
<div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt=""></i></div>
|
||||
</en-note>
|
||||
<en-note><div><input type="checkbox" onclick="return false;" />This is a test</div><div><input type="checkbox" onclick="return false;" />A test for <span STYLE="font-weight: bold;">bold</span></div><div><input type="checkbox" onclick="return false;" />A test for <i>italic</i><br/></div><div><br/></div><div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt="" /></i></div></en-note>
|
||||
@@ -1,3 +1,3 @@
|
||||
<en-note>
|
||||
<h1 style="box-sizing:inherit;font-family:"Guardian TextSans Web", "Helvetica Neue", Helvetica, Arial, sans-serif;margin-top:0.2em;margin-bottom:0.35em;font-size:2.125em;font-weight:600;line-height:1.3;">Association Between mRNA Vaccination and COVID-19 Hospitalization and Disease Severity</h1>
|
||||
<h1 STYLE="box-sizing:inherit;font-family:"Guardian TextSans Web", "Helvetica Neue", Helvetica, Arial, sans-serif;margin-top:0.2em;margin-bottom:0.35em;font-size:2.125em;font-weight:600;line-height:1.3;">Association Between mRNA Vaccination and COVID-19 Hospitalization and Disease Severity</h1>
|
||||
</en-note>
|
||||
@@ -1,3 +1,5 @@
|
||||
<en-note>
|
||||
<div><img style="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"></div>
|
||||
<div>
|
||||
<img STYLE="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" SRC="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"/>
|
||||
</div>
|
||||
</en-note>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
|
||||
<en-export export-date="20230724T173816Z" application="Evernote" version="10.58.8">
|
||||
<note>
|
||||
<title>test.json</title>
|
||||
<content><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
|
||||
<en-note><en-media hash="ac91cc691d21261b222681dd38c1e4ad" type="application/json"/></en-note>
|
||||
]]>
|
||||
</content>
|
||||
<created>20191002T075850Z</created>
|
||||
<updated>20191002T075850Z</updated>
|
||||
<note-attributes><latitude>48.79547119140625</latitude><longitude>9.809423921920198</longitude><altitude>398.0</altitude><author>Laurent</author><source>desktop.mac</source></note-attributes>
|
||||
<resource><data>eyAidGVzdCI6IDEyMyB9</data><mime>application/json</mime><width>0</width><height>0</height><resource-attributes><file-name>test.json</file-name><attachment>false</attachment></resource-attributes></resource></note></en-export>
|
||||
@@ -2,7 +2,7 @@ import PluginRunner from '../../../app/services/plugins/PluginRunner';
|
||||
import PluginService, { PluginSettings, defaultPluginSetting } from '@joplin/lib/services/plugins/PluginService';
|
||||
import { ContentScriptType } from '@joplin/lib/services/plugins/api/types';
|
||||
import MdToHtml from '@joplin/renderer/MdToHtml';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import shim, { MobilePlatform } from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import * as fs from 'fs-extra';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
@@ -310,7 +310,7 @@ describe('services_PluginService', () => {
|
||||
|
||||
let resetPlatformMock = () => {};
|
||||
if (!isDesktop) {
|
||||
resetPlatformMock = mockMobilePlatform('android').reset;
|
||||
resetPlatformMock = mockMobilePlatform(MobilePlatform.Android).reset;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
BIN
packages/app-cli/tests/support/onenote/onenote_desktop.one
Normal file
@@ -0,0 +1 @@
|
||||
content
|
||||
@@ -0,0 +1 @@
|
||||
content
|
||||
@@ -0,0 +1 @@
|
||||
content
|
||||
@@ -0,0 +1,3 @@
|
||||
1. File without extension and leading `./`: [file1](./file1). Gets imported, but filename is converted to extension, like `<internal_id>.file1`
|
||||
2. File without extension: [file2](file2). Not imported at all.
|
||||
3. File with extension: [file3](file3.text). Gets imported properly.
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Joplin Web Clipper [DEV]",
|
||||
"version": "3.4.0",
|
||||
"version": "3.5.0",
|
||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||
"homepage_url": "https://joplinapp.org",
|
||||
"content_security_policy": {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { defaultWindowId } from '@joplin/lib/reducer';
|
||||
import { msleep, Second } from '@joplin/utils/time';
|
||||
import determineBaseAppDirs from '@joplin/lib/determineBaseAppDirs';
|
||||
import getAppName from '@joplin/lib/getAppName';
|
||||
import { execCommand } from '@joplin/utils';
|
||||
|
||||
interface RendererProcessQuitReply {
|
||||
canClose: boolean;
|
||||
@@ -407,7 +408,17 @@ export default class ElectronAppWrapper {
|
||||
isGoingToExit = true;
|
||||
} else {
|
||||
event.preventDefault();
|
||||
this.hide();
|
||||
|
||||
const w = this.win_;
|
||||
if (!w) return;
|
||||
|
||||
if (w.isFullScreen()) {
|
||||
// leave fullscreen, then hide
|
||||
w.once('leave-full-screen', () => w.hide());
|
||||
w.setFullScreen(false);
|
||||
} else {
|
||||
w.hide();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const hasBackgroundWindows = this.secondaryWindows_.size > 0;
|
||||
@@ -612,7 +623,11 @@ export default class ElectronAppWrapper {
|
||||
console.warn('The window object was not available during the click event from tray icon');
|
||||
return;
|
||||
}
|
||||
this.mainWindow().show();
|
||||
if (!this.mainWindow().isVisible()) {
|
||||
this.mainWindow().show();
|
||||
} else {
|
||||
this.mainWindow().hide();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Cannot create tray', error);
|
||||
@@ -796,6 +811,33 @@ export default class ElectronAppWrapper {
|
||||
return this.customProtocolHandler_;
|
||||
}
|
||||
|
||||
private async fixLinuxAccessibility_() {
|
||||
if (this.electronApp().accessibilitySupportEnabled) return;
|
||||
|
||||
const isOrcaRunning = async () => {
|
||||
if (!shim.isLinux()) return false;
|
||||
try {
|
||||
const matchingProcesses = await execCommand(['ps', '--no-headers', '-C', 'orca'], { quiet: true });
|
||||
return matchingProcesses.trim().length > 0;
|
||||
} catch (error) {
|
||||
if (error.stderr || error.exitCode !== 1) {
|
||||
// eslint-disable-next-line no-console -- The main logger is not available at this point.
|
||||
console.error('Failed to check for and enable accessibility support:', error.stderr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Work around https://issues.chromium.org/issues/431257156 by force-enabling accessibility
|
||||
// when Orca (a screen reader) is running:
|
||||
if (await isOrcaRunning()) {
|
||||
// eslint-disable-next-line no-console -- The main logger is not available at this point.
|
||||
console.log('Linux accessibility: Enabling full accessibility support.');
|
||||
this.electronApp().setAccessibilitySupportEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// Since we are doing other async things before creating the window, we might miss
|
||||
// the "ready" event. So we use the function below to make sure that the app is ready.
|
||||
@@ -804,6 +846,8 @@ export default class ElectronAppWrapper {
|
||||
const alreadyRunning = await this.ensureSingleInstance();
|
||||
if (alreadyRunning) return;
|
||||
|
||||
await this.fixLinuxAccessibility_();
|
||||
|
||||
this.customProtocolHandler_ = handleCustomProtocols();
|
||||
this.createWindow();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
const { friendlySafeFilename } = require('@joplin/lib/path-utils');
|
||||
import time from '@joplin/lib/time';
|
||||
import { BrowserWindow } from 'electron';
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron';
|
||||
const md5 = require('md5');
|
||||
const url = require('url');
|
||||
|
||||
@@ -62,8 +62,10 @@ export default class InteropServiceHelper {
|
||||
|
||||
htmlFile = await this.exportNoteToHtmlFile(noteId, exportOptions);
|
||||
|
||||
const windowOptions = {
|
||||
show: false,
|
||||
const windowOptions: BrowserWindowConstructorOptions = {
|
||||
// Work around a printing issue: As of Electron 39, if the window is initially hidden, printing crashes the app.
|
||||
// This only seems to be necessary on Linux.
|
||||
show: shim.isLinux(),
|
||||
};
|
||||
|
||||
win = bridge().newBrowserWindow(windowOptions);
|
||||
@@ -86,8 +88,14 @@ export default class InteropServiceHelper {
|
||||
// pdfs.
|
||||
// https://github.com/laurent22/joplin/issues/6254.
|
||||
await win.webContents.executeJavaScript('document.querySelectorAll(\'details\').forEach(el=>el.setAttribute(\'open\',\'\'))');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const data = await win.webContents.printToPDF(options as any);
|
||||
const data = await win.webContents.printToPDF({
|
||||
...options,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partially refactored old code before rule was applied
|
||||
pageSize: options.pageSize as any,
|
||||
// Allows users to override the CSS page size.
|
||||
// See https://github.com/laurent22/joplin/issues/13096
|
||||
preferCSSPageSize: true,
|
||||
});
|
||||
resolve(data);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
@@ -114,6 +122,9 @@ export default class InteropServiceHelper {
|
||||
//
|
||||
// 2025-05-03: Windows and MacOS also need the window.print() workaround.
|
||||
// See https://github.com/electron/electron/pull/46937.
|
||||
//
|
||||
// 2025-10-30: window.print() now causes a crash on Linux -- switch back to the
|
||||
// other method.
|
||||
|
||||
const applyWorkaround = true;
|
||||
if (applyWorkaround) {
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface AppStateDialog {
|
||||
props: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface EditorScrollPercents {
|
||||
export interface NoteIdToScrollPercent {
|
||||
[noteId: string]: number;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ export interface AppState extends State, AppWindowState {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
navHistory: any[];
|
||||
watchedNoteFiles: string[];
|
||||
lastEditorScrollPercents: EditorScrollPercents;
|
||||
focusedField: string;
|
||||
layoutMoveMode: boolean;
|
||||
startupPluginsLoaded: boolean;
|
||||
@@ -90,7 +89,6 @@ export function createAppDefaultState(resourceEditWatcherDefaultState: any): App
|
||||
},
|
||||
navHistory: [],
|
||||
watchedNoteFiles: [],
|
||||
lastEditorScrollPercents: {},
|
||||
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
|
||||
focusedField: null,
|
||||
layoutMoveMode: false,
|
||||
@@ -289,16 +287,6 @@ export default function(state: AppState, action: any) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EDITOR_SCROLL_PERCENT_SET':
|
||||
|
||||
{
|
||||
newState = { ...state };
|
||||
const newPercents = { ...newState.lastEditorScrollPercents };
|
||||
newPercents[action.noteId] = action.percent;
|
||||
newState.lastEditorScrollPercents = newPercents;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOTE_DEVTOOLS_TOGGLE':
|
||||
newState = { ...state };
|
||||
newState.devToolsVisible = !newState.devToolsVisible;
|
||||
|
||||
@@ -63,6 +63,8 @@ import { refreshFolders } from '@joplin/lib/folders-screen-utils';
|
||||
import initializeCommandService from './utils/initializeCommandService';
|
||||
import OcrDriverBase from '@joplin/lib/services/ocr/OcrDriverBase';
|
||||
import PerformanceLogger from '@joplin/lib/PerformanceLogger';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
|
||||
const perfLogger = PerformanceLogger.create();
|
||||
|
||||
@@ -278,6 +280,16 @@ class Application extends BaseApplication {
|
||||
Setting.setValue('plugins.states', pluginSettings);
|
||||
}
|
||||
|
||||
// As of Joplin 3.5.7, the ABC rendering is part of the app so we automatically disable the plugin
|
||||
pluginSettings = {
|
||||
...pluginSettings,
|
||||
['org.joplinapp.plugins.AbcSheetMusic']: {
|
||||
enabled: false,
|
||||
deleted: false,
|
||||
hasBeenUpdated: false,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) {
|
||||
await service.loadAndRunPlugins(Setting.value('pluginDir'), pluginSettings);
|
||||
@@ -683,6 +695,11 @@ class Application extends BaseApplication {
|
||||
debug: new DebugService(reg.db()),
|
||||
resourceService: ResourceService.instance(),
|
||||
searchEngine: SearchEngine.instance(),
|
||||
shim,
|
||||
Note,
|
||||
Folder,
|
||||
Resource,
|
||||
Setting,
|
||||
ocrService: () => this.ocrService_,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { MarkupLanguage } from '@joplin/renderer';
|
||||
import { runtime as convertHtmlToMarkdown } from '@joplin/lib/commands/convertHtmlToMarkdown';
|
||||
import bridge from '../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'convertNoteToMarkdown',
|
||||
label: () => _('Convert note to Markdown'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, noteId: string = null) => {
|
||||
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||
|
||||
const note = await Note.load(noteId);
|
||||
|
||||
if (!note) return;
|
||||
|
||||
try {
|
||||
const markdownBody = await convertHtmlToMarkdown().execute(context, note.body);
|
||||
|
||||
const newNote = await Note.duplicate(note.id);
|
||||
|
||||
newNote.body = markdownBody;
|
||||
newNote.markup_language = MarkupLanguage.Markdown;
|
||||
|
||||
await Note.save(newNote);
|
||||
|
||||
await Note.delete(note.id, { toTrash: true });
|
||||
|
||||
context.dispatch({
|
||||
type: 'NOTE_HTML_TO_MARKDOWN_DONE',
|
||||
value: note.id,
|
||||
});
|
||||
|
||||
context.dispatch({
|
||||
type: 'NOTE_SELECT',
|
||||
id: newNote.id,
|
||||
});
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(_('Could not convert note to Markdown: %s', error.message));
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
enabledCondition: 'oneNoteSelected && noteIsHtml && !noteIsReadOnly',
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
// AUTO-GENERATED using `gulp buildScriptIndexes`
|
||||
import * as convertNoteToMarkdown from './convertNoteToMarkdown';
|
||||
import * as copyDevCommand from './copyDevCommand';
|
||||
import * as copyToClipboard from './copyToClipboard';
|
||||
import * as editProfileConfig from './editProfileConfig';
|
||||
@@ -25,7 +24,6 @@ import * as toggleSafeMode from './toggleSafeMode';
|
||||
import * as toggleTabMovesFocus from './toggleTabMovesFocus';
|
||||
|
||||
const index: any[] = [
|
||||
convertNoteToMarkdown,
|
||||
copyDevCommand,
|
||||
copyToClipboard,
|
||||
editProfileConfig,
|
||||
|
||||
@@ -225,7 +225,7 @@ const Button = React.forwardRef(({
|
||||
animation={iconAnimation}
|
||||
mr={iconOnly ? '0' : '6px'}
|
||||
color={color}
|
||||
className={iconName}
|
||||
className={`${iconName} icon`}
|
||||
role='img'
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -261,7 +261,10 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
if (settings['sync.target'] === SyncTargetRegistry.nameToId('joplinServerSaml')) {
|
||||
const server = settings['sync.11.path'] as string;
|
||||
|
||||
const goToSamlLogin = () => {
|
||||
const goToSamlLogin = async () => {
|
||||
// Save settings to allow SAML auth with the correct URL.
|
||||
await shared.saveSettings(this);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'JoplinServerSamlLogin',
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
|
||||
import bridge from '../../../../services/bridge';
|
||||
import { ItemEvent, PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import getPluginHelpUrl from '@joplin/lib/services/plugins/utils/getPluginHelpUrl';
|
||||
|
||||
export enum InstallState {
|
||||
NotInstalled = 1,
|
||||
@@ -150,8 +151,7 @@ export default function(props: Props) {
|
||||
|
||||
const onNameClick = useCallback(() => {
|
||||
const manifest = item.manifest;
|
||||
if (!manifest.homepage_url) return;
|
||||
void bridge().openExternal(manifest.homepage_url);
|
||||
void bridge().openExternal(getPluginHelpUrl(manifest.id));
|
||||
}, [item]);
|
||||
|
||||
const onRecommendedClick = useCallback(() => {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { Dispatch } from 'redux';
|
||||
import { PopupNotificationContext } from '../PopupNotification/PopupNotificationProvider';
|
||||
import { NotificationType } from '../PopupNotification/types';
|
||||
|
||||
interface Props {
|
||||
noteId: string;
|
||||
dispatch: Dispatch;
|
||||
}
|
||||
|
||||
export default (props: Props) => {
|
||||
const popupManager = useContext(PopupNotificationContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.noteId || props.noteId === '') return;
|
||||
|
||||
props.dispatch({ type: 'NOTE_HTML_TO_MARKDOWN_DONE', value: '' });
|
||||
|
||||
const notification = popupManager.createPopup(() => (
|
||||
<div>{_('The note has been converted to Markdown and the original note has been moved to the trash')}</div>
|
||||
), { type: NotificationType.Success });
|
||||
notification.scheduleDismiss();
|
||||
}, [props.dispatch, popupManager, props.noteId]);
|
||||
|
||||
return <div style={{ display: 'none' }}/>;
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import useKeyboardHandler from './DialogButtonRow/useKeyboardHandler';
|
||||
export interface ButtonSpec {
|
||||
name: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ClickEvent {
|
||||
@@ -51,21 +52,29 @@ export default function DialogButtonRow(props: Props) {
|
||||
if (props.onClick) props.onClick(event);
|
||||
}, [props.onClick]);
|
||||
|
||||
const onKeyDown = useKeyboardHandler({ onOkButtonClick, onCancelButtonClick });
|
||||
const okButtonShow = props.okButtonShow ?? true;
|
||||
const cancelButtonShow = props.cancelButtonShow ?? true;
|
||||
const canClickOk = okButtonShow && !props.okButtonDisabled;
|
||||
const canClickCancel = cancelButtonShow && !props.cancelButtonDisabled;
|
||||
|
||||
const onKeyDown = useKeyboardHandler({
|
||||
onOkButtonClick: canClickOk ? onOkButtonClick : null,
|
||||
onCancelButtonClick: canClickCancel ? onCancelButtonClick : null,
|
||||
});
|
||||
|
||||
const buttonComps = [];
|
||||
|
||||
if (props.customButtons) {
|
||||
for (const b of props.customButtons) {
|
||||
buttonComps.push(
|
||||
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} onKeyDown={onKeyDown}>
|
||||
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} disabled={b.disabled} onKeyDown={onKeyDown}>
|
||||
{b.label}
|
||||
</button>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.okButtonShow !== false) {
|
||||
if (okButtonShow) {
|
||||
buttonComps.push(
|
||||
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={onOkButtonClick} ref={props.okButtonRef} onKeyDown={onKeyDown}>
|
||||
{props.okButtonLabel ? props.okButtonLabel : _('OK')}
|
||||
@@ -73,7 +82,7 @@ export default function DialogButtonRow(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (props.cancelButtonShow !== false) {
|
||||
if (cancelButtonShow) {
|
||||
buttonComps.push(
|
||||
<button disabled={props.cancelButtonDisabled} key="cancel" style={{ ...buttonStyle }} onClick={onCancelButtonClick}>
|
||||
{props.cancelButtonLabel ? props.cancelButtonLabel : _('Cancel')}
|
||||
|
||||
@@ -2,11 +2,10 @@ import * as React from 'react';
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { isInsideContainer } from '@joplin/lib/dom';
|
||||
|
||||
type OnButtonClick = ()=> void;
|
||||
interface Props {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
onOkButtonClick: Function;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
onCancelButtonClick: Function;
|
||||
onOkButtonClick: null|OnButtonClick;
|
||||
onCancelButtonClick: null|OnButtonClick;
|
||||
}
|
||||
|
||||
const globalKeydownHandlers: string[] = [];
|
||||
@@ -48,15 +47,17 @@ export default (props: Props) => {
|
||||
|
||||
if (!isTopDialog() || isInSubModal(event.target)) return;
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
if (event.keyCode === 13 && props.onOkButtonClick) {
|
||||
if ('nodeName' in event.target && event.target.nodeName === 'INPUT') {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
if (target.type !== 'button' && target.type !== 'checkbox') {
|
||||
event.preventDefault();
|
||||
props.onOkButtonClick();
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode === 27) {
|
||||
} else if (event.keyCode === 27 && props.onCancelButtonClick) {
|
||||
event.preventDefault();
|
||||
props.onCancelButtonClick();
|
||||
}
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
|
||||
@@ -15,7 +15,7 @@ import { connect } from 'react-redux';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk';
|
||||
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk/ppk';
|
||||
import ToggleAdvancedSettingsButton from '../ConfigScreen/controls/ToggleAdvancedSettingsButton';
|
||||
import MacOSMissingPasswordHelpLink from '../ConfigScreen/controls/MissingPasswordHelpLink';
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ const JoplinCloudScreenComponent = (props: Props) => {
|
||||
<span className={state.className}>{state.errorMessage}</span>
|
||||
) : null}
|
||||
</p>
|
||||
{state.active === 'LINK_USED' ? <div id="loading-animation" /> : null}
|
||||
{state.active === 'LINK_USED' ? <div className="loading-animation" /> : null}
|
||||
<JoplinCloudSignUpCallToAction />
|
||||
</div>
|
||||
<ButtonBar onCancelClick={() => props.dispatch({ type: 'NAV_BACK' })} />
|
||||
|
||||
@@ -38,14 +38,12 @@ import restart from '../services/restart';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoteListColumns } from '@joplin/lib/services/plugins/api/noteListType';
|
||||
import validateColumns from './NoteListHeader/utils/validateColumns';
|
||||
import ConversionNotification from './ConversionNotification/ConversionNotification';
|
||||
import TrashNotification from './TrashNotification/TrashNotification';
|
||||
import UpdateNotification from './UpdateNotification/UpdateNotification';
|
||||
import NoteEditor from './NoteEditor/NoteEditor';
|
||||
import PluginNotification from './PluginNotification/PluginNotification';
|
||||
import { Toast } from '@joplin/lib/services/plugins/api/types';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
@@ -86,7 +84,6 @@ interface Props {
|
||||
showInvalidJoplinCloudCredential: boolean;
|
||||
toast: Toast;
|
||||
shouldSwitchToAppleSiliconVersion: boolean;
|
||||
noteHtmlToMarkdownDone: string;
|
||||
}
|
||||
|
||||
interface ShareFolderDialogOptions {
|
||||
@@ -800,10 +797,6 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<ConversionNotification
|
||||
noteId={this.props.noteHtmlToMarkdownDone}
|
||||
dispatch={this.props.dispatch as Dispatch}
|
||||
/>
|
||||
<TrashNotification
|
||||
lastDeletion={this.props.lastDeletion}
|
||||
lastDeletionNotificationTime={this.props.lastDeletionNotificationTime}
|
||||
@@ -860,7 +853,6 @@ const mapStateToProps = (state: AppState) => {
|
||||
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && state.mustAuthenticate,
|
||||
toast: state.toast,
|
||||
shouldSwitchToAppleSiliconVersion: shim.isAppleSilicon() && process.arch !== 'arm64',
|
||||
noteHtmlToMarkdownDone: state.noteHtmlToMarkdownDone,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import versionInfo, { PackageInfo } from '@joplin/lib/versionInfo';
|
||||
import makeDiscourseDebugUrl from '@joplin/lib/makeDiscourseDebugUrl';
|
||||
import { ImportModule } from '@joplin/lib/services/interop/Module';
|
||||
import InteropServiceHelper from '../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
@@ -29,6 +28,8 @@ import { EventName } from '@joplin/lib/eventManager';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import NavService from '@joplin/lib/services/NavService';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import { ImportCommandOptions } from './WindowCommandsAndDialogs/commands/importFrom';
|
||||
import { FileSystemItem } from '@joplin/lib/services/interop/types';
|
||||
|
||||
const logger = Logger.create('MenuBar');
|
||||
|
||||
@@ -304,83 +305,16 @@ function useMenu(props: Props) {
|
||||
void CommandService.instance().execute(commandName);
|
||||
}, []);
|
||||
|
||||
const onImportModuleClick = useCallback(async (module: ImportModule, moduleSource: string) => {
|
||||
let path = null;
|
||||
|
||||
if (moduleSource === 'file') {
|
||||
path = await bridge().showOpenDialog({
|
||||
filters: [{ name: module.description, extensions: module.fileExtensions }],
|
||||
});
|
||||
} else {
|
||||
path = await bridge().showOpenDialog({
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
});
|
||||
}
|
||||
|
||||
if (!path || (Array.isArray(path) && !path.length)) return;
|
||||
|
||||
if (Array.isArray(path)) path = path[0];
|
||||
|
||||
const modalMessage = _('Importing from "%s" as "%s" format. Please wait...', path, module.format);
|
||||
|
||||
void CommandService.instance().execute('showModalMessage', modalMessage);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const errors: any[] = [];
|
||||
|
||||
const importOptions = {
|
||||
path,
|
||||
format: module.format,
|
||||
outputFormat: module.outputFormat,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
onProgress: (status: any) => {
|
||||
const statusStrings: string[] = Object.keys(status).map((key: string) => {
|
||||
return `${key}: ${status[key]}`;
|
||||
});
|
||||
|
||||
void CommandService.instance().execute('showModalMessage', `${modalMessage}\n\n${statusStrings.join('\n')}`);
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
onError: (error: any) => {
|
||||
errors.push(error);
|
||||
console.warn(error);
|
||||
},
|
||||
const onImportModuleClick = useCallback(async (module: ImportModule, moduleSource: FileSystemItem) => {
|
||||
const options: ImportCommandOptions = {
|
||||
destinationFolderId: !module.isNoteArchive && moduleSource === 'file' ? props.selectedFolderId : null,
|
||||
sourcePath: undefined, // Show a file picker
|
||||
sourceType: moduleSource,
|
||||
importFormat: module.format,
|
||||
outputFormat: module.outputFormat,
|
||||
};
|
||||
|
||||
const service = InteropService.instance();
|
||||
try {
|
||||
const result = await service.import(importOptions);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Import result: ', result);
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
}
|
||||
|
||||
void CommandService.instance().execute('hideModalMessage');
|
||||
|
||||
if (errors.length) {
|
||||
const response = bridge().showErrorMessageBox('There was some errors importing the notes - check the console for more details.\n\nPlease consider sending a bug report to the forum!', {
|
||||
buttons: [_('Close'), _('Send bug report')],
|
||||
});
|
||||
|
||||
props.dispatch({ type: 'NOTE_DEVTOOLS_SET', value: true });
|
||||
|
||||
if (response === 1) {
|
||||
const url = makeDiscourseDebugUrl(
|
||||
`Error importing notes from format: ${module.format}`,
|
||||
`- Input format: ${module.format}\n- Output format: ${module.outputFormat}`,
|
||||
errors,
|
||||
packageInfo,
|
||||
PluginService.instance(),
|
||||
props.pluginSettings,
|
||||
);
|
||||
|
||||
void bridge().openExternal(url);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [props.selectedFolderId, props.pluginSettings]);
|
||||
await CommandService.instance().execute('importFrom', options);
|
||||
}, [props.selectedFolderId]);
|
||||
|
||||
const onMenuItemClickRef = useRef(null);
|
||||
onMenuItemClickRef.current = onMenuItemClick;
|
||||
|
||||
@@ -22,12 +22,6 @@ interface MultiNoteActionsProps {
|
||||
function styles_(props: MultiNoteActionsProps) {
|
||||
return buildStyle('MultiNoteActions', props.themeId, (theme: ThemeStyle) => {
|
||||
return {
|
||||
root: {
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.marginTop,
|
||||
width: '100%',
|
||||
},
|
||||
itemList: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -90,7 +84,7 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.root}>
|
||||
<div style={styles.root} className='multi-note-actions'>
|
||||
<div style={styles.itemList}>{itemComps}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -172,7 +172,12 @@ export default function NoteContentPropertiesDialog(props: NoteContentProperties
|
||||
<div style={{ ...labelCompStyle, marginTop: 10 }}>
|
||||
{readTimeLabel}
|
||||
</div>
|
||||
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
<DialogButtonRow
|
||||
themeId={props.themeId}
|
||||
onClick={buttonRow_click}
|
||||
okButtonShow={false}
|
||||
cancelButtonLabel={_('Close')}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,36 @@ const logger = Logger.create('useEditorSearch');
|
||||
// Registers a helper CodeMirror extension to be used with
|
||||
// useEditorSearchHandler.
|
||||
|
||||
export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulation) {
|
||||
interface SetMarkersOptions {
|
||||
selectedIndex: number;
|
||||
searchTimestamp: number;
|
||||
showEditorMarkers?: boolean;
|
||||
withSelection?: boolean;
|
||||
}
|
||||
type Keyword = { value: string };
|
||||
|
||||
export type OnSetMarkers = (cm: CodeMirror5Emulation, keywords: Keyword[], options: SetMarkersOptions)=> number;
|
||||
|
||||
|
||||
// Modified from codemirror/addons/search/search.js
|
||||
const searchOverlay = (query: RegExp) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
return { token: function(stream: any) {
|
||||
query.lastIndex = stream.pos;
|
||||
const match = query.exec(stream.string);
|
||||
if (match && match.index === stream.pos) {
|
||||
stream.pos += match[0].length || 1;
|
||||
return 'search-marker';
|
||||
} else if (match) {
|
||||
stream.pos = match.index;
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
}
|
||||
return null;
|
||||
} };
|
||||
};
|
||||
|
||||
export default function useEditorSearchExtension() {
|
||||
|
||||
const [markers, setMarkers] = useState([]);
|
||||
const [overlay, setOverlay] = useState(null);
|
||||
@@ -48,23 +77,6 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
setOverlayTimeout(null);
|
||||
}, [scrollbarMarks, overlay, overlayTimeout]);
|
||||
|
||||
// Modified from codemirror/addons/search/search.js
|
||||
const searchOverlay = useCallback((query: RegExp) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
return { token: function(stream: any) {
|
||||
query.lastIndex = stream.pos;
|
||||
const match = query.exec(stream.string);
|
||||
if (match && match.index === stream.pos) {
|
||||
stream.pos += match[0].length || 1;
|
||||
return 'search-marker';
|
||||
} else if (match) {
|
||||
stream.pos = match.index;
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
}
|
||||
return null;
|
||||
} };
|
||||
}, []);
|
||||
|
||||
// Highlights the currently active found work
|
||||
// It's possible to get tricky with this functions and just use findNext/findPrev
|
||||
@@ -115,16 +127,17 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
};
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
CodeMirror?.defineExtension('setMarkers', function(keywords: any, options: any) {
|
||||
const onSetMarkers: OnSetMarkers = (cm, keywords, options) => {
|
||||
// Pass arguments in via options to allow the extension to work if multiple editors are open simultaneously
|
||||
// See https://github.com/laurent22/joplin/issues/13399.
|
||||
if (!options) {
|
||||
options = { selectedIndex: 0, searchTimestamp: 0 };
|
||||
}
|
||||
|
||||
if (options.showEditorMarkers === false) {
|
||||
clearMarkers();
|
||||
clearOverlay(this);
|
||||
return;
|
||||
clearOverlay(cm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
clearMarkers();
|
||||
@@ -145,7 +158,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
|
||||
|
||||
try {
|
||||
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
|
||||
const match = highlightSearch(cm, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
|
||||
if (match) marks.push(match);
|
||||
} catch (error) {
|
||||
if (error.name !== 'SyntaxError') {
|
||||
@@ -165,7 +178,7 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
// SEARCHOVERLAY
|
||||
// We only want to highlight all matches when there is only 1 search term
|
||||
if (keywords.length !== 1 || keywords[0].value === '') {
|
||||
clearOverlay(this);
|
||||
clearOverlay(cm);
|
||||
const prev = keywords.length > 1 ? keywords[0].value : '';
|
||||
setPreviousKeywordValue(prev);
|
||||
return 0;
|
||||
@@ -175,22 +188,22 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
|
||||
// Determine the number of matches in the source, this is passed on
|
||||
// to the NoteEditor component
|
||||
const regexMatches = this.getValue().match(searchTerm);
|
||||
const regexMatches = cm.getValue().match(searchTerm);
|
||||
const nMatches = regexMatches ? regexMatches.length : 0;
|
||||
|
||||
// Don't bother clearing and re-calculating the overlay if the search term
|
||||
// hasn't changed
|
||||
if (keywords[0].value === previousKeywordValue) return nMatches;
|
||||
|
||||
clearOverlay(this);
|
||||
clearOverlay(cm);
|
||||
setPreviousKeywordValue(keywords[0].value);
|
||||
|
||||
// These operations are pretty slow, so we won't add use them until the user
|
||||
// has finished typing, 500ms is probably enough time
|
||||
const timeout = shim.setTimeout(() => {
|
||||
const scrollMarks = this.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
|
||||
const scrollMarks = cm.showMatchesOnScrollbar?.(searchTerm, true, 'cm-search-marker-scrollbar');
|
||||
const overlay = searchOverlay(searchTerm);
|
||||
this.addOverlay(overlay);
|
||||
cm.addOverlay(overlay);
|
||||
setOverlay(overlay);
|
||||
setScrollbarMarks(scrollMarks);
|
||||
}, 500);
|
||||
@@ -199,5 +212,9 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
||||
overlayTimeoutRef.current = timeout;
|
||||
|
||||
return nMatches;
|
||||
});
|
||||
};
|
||||
const onSetMarkersRef = useRef(onSetMarkers);
|
||||
onSetMarkersRef.current = onSetMarkers;
|
||||
|
||||
return { onSetMarkersRef };
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { RefObject, useEffect, useMemo, useRef } from 'react';
|
||||
import usePrevious from '../../../../hooks/usePrevious';
|
||||
import { RenderedBody } from './types';
|
||||
import { SearchMarkers } from '../../../utils/useSearchMarkers';
|
||||
import CodeMirror5Emulation from '@joplin/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation';
|
||||
import useEditorSearchExtension from './useEditorSearchExtension';
|
||||
const debounce = require('debounce');
|
||||
|
||||
interface Props {
|
||||
@@ -10,8 +12,7 @@ interface Props {
|
||||
searchMarkers: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
webviewRef: RefObject<any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
editorRef: RefObject<any>;
|
||||
editorRef: RefObject<CodeMirror5Emulation>;
|
||||
|
||||
noteContent: string;
|
||||
renderedBody: RenderedBody;
|
||||
@@ -23,6 +24,8 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
webviewRef, editorRef, renderedBody, noteContent, searchMarkers, showEditorMarkers,
|
||||
} = props;
|
||||
|
||||
const { onSetMarkersRef } = useEditorSearchExtension();
|
||||
|
||||
const previousContent = usePrevious(noteContent);
|
||||
const previousRenderedBody = usePrevious(renderedBody);
|
||||
const previousSearchMarkers = usePrevious(searchMarkers);
|
||||
@@ -31,15 +34,15 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
|
||||
// Fixes https://github.com/laurent22/joplin/issues/7565
|
||||
const debouncedMarkers = useMemo(() => debounce((searchMarkers: SearchMarkers) => {
|
||||
if (!editorRef.current) return;
|
||||
if (!onSetMarkersRef.current) return;
|
||||
|
||||
if (showEditorMarkersRef.current) {
|
||||
const matches = editorRef.current.setMarkers(searchMarkers.keywords, searchMarkers.options);
|
||||
const matches = onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, searchMarkers.options);
|
||||
props.setLocalSearchResultCount(matches);
|
||||
} else {
|
||||
editorRef.current.setMarkers(searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
|
||||
onSetMarkersRef.current(editorRef.current, searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
|
||||
}
|
||||
}, 50), [editorRef, props.setLocalSearchResultCount]);
|
||||
}, 50), [editorRef, onSetMarkersRef, props.setLocalSearchResultCount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchMarkers) return () => {};
|
||||
@@ -59,7 +62,7 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
}
|
||||
return () => {};
|
||||
}, [
|
||||
editorRef,
|
||||
onSetMarkersRef,
|
||||
webviewRef,
|
||||
searchMarkers,
|
||||
previousSearchMarkers,
|
||||
@@ -71,6 +74,10 @@ const useEditorSearchHandler = (props: Props) => {
|
||||
debouncedMarkers,
|
||||
]);
|
||||
|
||||
return {
|
||||
// Returned to allow quickly setting the initial search markers just after the editor loads.
|
||||
onSetInitialMarkersRef: onSetMarkersRef,
|
||||
};
|
||||
};
|
||||
|
||||
export default useEditorSearchHandler;
|
||||
|
||||
@@ -695,7 +695,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [renderedBody, webviewReady]);
|
||||
|
||||
useEditorSearchHandler({
|
||||
const { onSetInitialMarkersRef } = useEditorSearchHandler({
|
||||
setLocalSearchResultCount: props.setLocalSearchResultCount,
|
||||
searchMarkers: props.searchMarkers,
|
||||
webviewRef,
|
||||
@@ -737,6 +737,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
<Editor
|
||||
value={props.content}
|
||||
searchMarkers={props.searchMarkers}
|
||||
onSetMarkersRef={onSetInitialMarkersRef}
|
||||
ref={editorRef}
|
||||
mode={props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML ? 'xml' : 'joplin-markdown'}
|
||||
codeMirrorTheme={styles.editor.codeMirrorTheme}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react';
|
||||
import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef, RefObject } from 'react';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
|
||||
import CodeMirror from 'codemirror';
|
||||
@@ -16,7 +16,7 @@ import useListIdent from './utils/useListIdent';
|
||||
import useScrollUtils from './utils/useScrollUtils';
|
||||
import useCursorUtils from './utils/useCursorUtils';
|
||||
import useLineSorting from './utils/useLineSorting';
|
||||
import useEditorSearch from '../utils/useEditorSearchExtension';
|
||||
import { OnSetMarkers } from '../utils/useEditorSearchExtension';
|
||||
import useJoplinMode from './utils/useJoplinMode';
|
||||
import useKeymap from './utils/useKeymap';
|
||||
import useExternalPlugins from './utils/useExternalPlugins';
|
||||
@@ -67,11 +67,17 @@ import 'codemirror/mode/diff/diff';
|
||||
import 'codemirror/mode/erlang/erlang';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
|
||||
interface ExtendedWindow {
|
||||
CodeMirror?: unknown;
|
||||
}
|
||||
declare const window: ExtendedWindow;
|
||||
|
||||
|
||||
export interface EditorProps {
|
||||
value: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
searchMarkers: any;
|
||||
onSetMarkersRef: RefObject<OnSetMarkers>;
|
||||
mode: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
style: any;
|
||||
@@ -100,13 +106,20 @@ function Editor(props: EditorProps, ref: any) {
|
||||
const editorParent = useRef(null);
|
||||
const lastEditTime = useRef(NaN);
|
||||
|
||||
useEffect(() => {
|
||||
window.CodeMirror = CodeMirror;
|
||||
|
||||
return () => {
|
||||
window.CodeMirror = undefined;
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Codemirror plugins add new commands to codemirror (or change it's behavior)
|
||||
// This command adds the smartListIndent function which will be bound to tab
|
||||
useListIdent(CodeMirror);
|
||||
useScrollUtils(CodeMirror);
|
||||
useCursorUtils(CodeMirror);
|
||||
useLineSorting(CodeMirror);
|
||||
useEditorSearch(CodeMirror);
|
||||
useJoplinMode(CodeMirror);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
|
||||
@@ -215,7 +228,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
// It's possible for searchMarkers to be available before the editor
|
||||
// In these cases we set the markers asap so the user can see them as
|
||||
// soon as the editor is ready
|
||||
if (props.searchMarkers) { cm.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options); }
|
||||
if (props.searchMarkers) { props.onSetMarkersRef.current(cm, props.searchMarkers.keywords, props.searchMarkers.options); }
|
||||
|
||||
return () => {
|
||||
// Clean up codemirror
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
import bridge from '../../../../../../services/bridge';
|
||||
import { contentScriptsToCodeMirrorPlugin } from '@joplin/lib/services/plugins/utils/loadContentScripts';
|
||||
import { extname } from 'path';
|
||||
import shim from '@joplin/lib/shim';
|
||||
@@ -7,6 +8,18 @@ import uuid from '@joplin/lib/uuid';
|
||||
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
|
||||
const addPluginDependency = (path: string) => {
|
||||
const id = `content-script-${encodeURIComponent(path)}`;
|
||||
if (document.getElementById(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = document.createElement('script');
|
||||
element.setAttribute('id', id);
|
||||
element.setAttribute('src', path);
|
||||
document.head.appendChild(element);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
export default function useExternalPlugins(CodeMirror: any, plugins: PluginStates) {
|
||||
const [options, setOptions] = useState({});
|
||||
@@ -23,7 +36,14 @@ export default function useExternalPlugins(CodeMirror: any, plugins: PluginState
|
||||
if (mod.codeMirrorResources) {
|
||||
for (const asset of mod.codeMirrorResources) {
|
||||
try {
|
||||
require(`codemirror/${asset}`);
|
||||
let assetPath = shim.fsDriver().resolveRelativePathWithinDir(`${bridge().vendorDir()}/lib/codemirror/`, asset);
|
||||
|
||||
// Compatibility with old versions of Joplin, where the file extension was automatically added by require().
|
||||
if (extname(assetPath) === '') {
|
||||
assetPath += '.js';
|
||||
}
|
||||
|
||||
addPluginDependency(assetPath);
|
||||
} catch (error) {
|
||||
error.message = `${asset} is not a valid CodeMirror asset, keymap or mode. You can find a list of valid assets here: https://codemirror.net/doc/manual.html#addons`;
|
||||
throw error;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../../../../../services/bridge';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
const { clipboard } = require('electron');
|
||||
import { clipboard } from 'electron';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import ErrorBoundary from '../../../../ErrorBoundary';
|
||||
import { EditorKeymap, EditorLanguageType, EditorSettings, SearchState, UserEventSource } from '@joplin/editor/types';
|
||||
@@ -30,6 +30,9 @@ import useEditorSearchHandler from '../utils/useEditorSearchHandler';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import useRefocusOnVisiblePaneChange from './utils/useRefocusOnVisiblePaneChange';
|
||||
import { WindowIdContext } from '../../../../NewWindowOrIFrame';
|
||||
import eventManager, { EventName, ResourceChangeEvent } from '@joplin/lib/eventManager';
|
||||
import useSyncEditorValue from './utils/useSyncEditorValue';
|
||||
import { getGlobalSettings } from '@joplin/renderer/types';
|
||||
|
||||
const logger = Logger.create('CodeMirror6');
|
||||
const logDebug = (message: string) => logger.debug(message);
|
||||
@@ -91,41 +94,13 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
|
||||
const editorCutText = useCallback(() => {
|
||||
if (editorRef.current) {
|
||||
const selections = editorRef.current.getSelections();
|
||||
if (selections.length > 0 && selections[0]) {
|
||||
clipboard.writeText(selections[0]);
|
||||
// Easy way to wipe out just the first selection
|
||||
selections[0] = '';
|
||||
editorRef.current.replaceSelections(selections);
|
||||
} else {
|
||||
const cursor = editorRef.current.getCursor();
|
||||
const line = editorRef.current.getLine(cursor.line);
|
||||
clipboard.writeText(`${line}\n`);
|
||||
const startLine = editorRef.current.getCursor('head');
|
||||
startLine.ch = 0;
|
||||
const endLine = {
|
||||
line: startLine.line + 1,
|
||||
ch: 0,
|
||||
};
|
||||
editorRef.current.replaceRange('', startLine, endLine);
|
||||
}
|
||||
editorRef.current.cutText(text => clipboard.writeText(text));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const editorCopyText = useCallback(() => {
|
||||
if (editorRef.current) {
|
||||
const selections = editorRef.current.getSelections();
|
||||
|
||||
// Handle the case when there is a selection - copy the selection to the clipboard
|
||||
// When there is no selection, the selection array contains an empty string.
|
||||
if (selections.length > 0 && selections[0]) {
|
||||
clipboard.writeText(selections[0]);
|
||||
} else {
|
||||
// This is the case when there is no selection - copy the current line to the clipboard
|
||||
const cursor = editorRef.current.getCursor();
|
||||
const line = editorRef.current.getLine(cursor.line);
|
||||
clipboard.writeText(line);
|
||||
}
|
||||
editorRef.current.copyText(text => clipboard.writeText(text));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -166,9 +141,8 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
},
|
||||
scrollTo: (options: ScrollOptions) => {
|
||||
if (options.type === ScrollOptionTypes.Hash) {
|
||||
if (!webviewRef.current) return;
|
||||
const hash: string = options.value;
|
||||
webviewRef.current.send('scrollToHash', hash);
|
||||
webviewRef.current?.send('scrollToHash', hash);
|
||||
editorRef.current.jumpToHash(hash);
|
||||
} else if (options.type === ScrollOptionTypes.Percent) {
|
||||
const percent = options.value as number;
|
||||
@@ -247,6 +221,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
useCustomPdfViewer: props.useCustomPdfViewer,
|
||||
noteId: props.noteId,
|
||||
vendorDir: bridge().vendorDir(),
|
||||
globalSettings: getGlobalSettings(Setting),
|
||||
}));
|
||||
|
||||
if (cancelled) return;
|
||||
@@ -272,6 +247,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
props.noteId, props.useCustomPdfViewer,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: ResourceChangeEvent) => {
|
||||
editorRef.current?.onResourceChanged(event.id);
|
||||
};
|
||||
|
||||
eventManager.on(EventName.ResourceChange, listener);
|
||||
return () => {
|
||||
eventManager.off(EventName.ResourceChange, listener);
|
||||
};
|
||||
}, [props.resourceInfos]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!webviewReady) return;
|
||||
|
||||
@@ -330,6 +316,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
} else if (event.kind === EditorEventType.Change) {
|
||||
codeMirror_change(event.value);
|
||||
} else if (event.kind === EditorEventType.SelectionRangeChange) {
|
||||
props.onCursorMotion({ markdown: event.from });
|
||||
setSelectionRange({ from: event.from, to: event.to });
|
||||
} else if (event.kind === EditorEventType.UpdateSearchDialog) {
|
||||
if (lastSearchState.current?.searchText !== event.searchState.searchText) {
|
||||
@@ -343,7 +330,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
} else if (event.kind === EditorEventType.FollowLink) {
|
||||
void CommandService.instance().execute('openItem', event.link);
|
||||
}
|
||||
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch]);
|
||||
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch, props.onCursorMotion]);
|
||||
|
||||
const onSelectPastBeginning = useCallback(() => {
|
||||
void CommandService.instance().execute('focusElement', 'noteTitle');
|
||||
@@ -388,15 +375,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
props.tabMovesFocus,
|
||||
]);
|
||||
|
||||
// Update the editor's value
|
||||
useEffect(() => {
|
||||
// Include the noteId in the update props to give plugins access
|
||||
// to the current note ID.
|
||||
const updateProps = { noteId: props.noteId };
|
||||
if (editorRef.current?.updateBody(props.content, updateProps)) {
|
||||
editorRef.current?.clearHistory();
|
||||
}
|
||||
}, [props.content, props.noteId]);
|
||||
const initialCursorLocationRef = useRef(0);
|
||||
initialCursorLocationRef.current = props.initialCursorLocation.markdown ?? 0;
|
||||
|
||||
useSyncEditorValue({
|
||||
content: props.content,
|
||||
visiblePanes: props.visiblePanes,
|
||||
onMessage: props.onMessage,
|
||||
editorRef,
|
||||
noteId: props.noteId,
|
||||
initialCursorLocationRef,
|
||||
});
|
||||
|
||||
const renderEditor = () => {
|
||||
return (
|
||||
@@ -404,6 +393,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
||||
<Editor
|
||||
style={styles.editor}
|
||||
initialText={props.content}
|
||||
initialSelectionRef={initialCursorLocationRef}
|
||||
initialNoteId={props.noteId}
|
||||
ref={editorRef}
|
||||
settings={editorSettings}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { ForwardedRef } from 'react';
|
||||
import { ForwardedRef, RefObject } from 'react';
|
||||
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import { EditorProps, LogMessageCallback, OnEventCallback, ContentScriptData } from '@joplin/editor/types';
|
||||
import createEditor from '@joplin/editor/CodeMirror/createEditor';
|
||||
@@ -11,7 +11,6 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import setupVim from '@joplin/editor/CodeMirror/utils/setupVim';
|
||||
import { dirname } from 'path';
|
||||
import useKeymap from './utils/useKeymap';
|
||||
import useEditorSearch from '../utils/useEditorSearchExtension';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { SearchMarkers } from '../../../utils/useSearchMarkers';
|
||||
import localisation from './utils/localisation';
|
||||
@@ -23,6 +22,7 @@ import getResourceBaseUrl from '../../../utils/getResourceBaseUrl';
|
||||
interface Props extends EditorProps {
|
||||
style: React.CSSProperties;
|
||||
pluginStates: PluginStates;
|
||||
initialSelectionRef: RefObject<number>;
|
||||
|
||||
onEditorPaste: (event: Event)=> void;
|
||||
externalSearch: SearchMarkers;
|
||||
@@ -43,8 +43,6 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
onLogMessageRef.current = props.onLogMessage;
|
||||
}, [props.onEvent, props.onLogMessage]);
|
||||
|
||||
useEditorSearch(editor);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return () => {};
|
||||
@@ -110,12 +108,12 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
|
||||
const editor = createEditor(editorContainerRef.current, {
|
||||
...editorProps,
|
||||
resolveImageSrc: async src => {
|
||||
resolveImageSrc: async (src, reloadCounter) => {
|
||||
const url = parseResourceUrl(src);
|
||||
if (!url.itemId) return null;
|
||||
const item = await Resource.load(url.itemId);
|
||||
if (!item) return null;
|
||||
return `${getResourceBaseUrl()}/${resourceFilename(item)}`;
|
||||
return `${getResourceBaseUrl()}/${resourceFilename(item)}${reloadCounter ? `?r=${reloadCounter}` : ''}`;
|
||||
},
|
||||
});
|
||||
editor.addStyles({
|
||||
@@ -127,6 +125,9 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||
direction: 'unset',
|
||||
},
|
||||
});
|
||||
const cursor = props.initialSelectionRef.current;
|
||||
editor.select(cursor, cursor);
|
||||
|
||||
setEditor(editor);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useRef, RefObject } from 'react';
|
||||
import { OnMessage } from '../../../../utils/types';
|
||||
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
|
||||
|
||||
interface Props {
|
||||
content: string;
|
||||
|
||||
visiblePanes: string[];
|
||||
onMessage: OnMessage;
|
||||
editorRef: RefObject<CodeMirrorControl>;
|
||||
noteId: string;
|
||||
initialCursorLocationRef: RefObject<number>;
|
||||
}
|
||||
|
||||
// Updates the editor's value as necessary
|
||||
const useSyncEditorValue = ({ content, visiblePanes, onMessage, editorRef, noteId, initialCursorLocationRef }: Props) => {
|
||||
const visiblePanesRef = useRef(visiblePanes);
|
||||
visiblePanesRef.current = visiblePanes;
|
||||
const onMessageRef = useRef(onMessage);
|
||||
onMessageRef.current = onMessage;
|
||||
|
||||
const lastNoteIdRef = useRef(noteId);
|
||||
|
||||
useEffect(() => {
|
||||
// Include the noteId in the update props to give plugins access
|
||||
// to the current note ID.
|
||||
const updateProps = { noteId: noteId };
|
||||
if (editorRef.current?.updateBody(content, updateProps)) {
|
||||
editorRef.current?.clearHistory();
|
||||
|
||||
// Only reset the cursor location when switching notes. If, for example,
|
||||
// the note is updated from a secondary window, the cursor location shouldn't
|
||||
// reset.
|
||||
const noteChanged = lastNoteIdRef.current !== noteId;
|
||||
if (noteChanged) {
|
||||
const cursorLocation = initialCursorLocationRef.current;
|
||||
editorRef.current?.select(cursorLocation, cursorLocation);
|
||||
}
|
||||
lastNoteIdRef.current = noteId;
|
||||
|
||||
// If the viewer isn't visible, the content should be considered rendered
|
||||
// after the editor has finished updating:
|
||||
if (!visiblePanesRef.current.includes('viewer')) {
|
||||
onMessageRef.current({ channel: 'noteRenderComplete' });
|
||||
}
|
||||
}
|
||||
}, [content, noteId, editorRef, initialCursorLocationRef]);
|
||||
};
|
||||
|
||||
export default useSyncEditorValue;
|
||||
@@ -23,7 +23,7 @@ import { themeStyle } from '@joplin/lib/theme';
|
||||
import { loadScript } from '../../../utils/loadScript';
|
||||
import bridge from '../../../../services/bridge';
|
||||
import { TinyMceEditorEvents } from './utils/types';
|
||||
import type { Editor, EditorEvent } from 'tinymce';
|
||||
import type { Bookmark, Editor, EditorEvent } from 'tinymce';
|
||||
import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands';
|
||||
import shouldPasteResources from './utils/shouldPasteResources';
|
||||
import lightTheme from '@joplin/lib/themes/light';
|
||||
@@ -47,6 +47,7 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
import useTextPatternsLookup, { TextPatternContext } from './utils/useTextPatternsLookup';
|
||||
import { toFileProtocolPath } from '@joplin/utils/path';
|
||||
import { RenderResultPluginAsset } from '@joplin/renderer/types';
|
||||
import useCursorPositioning from './utils/useCursorPositioning';
|
||||
|
||||
const logger = Logger.create('TinyMCE');
|
||||
|
||||
@@ -918,8 +919,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
|
||||
editor.on('SetContent', () => {
|
||||
preprocessContent();
|
||||
|
||||
props_onMessage.current({ channel: 'noteRenderComplete' });
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1046,6 +1045,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
return true;
|
||||
}
|
||||
|
||||
const { onRestoreCursorPosition } = useCursorPositioning({
|
||||
initialCursorLocation: props.initialCursorLocation.richText as Bookmark,
|
||||
onCursorUpdate: props.onCursorMotion,
|
||||
editor,
|
||||
});
|
||||
|
||||
const lastNoteIdRef = useRef(props.noteId);
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
@@ -1113,8 +1118,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
// times would result in an empty note.
|
||||
// https://github.com/laurent22/joplin/issues/3534
|
||||
editor.undoManager.reset();
|
||||
|
||||
// Only restore the cursor position from the global state when switching notes.
|
||||
// See https://github.com/laurent22/joplin/issues/13579
|
||||
onRestoreCursorPosition();
|
||||
} else {
|
||||
// Restore the cursor location
|
||||
// Restore the cursor location from the current note
|
||||
editor.selection.bookmarkManager.moveToBookmark(bookmark);
|
||||
}
|
||||
|
||||
@@ -1123,6 +1132,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
resourceInfos: props.resourceInfos,
|
||||
contentKey: props.contentKey,
|
||||
};
|
||||
props_onMessage.current({ channel: 'noteRenderComplete' });
|
||||
}
|
||||
|
||||
const allAssetsOptions: NoteStyleOptions = {
|
||||
@@ -1387,16 +1397,18 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
async function onCopy(event: any) {
|
||||
const copiedContent = editor.selection.getContent();
|
||||
if (!copiedContent) return;
|
||||
copyHtmlToClipboard(copiedContent);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
async function onCut(event: any) {
|
||||
event.preventDefault();
|
||||
const selectedContent = editor.selection.getContent();
|
||||
if (!selectedContent) return;
|
||||
copyHtmlToClipboard(selectedContent);
|
||||
editor.insertContent('');
|
||||
event.preventDefault();
|
||||
onChangeHandler();
|
||||
}
|
||||
|
||||
@@ -1444,7 +1456,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
// `compositionend` means that a user has finished entering a Chinese
|
||||
// (or other languages that require IME) character.
|
||||
editor.on(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Cut, onCut);
|
||||
editor.on(TinyMceEditorEvents.Cut, onCut, true);
|
||||
editor.on(TinyMceEditorEvents.JoplinChange, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Undo, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Redo, onChangeHandler);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { Bookmark, Editor } from 'tinymce';
|
||||
import { OnCursorMotion } from '../../../utils/types';
|
||||
|
||||
interface Props {
|
||||
initialCursorLocation: Bookmark;
|
||||
editor: Editor;
|
||||
onCursorUpdate: OnCursorMotion;
|
||||
}
|
||||
|
||||
const useCursorPositioning = ({ initialCursorLocation, editor, onCursorUpdate }: Props) => {
|
||||
const initialCursorLocationRef = useRef(initialCursorLocation);
|
||||
initialCursorLocationRef.current = initialCursorLocation;
|
||||
|
||||
const appliedInitialCursorLocationRef = useRef(false);
|
||||
const onRestoreCursorPosition = useCallback(() => {
|
||||
if (editor) {
|
||||
if (initialCursorLocationRef.current) {
|
||||
editor.selection.moveToBookmark(initialCursorLocationRef.current);
|
||||
}
|
||||
|
||||
appliedInitialCursorLocationRef.current = true;
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
|
||||
const onSelectionChange = () => {
|
||||
// Wait until the initial cursor position has been set. This avoids resetting
|
||||
// the initial cursor position to zero when the editor first loads.
|
||||
if (!appliedInitialCursorLocationRef.current) return;
|
||||
|
||||
// Use an offset bookmark -- the default bookmark type is not preserved after unloading
|
||||
// and reloading the editor.
|
||||
const offsetBookmarkId = 2;
|
||||
onCursorUpdate({
|
||||
richText: editor.selection.getBookmark(offsetBookmarkId, true),
|
||||
});
|
||||
};
|
||||
|
||||
editor.on('SelectionChange', onSelectionChange);
|
||||
|
||||
return () => {
|
||||
editor.off('SelectionChange', onSelectionChange);
|
||||
};
|
||||
}, [editor, onCursorUpdate, onRestoreCursorPosition]);
|
||||
|
||||
return { onRestoreCursorPosition };
|
||||
};
|
||||
|
||||
export default useCursorPositioning;
|
||||
@@ -5,6 +5,8 @@ import { MarkupToHtmlHandler } from '../../../utils/types';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import enableTextAreaTab, { TextAreaTabHandler } from './enableTextAreaTab';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
import { getGlobalSettings } from '@joplin/renderer/types';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
interface Props {
|
||||
editor: Editor;
|
||||
@@ -90,7 +92,7 @@ function openEditDialog(
|
||||
onSubmit: async (dialogApi: any) => {
|
||||
const newSource = newBlockSource(dialogApi.getData().languageInput, dialogApi.getData().codeTextArea, source);
|
||||
const md = `${newSource.openCharacters}${newSource.content}${newSource.closeCharacters}`;
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, md, { bodyOnly: true });
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, md, { bodyOnly: true, globalSettings: getGlobalSettings(Setting) });
|
||||
|
||||
// markupToHtml will return the complete editable HTML, but we only
|
||||
// want to update the inner HTML, so as not to break additional props that
|
||||
|
||||
@@ -57,6 +57,8 @@ import StatusBar from './StatusBar';
|
||||
import useVisiblePluginEditorViewIds from '@joplin/lib/hooks/plugins/useVisiblePluginEditorViewIds';
|
||||
import useConnectToEditorPlugin from './utils/useConnectToEditorPlugin';
|
||||
import getResourceBaseUrl from './utils/getResourceBaseUrl';
|
||||
import useInitialCursorLocation from './utils/useInitialCursorLocation';
|
||||
import NotePositionService, { EditorCursorLocations } from '@joplin/lib/services/NotePositionService';
|
||||
|
||||
const debounce = require('debounce');
|
||||
|
||||
@@ -329,13 +331,13 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
});
|
||||
}, [formNote, setFormNote, handleProvisionalFlag, props.dispatch]);
|
||||
|
||||
const { scrollWhenReady, clearScrollWhenReady } = useScrollWhenReadyOptions({
|
||||
const { scrollWhenReadyRef, clearScrollWhenReady } = useScrollWhenReadyOptions({
|
||||
noteId: formNote.id,
|
||||
selectedNoteHash: props.selectedNoteHash,
|
||||
lastEditorScrollPercents: props.lastEditorScrollPercents,
|
||||
editorRef,
|
||||
editorName: props.bodyEditor,
|
||||
});
|
||||
const onMessage = useMessageHandler(scrollWhenReady, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
|
||||
const onMessage = useMessageHandler(scrollWhenReadyRef, clearScrollWhenReady, windowId, editorRef, setLocalSearchResultCount, props.dispatch, formNote, htmlToMarkdown, markupToHtml);
|
||||
|
||||
useResourceUnwatcher({ noteId: formNote.id, windowId });
|
||||
|
||||
@@ -399,15 +401,14 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
}, [setShowRevisions]);
|
||||
|
||||
const onScroll = useCallback((event: { percent: number }) => {
|
||||
props.dispatch({
|
||||
type: 'EDITOR_SCROLL_PERCENT_SET',
|
||||
// In callbacks of setTimeout()/setInterval(), props/state cannot be used
|
||||
// to refer the current value, since they would be one or more generations old.
|
||||
// For the purpose, useRef value should be used.
|
||||
noteId: formNoteRef.current.id,
|
||||
percent: event.percent,
|
||||
});
|
||||
}, [props.dispatch]);
|
||||
const noteId = formNoteRef.current.id;
|
||||
NotePositionService.instance().updateScrollPosition(noteId, windowId, event.percent);
|
||||
}, [windowId]);
|
||||
|
||||
const onCursorMotion = useCallback((location: EditorCursorLocations) => {
|
||||
const noteId = formNoteRef.current.id;
|
||||
NotePositionService.instance().updateCursorPosition(noteId, windowId, location);
|
||||
}, [windowId]);
|
||||
|
||||
function renderNoNotes(rootStyle: React.CSSProperties) {
|
||||
const emptyDivStyle = {
|
||||
@@ -419,6 +420,9 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
}
|
||||
|
||||
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
|
||||
const initialCursorLocation = useInitialCursorLocation({
|
||||
noteId: props.noteId,
|
||||
});
|
||||
|
||||
const markupLanguage = formNote.markup_language;
|
||||
const editorProps: NoteBodyEditorPropsAndRef = {
|
||||
@@ -432,6 +436,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
content: formNote.body,
|
||||
contentMarkupLanguage: markupLanguage,
|
||||
contentOriginalCss: formNote.originalCss,
|
||||
initialCursorLocation,
|
||||
resourceInfos: resourceInfos,
|
||||
resourceDirectory: Setting.value('resourceDir'),
|
||||
htmlToMarkdown: htmlToMarkdown,
|
||||
@@ -442,6 +447,7 @@ function NoteEditorContent(props: NoteEditorProps) {
|
||||
dispatch: props.dispatch,
|
||||
noteToolbar: null,
|
||||
onScroll: onScroll,
|
||||
onCursorMotion,
|
||||
setLocalSearchResultCount: setLocalSearchResultCount,
|
||||
setLocalSearch: localSearch_change,
|
||||
setShowLocalSearch,
|
||||
@@ -728,7 +734,6 @@ const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
|
||||
watchedNoteFiles: state.watchedNoteFiles,
|
||||
notesParentType: windowState.notesParentType,
|
||||
selectedNoteTags: windowState.selectedNoteTags,
|
||||
lastEditorScrollPercents: state.lastEditorScrollPercents,
|
||||
selectedNoteHash: windowState.selectedNoteHash,
|
||||
searches: state.searches,
|
||||
selectedSearchId: windowState.selectedSearchId,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { LinkRenderingType } from '@joplin/renderer/MdToHtml';
|
||||
import { MarkupToHtmlOptions } from './types';
|
||||
import { getGlobalSettings, ResourceInfos } from '@joplin/renderer/types';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
export default (override: MarkupToHtmlOptions = null): MarkupToHtmlOptions => {
|
||||
interface OptionOverride {
|
||||
bodyOnly: boolean;
|
||||
resourceInfos?: ResourceInfos;
|
||||
allowedFilePrefixes?: string[];
|
||||
}
|
||||
|
||||
export default (override: OptionOverride = null): MarkupToHtmlOptions => {
|
||||
return {
|
||||
plugins: {
|
||||
checkbox: {
|
||||
@@ -12,6 +20,7 @@ export default (override: MarkupToHtmlOptions = null): MarkupToHtmlOptions => {
|
||||
},
|
||||
},
|
||||
replaceResourceInternalToExternalLinks: true,
|
||||
globalSettings: getGlobalSettings(Setting),
|
||||
...override,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ const joplinRendererUtils = require('@joplin/renderer').utils;
|
||||
const { clipboard } = require('electron');
|
||||
import * as mimeUtils from '@joplin/lib/mime-utils';
|
||||
import bridge from '../../../services/bridge';
|
||||
import { getCollator, getCollatorLocale } from '@joplin/lib/models/utils/getCollator';
|
||||
const md5 = require('md5');
|
||||
const path = require('path');
|
||||
|
||||
@@ -43,22 +44,30 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
|
||||
if (!filePaths || !filePaths.length) return null;
|
||||
}
|
||||
|
||||
const collatorLocale = getCollatorLocale();
|
||||
const collator = getCollator(collatorLocale);
|
||||
filePaths = filePaths.sort((a, b) => {
|
||||
return collator.compare(a, b);
|
||||
});
|
||||
|
||||
let pos = options.position ?? 0;
|
||||
|
||||
for (let i = 0; i < filePaths.length; i++) {
|
||||
const filePath = filePaths[i];
|
||||
const beforeLen = body.length;
|
||||
try {
|
||||
logger.info(`Attaching ${filePath}`);
|
||||
const newBody = await shim.attachFileToNoteBody(body, filePath, options.position, {
|
||||
const newBody = await shim.attachFileToNoteBody(body, filePath, pos, {
|
||||
createFileURL: options.createFileURL,
|
||||
resizeLargeImages: Setting.value('imageResizing'),
|
||||
markupLanguage: options.markupLanguage,
|
||||
resourceSuffix: i > 0 ? ' ' : '',
|
||||
resourcePrefix: i > 0 ? ' ' : '',
|
||||
});
|
||||
|
||||
if (!newBody) {
|
||||
logger.info('File attachment was cancelled');
|
||||
return null;
|
||||
}
|
||||
|
||||
pos += newBody.length - beforeLen;
|
||||
body = newBody;
|
||||
logger.info('File was attached.');
|
||||
} catch (error) {
|
||||
@@ -66,7 +75,6 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -90,6 +98,10 @@ export async function getResourcesFromPasteEvent(event: any) {
|
||||
const formatType = format.split('/')[0];
|
||||
|
||||
if (formatType === 'image') {
|
||||
// writeImageToFile can process only image/jpeg, image/jpg or image/png mime types
|
||||
if (['image/png', 'image/jpg', 'image/jpeg'].indexOf(format) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (event) event.preventDefault();
|
||||
|
||||
const image = clipboard.readImage();
|
||||
|
||||
@@ -13,6 +13,8 @@ import { MarkupToHtmlOptions } from '../../hooks/useMarkupToHtml';
|
||||
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||
import { RefObject, SetStateAction } from 'react';
|
||||
import * as React from 'react';
|
||||
import { ResourceEntity, ResourceLocalStateEntity } from '@joplin/lib/services/database/types';
|
||||
import { EditorCursorLocations } from '@joplin/lib/services/NotePositionService';
|
||||
|
||||
export interface AllAssetsOptions {
|
||||
contentMaxWidthTarget?: string;
|
||||
@@ -39,8 +41,6 @@ export interface NoteEditorProps {
|
||||
notesParentType: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
selectedNoteTags: any[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
lastEditorScrollPercents: any;
|
||||
selectedNoteHash: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
searches: any[];
|
||||
@@ -82,6 +82,14 @@ export interface NoteBodyEditorRef {
|
||||
export { MarkupToHtmlOptions };
|
||||
export type MarkupToHtmlHandler = (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
|
||||
export type HtmlToMarkdownHandler = (markupLanguage: number, html: string, originalCss: string, parseOptions?: ParseOptions)=> Promise<string>;
|
||||
export type OnCursorMotion = (event: EditorCursorLocations)=> void;
|
||||
|
||||
export interface MessageEvent {
|
||||
channel: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partially refactored old code before rule was applied
|
||||
args?: any[];
|
||||
}
|
||||
export type OnMessage = (event: MessageEvent)=> void;
|
||||
|
||||
export interface NoteBodyEditorProps {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
@@ -101,12 +109,13 @@ export interface NoteBodyEditorProps {
|
||||
contentKey: string;
|
||||
contentMarkupLanguage: number;
|
||||
contentOriginalCss: string;
|
||||
initialCursorLocation: EditorCursorLocations;
|
||||
onChange(event: OnChangeEvent): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
onWillChange(event: any): void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
onMessage(event: any): void;
|
||||
onMessage: OnMessage;
|
||||
onScroll(event: { percent: number }): void;
|
||||
onCursorMotion: OnCursorMotion;
|
||||
markupToHtml: MarkupToHtmlHandler;
|
||||
htmlToMarkdown: HtmlToMarkdownHandler;
|
||||
allAssets: (markupLanguage: MarkupLanguage, options: AllAssetsOptions)=> Promise<RenderResultPluginAsset[]>;
|
||||
@@ -214,10 +223,8 @@ export function defaultFormNote(): FormNote {
|
||||
}
|
||||
|
||||
export interface ResourceInfo {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
localState: any;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
item: any;
|
||||
localState: ResourceLocalStateEntity;
|
||||
item: ResourceEntity;
|
||||
}
|
||||
|
||||
export interface ResourceInfos {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { WindowIdContext } from '../../NewWindowOrIFrame';
|
||||
import NotePositionService from '@joplin/lib/services/NotePositionService';
|
||||
|
||||
interface Props {
|
||||
noteId: string;
|
||||
}
|
||||
|
||||
const useInitialCursorLocation = ({ noteId }: Props) => {
|
||||
const windowId = useContext(WindowIdContext);
|
||||
|
||||
return useMemo(() => {
|
||||
return NotePositionService.instance().getCursorPosition(noteId, windowId);
|
||||
}, [noteId, windowId]);
|
||||
};
|
||||
|
||||
export default useInitialCursorLocation;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions } from './types';
|
||||
import { RefObject, useCallback } from 'react';
|
||||
import { FormNote, HtmlToMarkdownHandler, MarkupToHtmlHandler, ScrollOptions, MessageEvent } from './types';
|
||||
import contextMenu from './contextMenu';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import PostMessageService from '@joplin/lib/services/PostMessageService';
|
||||
@@ -8,7 +8,7 @@ import { reg } from '@joplin/lib/registry';
|
||||
import bridge from '../../../services/bridge';
|
||||
|
||||
export default function useMessageHandler(
|
||||
scrollWhenReady: ScrollOptions|null,
|
||||
scrollWhenReadyRef: RefObject<ScrollOptions|null>,
|
||||
clearScrollWhenReady: ()=> void,
|
||||
windowId: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
@@ -21,8 +21,7 @@ export default function useMessageHandler(
|
||||
htmlToMd: HtmlToMarkdownHandler,
|
||||
mdToHtml: MarkupToHtmlHandler,
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
return useCallback(async (event: any) => {
|
||||
return useCallback(async (event: MessageEvent) => {
|
||||
const msg = event.channel ? event.channel : '';
|
||||
const args = event.args;
|
||||
const arg0 = args && args.length >= 1 ? args[0] : null;
|
||||
@@ -35,8 +34,8 @@ export default function useMessageHandler(
|
||||
s.splice(0, 1);
|
||||
reg.logger().error(s.join(':'));
|
||||
} else if (msg === 'noteRenderComplete') {
|
||||
if (scrollWhenReady) {
|
||||
const options = { ...scrollWhenReady };
|
||||
if (scrollWhenReadyRef.current) {
|
||||
const options = { ...scrollWhenReadyRef.current };
|
||||
clearScrollWhenReady();
|
||||
editorRef.current.scrollTo(options);
|
||||
}
|
||||
@@ -78,5 +77,5 @@ export default function useMessageHandler(
|
||||
// bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
|
||||
}
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [dispatch, setLocalSearchResultCount, scrollWhenReady, formNote]);
|
||||
}, [dispatch, setLocalSearchResultCount, scrollWhenReadyRef, formNote]);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,49 @@
|
||||
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { RefObject, useCallback, useContext, useRef } from 'react';
|
||||
import { NoteBodyEditorRef, ScrollOptions, ScrollOptionTypes } from './types';
|
||||
import usePrevious from '@joplin/lib/hooks/usePrevious';
|
||||
import type { EditorScrollPercents } from '../../../app.reducer';
|
||||
import NotePositionService from '@joplin/lib/services/NotePositionService';
|
||||
import useNowEffect from '@joplin/lib/hooks/useNowEffect';
|
||||
import { WindowIdContext } from '../../NewWindowOrIFrame';
|
||||
|
||||
interface Props {
|
||||
noteId: string;
|
||||
editorName: string;
|
||||
selectedNoteHash: string;
|
||||
lastEditorScrollPercents: EditorScrollPercents;
|
||||
editorRef: RefObject<NoteBodyEditorRef>;
|
||||
}
|
||||
|
||||
const useScrollWhenReadyOptions = ({ noteId, selectedNoteHash, lastEditorScrollPercents, editorRef }: Props) => {
|
||||
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions|null>(null);
|
||||
const useScrollWhenReadyOptions = ({ noteId, editorName, selectedNoteHash, editorRef }: Props) => {
|
||||
const scrollWhenReadyRef = useRef<ScrollOptions|null>(null);
|
||||
|
||||
const previousNoteId = usePrevious(noteId);
|
||||
const lastScrollPercentsRef = useRef<EditorScrollPercents>(null);
|
||||
lastScrollPercentsRef.current = lastEditorScrollPercents;
|
||||
const previousEditor = usePrevious(editorName);
|
||||
const windowId = useContext(WindowIdContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (noteId === previousNoteId) return;
|
||||
|
||||
// This needs to be a nowEffect to prevent race conditions
|
||||
useNowEffect(() => {
|
||||
const editorChanged = editorName !== previousEditor;
|
||||
const noteIdChanged = noteId !== previousNoteId;
|
||||
if (!editorChanged && !noteIdChanged) return () => {};
|
||||
|
||||
const lastScrollPercent = NotePositionService.instance().getScrollPercent(noteId, windowId) || 0;
|
||||
scrollWhenReadyRef.current = {
|
||||
type: selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent,
|
||||
value: selectedNoteHash ? selectedNoteHash : lastScrollPercent,
|
||||
};
|
||||
|
||||
if (editorRef.current) {
|
||||
editorRef.current.resetScroll();
|
||||
}
|
||||
|
||||
const lastScrollPercent = lastScrollPercentsRef.current[noteId] || 0;
|
||||
setScrollWhenReady({
|
||||
type: selectedNoteHash ? ScrollOptionTypes.Hash : ScrollOptionTypes.Percent,
|
||||
value: selectedNoteHash ? selectedNoteHash : lastScrollPercent,
|
||||
});
|
||||
}, [noteId, previousNoteId, selectedNoteHash, editorRef]);
|
||||
return () => {};
|
||||
}, [editorName, previousEditor, noteId, previousNoteId, selectedNoteHash, editorRef, windowId]);
|
||||
|
||||
const clearScrollWhenReady = useCallback(() => {
|
||||
setScrollWhenReady(null);
|
||||
scrollWhenReadyRef.current = null;
|
||||
}, []);
|
||||
|
||||
return { scrollWhenReady, clearScrollWhenReady };
|
||||
return { scrollWhenReadyRef, clearScrollWhenReady };
|
||||
};
|
||||
|
||||
export default useScrollWhenReadyOptions;
|
||||
|
||||
@@ -501,7 +501,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
<div role='table' aria-labelledby='note-properties-dialog-title'>
|
||||
{noteComps}
|
||||
</div>
|
||||
<DialogButtonRow themeId={this.props.themeId} okButtonShow={!this.isReadOnly()} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
|
||||
<DialogButtonRow
|
||||
themeId={this.props.themeId}
|
||||
okButtonShow={!this.isReadOnly()}
|
||||
okButtonRef={this.okButton}
|
||||
onClick={this.buttonRow_click}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
import useDeleteHistoryClick from '@joplin/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick';
|
||||
import { getGlobalSettings } from '@joplin/renderer/types';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
@@ -72,6 +74,7 @@ const useNoteContent = (
|
||||
const result = await markupToHtml(markupLanguage, noteBody, {
|
||||
resources: await shared.attachedResources(noteBody),
|
||||
whiteBackgroundNoteRendering: markupLanguage === MarkupLanguage.Html,
|
||||
globalSettings: getGlobalSettings(Setting),
|
||||
});
|
||||
|
||||
viewerRef.current.setHtml(result.html, {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { createContext, useMemo, useRef, useState } from 'react';
|
||||
import { createContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { NotificationType, PopupHandle, PopupControl as PopupManager } from './types';
|
||||
import { Hour, msleep } from '@joplin/utils/time';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
export const PopupNotificationContext = createContext<PopupManager|null>(null);
|
||||
export const VisibleNotificationsContext = createContext<PopupSpec[]>([]);
|
||||
@@ -112,6 +113,18 @@ const PopupNotificationProvider: React.FC<Props> = props => {
|
||||
return manager;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultShowToast = shim.showToast;
|
||||
shim.showToast = async (message: string, options) => {
|
||||
const popup = popupManager.createPopup(() => message, { type: options?.type ?? NotificationType.Info });
|
||||
popup.scheduleDismiss();
|
||||
};
|
||||
|
||||
return () => {
|
||||
shim.showToast = defaultShowToast;
|
||||
};
|
||||
}, [popupManager]);
|
||||
|
||||
return <PopupNotificationContext.Provider value={popupManager}>
|
||||
<VisibleNotificationsContext.Provider value={popupSpecs}>
|
||||
{props.children}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToastType } from '@joplin/lib/shim';
|
||||
import * as React from 'react';
|
||||
|
||||
export type PopupHandle = {
|
||||
@@ -5,14 +6,13 @@ export type PopupHandle = {
|
||||
scheduleDismiss(delay?: number): void;
|
||||
};
|
||||
|
||||
export enum NotificationType {
|
||||
Info = 'info',
|
||||
Success = 'success',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
export type NotificationContentCallback = ()=> React.ReactNode;
|
||||
|
||||
// NotificationType is an alias for ToastType
|
||||
export type NotificationType = ToastType;
|
||||
// eslint-disable-next-line no-redeclare -- export const is necessary for creating an alias, this is not a redeclaration.
|
||||
export const NotificationType = ToastType;
|
||||
|
||||
export interface PopupOptions {
|
||||
type?: NotificationType;
|
||||
}
|
||||
|
||||
@@ -251,8 +251,6 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
} else {
|
||||
onClose(true);
|
||||
}
|
||||
} else if (event.key === 'Escape') {
|
||||
onClose(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -309,7 +307,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog className='prompt-dialog' contentStyle={styles.dialog}>
|
||||
<Dialog className='prompt-dialog' contentStyle={styles.dialog} onCancel={() => onClose(false, 'cancel')}>
|
||||
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
||||
<div style={{ display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor }}>
|
||||
{inputComp}
|
||||
|
||||
@@ -71,7 +71,7 @@ async function initialize() {
|
||||
panes: Setting.value('noteVisiblePanes'),
|
||||
});
|
||||
|
||||
InteropService.instance().document = document;
|
||||
InteropService.instance().domParser = new DOMParser();
|
||||
InteropService.instance().xmlSerializer = new XMLSerializer();
|
||||
}
|
||||
|
||||
|
||||