mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 10:09:26 +02:00
Compare commits
800 Commits
2.5.4-andr
...
fix-github
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e68dd129e | ||
|
|
2350d0e41d | ||
|
|
fbd5c8e3cd | ||
|
|
ee5c4077fe | ||
|
|
47bbfc618e | ||
|
|
b377329a8b | ||
|
|
ae8bcf3865 | ||
|
|
2f67611829 | ||
|
|
058c612f4f | ||
|
|
be8df90ccb | ||
|
|
aa730b5b93 | ||
|
|
34bbcf6387 | ||
|
|
33c3c78e2f | ||
|
|
bf144dcda4 | ||
|
|
284ebdb86f | ||
|
|
9dac4cf7f4 | ||
|
|
fb973a5514 | ||
|
|
f9226510f9 | ||
|
|
5c0a964699 | ||
|
|
41ffaab3dc | ||
|
|
764c721f1d | ||
|
|
3d267f91ef | ||
|
|
f10f28a7f8 | ||
|
|
8d04f89a18 | ||
|
|
9d1374dcd4 | ||
|
|
094a65e514 | ||
|
|
372da979e1 | ||
|
|
d3371326f2 | ||
|
|
57452bd0e6 | ||
|
|
993b8460bf | ||
|
|
081201aa42 | ||
|
|
270e6778d1 | ||
|
|
2074320583 | ||
|
|
cd90030616 | ||
|
|
807bd683b7 | ||
|
|
35929026a4 | ||
|
|
41eb94ee47 | ||
|
|
ba51e110ad | ||
|
|
ecad06d249 | ||
|
|
19a151f637 | ||
|
|
7c4a6f1e17 | ||
|
|
bc88e953d1 | ||
|
|
7d89ec676a | ||
|
|
c8f8bcca93 | ||
|
|
9fa904e849 | ||
|
|
05cc947cb4 | ||
|
|
a20a683b17 | ||
|
|
5fc43c931e | ||
|
|
0879c3b4d6 | ||
|
|
2eded71de3 | ||
|
|
9e30ee50b8 | ||
|
|
08621a5873 | ||
|
|
17f5bd28ba | ||
|
|
4558c0b2eb | ||
|
|
a442677e91 | ||
|
|
95666bee36 | ||
|
|
c4232ac95a | ||
|
|
0283037289 | ||
|
|
b6fd0df715 | ||
|
|
f53fb58024 | ||
|
|
21d452308d | ||
|
|
823608cca1 | ||
|
|
f3a0b998e4 | ||
|
|
a47a38b16b | ||
|
|
a184c53dd5 | ||
|
|
a3464255ec | ||
|
|
088c6670a9 | ||
|
|
27840b9bd6 | ||
|
|
ae819504f0 | ||
|
|
af792e394c | ||
|
|
76a4d5c2a5 | ||
|
|
4e23acd608 | ||
|
|
3c65dcdd8b | ||
|
|
efea6e3972 | ||
|
|
185cca7880 | ||
|
|
fbded93405 | ||
|
|
674881b873 | ||
|
|
248fb6dd2d | ||
|
|
3211e7a149 | ||
|
|
08496c699e | ||
|
|
060fa25814 | ||
|
|
a6b1effc77 | ||
|
|
5cf9c4808e | ||
|
|
d5a44067fd | ||
|
|
0de463526f | ||
|
|
5fd79875ef | ||
|
|
bdb19ad325 | ||
|
|
7a12efc823 | ||
|
|
2f3bddf860 | ||
|
|
6127eb97af | ||
|
|
86386e14f3 | ||
|
|
9cc17c29f6 | ||
|
|
a31e863c7b | ||
|
|
15a32ac83a | ||
|
|
fa5b84ec0d | ||
|
|
7905584eac | ||
|
|
afc9b96a11 | ||
|
|
4e6466fc18 | ||
|
|
233baf78ce | ||
|
|
42d0bee37f | ||
|
|
ccadab6a10 | ||
|
|
bfe5a5cb96 | ||
|
|
88fc24c4cf | ||
|
|
8be4fea7a3 | ||
|
|
a3d39d8b6d | ||
|
|
ff36f0259a | ||
|
|
d13654088a | ||
|
|
8a6b97c757 | ||
|
|
d71e0c109b | ||
|
|
7e345303fe | ||
|
|
d881bdb77c | ||
|
|
4961102d8f | ||
|
|
4f2ab4f1da | ||
|
|
7540e34366 | ||
|
|
4d7a8b9ea0 | ||
|
|
812ecf93c8 | ||
|
|
74d99d97bc | ||
|
|
5bbc453268 | ||
|
|
d71b9edc7f | ||
|
|
134904b551 | ||
|
|
3eeb292d70 | ||
|
|
04125bbb98 | ||
|
|
cfc3a8341c | ||
|
|
cbb0a511a8 | ||
|
|
dff1c7ed32 | ||
|
|
7987ef81a6 | ||
|
|
8d8d4ee782 | ||
|
|
3a50c73cab | ||
|
|
4ee0071eca | ||
|
|
248192f404 | ||
|
|
e56b766c3a | ||
|
|
170b2b24ab | ||
|
|
50137e0994 | ||
|
|
8b5babd5f8 | ||
|
|
39e83200c8 | ||
|
|
ead0e0107e | ||
|
|
1c888bd4fd | ||
|
|
ff92530339 | ||
|
|
ee38032403 | ||
|
|
2ce245659a | ||
|
|
2c635eb4d7 | ||
|
|
d653223df0 | ||
|
|
290b0dfc78 | ||
|
|
06bb9e654b | ||
|
|
576fe78e33 | ||
|
|
0c16bfb5ff | ||
|
|
a101dbdf31 | ||
|
|
682609044f | ||
|
|
c0a7fb3a5c | ||
|
|
a153aa791c | ||
|
|
76b3dd1f96 | ||
|
|
8b9e2c8526 | ||
|
|
0dc2d14736 | ||
|
|
fc5dada701 | ||
|
|
f34060c4e0 | ||
|
|
006a239680 | ||
|
|
c6ed4bcab1 | ||
|
|
b0fe967087 | ||
|
|
3f32673b0a | ||
|
|
0525cecaee | ||
|
|
7d84a9582b | ||
|
|
289acd8ab6 | ||
|
|
bb6d9f604b | ||
|
|
c20a8232cd | ||
|
|
c886212e89 | ||
|
|
799ae111ae | ||
|
|
2a990979a0 | ||
|
|
2d1071117b | ||
|
|
aefce1ee67 | ||
|
|
64d10f5050 | ||
|
|
95b9edca62 | ||
|
|
06c8422cbe | ||
|
|
177d8b5112 | ||
|
|
39afa98fce | ||
|
|
5bc1ad4ba5 | ||
|
|
0b1223976c | ||
|
|
3873758fe5 | ||
|
|
3c876cb1d8 | ||
|
|
0a1a16c197 | ||
|
|
3c56325fa6 | ||
|
|
e5db3353ae | ||
|
|
3416ba4eaa | ||
|
|
cdb69bf553 | ||
|
|
1aff6d0d55 | ||
|
|
67b7bdcea3 | ||
|
|
9b6a6de415 | ||
|
|
8325246a56 | ||
|
|
3d11fb5e3f | ||
|
|
4627a58a52 | ||
|
|
e557047af1 | ||
|
|
8b4cea7339 | ||
|
|
ba5e35ec5c | ||
|
|
f53817e94d | ||
|
|
846efc5351 | ||
|
|
0149d00d64 | ||
|
|
275f9730f1 | ||
|
|
43bc954989 | ||
|
|
c88bc722e1 | ||
|
|
ec6fc6839e | ||
|
|
8383c93292 | ||
|
|
37d8d85935 | ||
|
|
95db7c6329 | ||
|
|
636783335f | ||
|
|
f88431ded4 | ||
|
|
91b0de0580 | ||
|
|
a5704ad75c | ||
|
|
ba8c09a7d9 | ||
|
|
dbe4015655 | ||
|
|
76237ad7dd | ||
|
|
dbfb1168fb | ||
|
|
830d28fbe5 | ||
|
|
c6def2a8fe | ||
|
|
ebc95fcea7 | ||
|
|
f78cfe59e8 | ||
|
|
a2cbc93d34 | ||
|
|
4728f14e8c | ||
|
|
e94b431d58 | ||
|
|
0ac5e2b995 | ||
|
|
365764e3e3 | ||
|
|
8d2b5aa3de | ||
|
|
9a031a4a43 | ||
|
|
d412679fd7 | ||
|
|
22c52d4432 | ||
|
|
9e5bdf9e33 | ||
|
|
7253eca5c0 | ||
|
|
b1f3dc29c5 | ||
|
|
8e3df8d41b | ||
|
|
efad22c00a | ||
|
|
b7690f13b1 | ||
|
|
6e1739da2e | ||
|
|
77ab423041 | ||
|
|
96a8e885d3 | ||
|
|
2b917c7eca | ||
|
|
942d2f7a4e | ||
|
|
75142ff1b2 | ||
|
|
74172dc3fa | ||
|
|
848fdda8d7 | ||
|
|
3895569a0c | ||
|
|
0aac29ca76 | ||
|
|
25f9eece8d | ||
|
|
0e22b8ab2c | ||
|
|
577c2ce6cd | ||
|
|
5b9cb8ca4b | ||
|
|
2eda93c0c2 | ||
|
|
d916adf2e3 | ||
|
|
1f065bcbb2 | ||
|
|
3efdd9505e | ||
|
|
ab3f5f6a6c | ||
|
|
9027be7bbb | ||
|
|
7b08b5c035 | ||
|
|
4a557c67b7 | ||
|
|
a6ebe092e8 | ||
|
|
9759e1b396 | ||
|
|
b6147ca5a1 | ||
|
|
83887db77a | ||
|
|
1facfbec0e | ||
|
|
d6608325b4 | ||
|
|
439b090470 | ||
|
|
ed98319a21 | ||
|
|
54f94f44c0 | ||
|
|
57cf0e23cf | ||
|
|
9e66269aca | ||
|
|
a4f50fa2c3 | ||
|
|
7a0bafb02c | ||
|
|
a24658ac0e | ||
|
|
bb77385b66 | ||
|
|
e8b1bb649c | ||
|
|
023347da3f | ||
|
|
f10146be03 | ||
|
|
7bac3c1326 | ||
|
|
528c0a10de | ||
|
|
c1e1a8d1a9 | ||
|
|
21720a510d | ||
|
|
a2d5e7f32f | ||
|
|
629fb7b9cd | ||
|
|
f6a4af8c00 | ||
|
|
837e60316a | ||
|
|
2592a90853 | ||
|
|
788429efb7 | ||
|
|
674d8672f0 | ||
|
|
8d371fce79 | ||
|
|
f0f391ec73 | ||
|
|
abc3b5569c | ||
|
|
c42dc0f12e | ||
|
|
7ec14f2eff | ||
|
|
31d4e88efd | ||
|
|
da06184d51 | ||
|
|
08296793f1 | ||
|
|
3b6fde2ea6 | ||
|
|
1f2948c9a5 | ||
|
|
e62267042e | ||
|
|
883fe48946 | ||
|
|
6ba18c5bd1 | ||
|
|
207347190d | ||
|
|
0cf76e1f11 | ||
|
|
9d76311f82 | ||
|
|
c327a292c7 | ||
|
|
5a965f6d52 | ||
|
|
a81c621d49 | ||
|
|
2e7b5d0d08 | ||
|
|
6d324fe8da | ||
|
|
6d2be3a083 | ||
|
|
d252c6fc5c | ||
|
|
30c5e87c67 | ||
|
|
2db819de2b | ||
|
|
bff5e64484 | ||
|
|
3492bef2c7 | ||
|
|
6d9aa56084 | ||
|
|
94c40b3444 | ||
|
|
278c8b1115 | ||
|
|
8e8581cce5 | ||
|
|
bd29f4e9e2 | ||
|
|
eb2b92d85c | ||
|
|
042ac0d338 | ||
|
|
a7011f0d6b | ||
|
|
22144ee18d | ||
|
|
b85484fceb | ||
|
|
b58956371f | ||
|
|
f487e9f7e2 | ||
|
|
f14efd3b1e | ||
|
|
3496a42c19 | ||
|
|
0882c3ecf5 | ||
|
|
1f6d76bac7 | ||
|
|
db7c3f4321 | ||
|
|
9ea4651fb0 | ||
|
|
ed5a75447f | ||
|
|
d08994a314 | ||
|
|
2bf6e7073c | ||
|
|
7990458cf3 | ||
|
|
f3b13735f3 | ||
|
|
ae680a32bc | ||
|
|
cbd0f18c6e | ||
|
|
fd62b1b631 | ||
|
|
2159f6720e | ||
|
|
67c3a36464 | ||
|
|
652efc8624 | ||
|
|
30da041ea3 | ||
|
|
e90c401503 | ||
|
|
83d919f06e | ||
|
|
74fef0d2d9 | ||
|
|
5906b24da1 | ||
|
|
8dd36f7adf | ||
|
|
f2dc26dbff | ||
|
|
87915ecd00 | ||
|
|
3cf4e55621 | ||
|
|
4c7cd840df | ||
|
|
bd238a6fe4 | ||
|
|
beaace9b54 | ||
|
|
c5352c2a73 | ||
|
|
dd6410c33e | ||
|
|
ac9dca8098 | ||
|
|
74ade6fecc | ||
|
|
e1cfbb155d | ||
|
|
030aba2ac3 | ||
|
|
9ab7e722e0 | ||
|
|
14d6714209 | ||
|
|
e8e50bebcf | ||
|
|
fa0610e392 | ||
|
|
124489a5a5 | ||
|
|
22fcdc0fb1 | ||
|
|
1375300a91 | ||
|
|
e6f4aea4d7 | ||
|
|
d72c4057ff | ||
|
|
02d80ee4fa | ||
|
|
2da3e9e0be | ||
|
|
8333e7bdb3 | ||
|
|
cef2472aae | ||
|
|
a4946f7e89 | ||
|
|
3e95e0feae | ||
|
|
b81843cdf7 | ||
|
|
eedd50fc64 | ||
|
|
1cb00bca0d | ||
|
|
8c233570a7 | ||
|
|
8a3c8b85bb | ||
|
|
2ee8a807dc | ||
|
|
ec85f748fa | ||
|
|
051b7fcb8c | ||
|
|
9af88811d8 | ||
|
|
6c4672037c | ||
|
|
d59e0d9ee2 | ||
|
|
5a001a1457 | ||
|
|
44d6474b36 | ||
|
|
0607e005f5 | ||
|
|
308eb4b55f | ||
|
|
6abb6207c5 | ||
|
|
89189d52ae | ||
|
|
e653ce69f8 | ||
|
|
9b89b328f6 | ||
|
|
77ae71fab1 | ||
|
|
7f7c1e6b93 | ||
|
|
30341bea1c | ||
|
|
82d44ea890 | ||
|
|
5f0907168f | ||
|
|
48061962b1 | ||
|
|
24141b66cf | ||
|
|
41beee45ba | ||
|
|
cb7c70b0d6 | ||
|
|
2aa5a57557 | ||
|
|
17582bcefb | ||
|
|
1f5a5afb10 | ||
|
|
d1c724563f | ||
|
|
72aa5eccb7 | ||
|
|
95e0c4b0d6 | ||
|
|
9ec6ca0ded | ||
|
|
85b7611214 | ||
|
|
d256d435e4 | ||
|
|
577a976e01 | ||
|
|
07453834b0 | ||
|
|
59f6b5e411 | ||
|
|
ac9be8360a | ||
|
|
32d0b07f09 | ||
|
|
7b57639076 | ||
|
|
1fa174bab7 | ||
|
|
6c52b5a77d | ||
|
|
d4a6157ed3 | ||
|
|
a86358bbcd | ||
|
|
20d0e6d047 | ||
|
|
51fc4bf3e4 | ||
|
|
61d0dafd21 | ||
|
|
89398cb5e5 | ||
|
|
72ec4063de | ||
|
|
f8423eeea8 | ||
|
|
d91203990e | ||
|
|
fc84c1af51 | ||
|
|
58de07073d | ||
|
|
cafa5ff821 | ||
|
|
1859799ca3 | ||
|
|
271aa11f74 | ||
|
|
e42f1a65f5 | ||
|
|
73f3a31614 | ||
|
|
4d7792d06a | ||
|
|
1a0f17c059 | ||
|
|
89d941401a | ||
|
|
a076ee640d | ||
|
|
8c1629f7dd | ||
|
|
9a31c07ac5 | ||
|
|
11f39316b5 | ||
|
|
f157229e00 | ||
|
|
a0f25c4e6f | ||
|
|
003b3d2a84 | ||
|
|
1775e0bda1 | ||
|
|
d5038ac631 | ||
|
|
7eab77ba01 | ||
|
|
89413c56e2 | ||
|
|
4d89643203 | ||
|
|
e119590249 | ||
|
|
ace2dfac30 | ||
|
|
5843df0ce8 | ||
|
|
403ed8adb6 | ||
|
|
1eb5c8bc46 | ||
|
|
46f7ec5e00 | ||
|
|
52adece147 | ||
|
|
de50be9d1b | ||
|
|
0f9e32b49f | ||
|
|
b4c15adf5d | ||
|
|
a3d3886c51 | ||
|
|
0cfa7ce774 | ||
|
|
8299ea909e | ||
|
|
8b056fe610 | ||
|
|
511c40db5c | ||
|
|
42d0d42188 | ||
|
|
c80a998444 | ||
|
|
0a10df75d1 | ||
|
|
40d755d191 | ||
|
|
0409a610ab | ||
|
|
aa9cd23cf3 | ||
|
|
12d89114a4 | ||
|
|
d5b679187a | ||
|
|
463ba3754a | ||
|
|
2fa8fd14e7 | ||
|
|
fac788d2a9 | ||
|
|
6554f900d7 | ||
|
|
81d3ec83d9 | ||
|
|
339c5a357b | ||
|
|
b484992037 | ||
|
|
87e42e98c6 | ||
|
|
51bef01609 | ||
|
|
6a49244614 | ||
|
|
dad2b61a74 | ||
|
|
fabc595f3d | ||
|
|
a700787cf3 | ||
|
|
fb26229715 | ||
|
|
c4f894f8d0 | ||
|
|
ddd1c19c64 | ||
|
|
79cb3bdba4 | ||
|
|
8447c2150b | ||
|
|
bc465971be | ||
|
|
f8ce23aa11 | ||
|
|
84004c4d73 | ||
|
|
2b6aa9b89b | ||
|
|
54b8a3e789 | ||
|
|
7ff5f0001c | ||
|
|
d384aa9dcf | ||
|
|
1190da7c91 | ||
|
|
6561791530 | ||
|
|
26e233d3eb | ||
|
|
0417981538 | ||
|
|
81fc56937e | ||
|
|
cb778d7e35 | ||
|
|
8442b43715 | ||
|
|
28b3306506 | ||
|
|
f51f45aeb2 | ||
|
|
7f1184736c | ||
|
|
e4c8e7a0c5 | ||
|
|
44e4722de2 | ||
|
|
7c2b0c5947 | ||
|
|
6ea912ad0e | ||
|
|
124bbb6ac1 | ||
|
|
30d6345152 | ||
|
|
60ff7b2f97 | ||
|
|
c6e90142bb | ||
|
|
1289a025a5 | ||
|
|
18d0b72785 | ||
|
|
0157cf496f | ||
|
|
3450f2d19d | ||
|
|
e509c82807 | ||
|
|
d5afd6f32e | ||
|
|
fb34dd2864 | ||
|
|
0cd1ce3431 | ||
|
|
a24447d569 | ||
|
|
0bf26afa33 | ||
|
|
49455e8054 | ||
|
|
1136e43115 | ||
|
|
4b74cba4e4 | ||
|
|
c8a77934f2 | ||
|
|
e1739e04d1 | ||
|
|
a5d39a18a7 | ||
|
|
77eb661b0e | ||
|
|
667d0d16ea | ||
|
|
445ea660c3 | ||
|
|
7a35640739 | ||
|
|
f1650fd383 | ||
|
|
5d67a554d6 | ||
|
|
380bfba776 | ||
|
|
ed6d125391 | ||
|
|
adaa5fd56d | ||
|
|
0a8db237eb | ||
|
|
6d723e022b | ||
|
|
dc3b739719 | ||
|
|
8d67599278 | ||
|
|
c8da5770c8 | ||
|
|
51c11d9d1a | ||
|
|
414c164100 | ||
|
|
aa5fd6d946 | ||
|
|
c89f74d35b | ||
|
|
8c2fdf8554 | ||
|
|
f2438d9524 | ||
|
|
54e7b29716 | ||
|
|
1d7f4036ec | ||
|
|
a2d5eda538 | ||
|
|
1b6a27376c | ||
|
|
8431150f0a | ||
|
|
ed5107afaa | ||
|
|
e52bdd7ca5 | ||
|
|
26e50848da | ||
|
|
41fa52c9d5 | ||
|
|
885aa3035e | ||
|
|
b20c20983a | ||
|
|
42994847e9 | ||
|
|
b59a12e743 | ||
|
|
f67792cd30 | ||
|
|
60ba345e6c | ||
|
|
0074548b84 | ||
|
|
5af5549173 | ||
|
|
f9f0235d7b | ||
|
|
cd51420931 | ||
|
|
000d11976b | ||
|
|
392261c208 | ||
|
|
71aee40f8d | ||
|
|
565ef2c321 | ||
|
|
2d5c8c9e6d | ||
|
|
800f983688 | ||
|
|
7291e54db7 | ||
|
|
794b1c70eb | ||
|
|
330b227027 | ||
|
|
59a66a63c5 | ||
|
|
ceb28f08c5 | ||
|
|
7287754218 | ||
|
|
80d2f64f10 | ||
|
|
9951b83539 | ||
|
|
66d50be35a | ||
|
|
9eb5a4cee4 | ||
|
|
3c575c01ad | ||
|
|
643028f5d8 | ||
|
|
3831b9409e | ||
|
|
985ae848d3 | ||
|
|
0c8c8b6040 | ||
|
|
a01c084045 | ||
|
|
7577adf09d | ||
|
|
394002eb4c | ||
|
|
5c386148a4 | ||
|
|
3558d3db93 | ||
|
|
2bed812df1 | ||
|
|
c5db2e0ea9 | ||
|
|
6cd1d7c41b | ||
|
|
36662d58da | ||
|
|
e85cc6ac56 | ||
|
|
a17d2d69ef | ||
|
|
3ec615a6ec | ||
|
|
8eadc6aea1 | ||
|
|
506678e72c | ||
|
|
aa3561555e | ||
|
|
405a8aaad0 | ||
|
|
7edd45419e | ||
|
|
5af028698c | ||
|
|
ce822e7538 | ||
|
|
9936bee8e0 | ||
|
|
b903e8461d | ||
|
|
4d2c42330e | ||
|
|
0ea37fdb31 | ||
|
|
22bf738d21 | ||
|
|
922ba7d192 | ||
|
|
c38a98c239 | ||
|
|
0f9452f6ca | ||
|
|
5cc9c6e3e4 | ||
|
|
5da30faf71 | ||
|
|
99d984f516 | ||
|
|
20f0760f13 | ||
|
|
20ffa9bee5 | ||
|
|
bdd003f177 | ||
|
|
a45a960bfd | ||
|
|
fbf56f2663 | ||
|
|
0e7ac37b9d | ||
|
|
bcf0d86605 | ||
|
|
954f894738 | ||
|
|
908def1b69 | ||
|
|
f26f638ad4 | ||
|
|
700f47fd49 | ||
|
|
3d0eaacaac | ||
|
|
c76118d9d6 | ||
|
|
148c39158b | ||
|
|
89c2d45a4b | ||
|
|
c30c54ab97 | ||
|
|
734cfba45d | ||
|
|
3b363a1b20 | ||
|
|
a747bafa69 | ||
|
|
9d0c11b051 | ||
|
|
bd9e997ef1 | ||
|
|
f222327a89 | ||
|
|
4e5351134f | ||
|
|
ec1d5cc211 | ||
|
|
8f41e25ac1 | ||
|
|
2d90caafc6 | ||
|
|
a9f36076b3 | ||
|
|
8365e557d5 | ||
|
|
080160ca70 | ||
|
|
e6164b0b3e | ||
|
|
26d786a368 | ||
|
|
c6172644f0 | ||
|
|
5810947a5d | ||
|
|
708c2d373d | ||
|
|
d11a9b2219 | ||
|
|
32b8b008f2 | ||
|
|
e066d1ce8d | ||
|
|
4f3f19a785 | ||
|
|
6c3ea4517d | ||
|
|
1e7e3b8c42 | ||
|
|
a54c18e89b | ||
|
|
ed4d5efea6 | ||
|
|
4bab279345 | ||
|
|
ec32eeba0b | ||
|
|
2b74e2afb2 | ||
|
|
c4a9d244a9 | ||
|
|
6643fb0d63 | ||
|
|
b2a3949ad0 | ||
|
|
93c1a44285 | ||
|
|
62c4fb328c | ||
|
|
578fb72ddc | ||
|
|
19faaea10e | ||
|
|
9951aae398 | ||
|
|
0963559a5f | ||
|
|
622294b807 | ||
|
|
ba72a92a8a | ||
|
|
5a5e072fb4 | ||
|
|
91cb2935eb | ||
|
|
387aeab319 | ||
|
|
4db7b54a5f | ||
|
|
ddfd7254f1 | ||
|
|
2e410c521a | ||
|
|
5dd22e85fa | ||
|
|
022dd78eda | ||
|
|
2b1812b5de | ||
|
|
3c9678c6d5 | ||
|
|
4f1f9ecb6b | ||
|
|
07fb572a67 | ||
|
|
7d7f4acc8c | ||
|
|
efba05291d | ||
|
|
8b0d7a5cdb | ||
|
|
162deccf04 | ||
|
|
083f14bf50 | ||
|
|
cae3f7354e | ||
|
|
75e62a3fac | ||
|
|
69dee3be78 | ||
|
|
36bf7c5cc6 | ||
|
|
c041819930 | ||
|
|
dd4d00eae6 | ||
|
|
d12e14ab34 | ||
|
|
c0a444d0c6 | ||
|
|
e16a5524b8 | ||
|
|
c386e92831 | ||
|
|
ed277a95fb | ||
|
|
c14b09ee1d | ||
|
|
52fecc27ce | ||
|
|
056b24e936 | ||
|
|
e34607f244 | ||
|
|
7ba7bf64f0 | ||
|
|
6f51a98477 | ||
|
|
cd7e0f5380 | ||
|
|
d95aba8cdd | ||
|
|
922fce7bcc | ||
|
|
9e3646ebc5 | ||
|
|
cdf3ea9dab | ||
|
|
e38ac19a58 | ||
|
|
9d3f309fc3 | ||
|
|
6b7eb9a1f2 | ||
|
|
d2165c99f1 | ||
|
|
8b538b833b | ||
|
|
2977fb39e4 | ||
|
|
01cb43ba2e | ||
|
|
4b68a083c5 | ||
|
|
5854a3eef5 | ||
|
|
df5cbacc8a | ||
|
|
05fa30a039 | ||
|
|
adabf488ee | ||
|
|
e86b7c68e6 | ||
|
|
c5b974e055 | ||
|
|
b3695d4a93 | ||
|
|
b5e5450452 | ||
|
|
d19cddccea | ||
|
|
c61649f7fa | ||
|
|
13b8580b2f | ||
|
|
aba2b0ea19 | ||
|
|
0894637b61 | ||
|
|
5970aabb0d | ||
|
|
72a25dcf34 | ||
|
|
6fff30bd89 | ||
|
|
a9a71834b8 | ||
|
|
8ec4865e23 | ||
|
|
111c7ef954 | ||
|
|
35a37458e4 | ||
|
|
92bafbafa7 | ||
|
|
2705caf6bd | ||
|
|
8380c7d994 | ||
|
|
550c0f3854 | ||
|
|
04cd135295 | ||
|
|
e212a080c2 | ||
|
|
533abb58db | ||
|
|
941a542cc6 | ||
|
|
5bcda2cd3a | ||
|
|
869d8ba8f9 | ||
|
|
984e8f8780 | ||
|
|
750b61fb75 | ||
|
|
4a2defa300 | ||
|
|
5c9f7a8bcb | ||
|
|
b66caa9848 | ||
|
|
81cac1e54f | ||
|
|
36a99cd64f | ||
|
|
8c494642e4 | ||
|
|
7ab4088331 | ||
|
|
580af33caf | ||
|
|
bbeeacc480 | ||
|
|
51efef5005 | ||
|
|
ee030c8f61 | ||
|
|
20835e1fb3 | ||
|
|
b542680e7a | ||
|
|
fb32aedd69 | ||
|
|
56f6d34995 | ||
|
|
c68e380a0f | ||
|
|
fc5507bdd3 | ||
|
|
3f0a656a36 | ||
|
|
60667149d4 | ||
|
|
cdb3ede207 | ||
|
|
445494ce95 | ||
|
|
972f52fd01 | ||
|
|
f5e86d668f | ||
|
|
0e817236a1 | ||
|
|
0ea3b3ac2a | ||
|
|
7bfaef82d8 | ||
|
|
f51220c9a8 | ||
|
|
88e9ca721a | ||
|
|
c1ab444646 | ||
|
|
b497b53ef2 | ||
|
|
80decaae8a | ||
|
|
41197cb52c | ||
|
|
d14abcbba7 | ||
|
|
cd36fc8d4a | ||
|
|
e872487724 | ||
|
|
5e7e91caf0 | ||
|
|
a971a29ff2 | ||
|
|
a967482c4f | ||
|
|
f91ada3c48 | ||
|
|
44782d552d | ||
|
|
a51dfb4033 | ||
|
|
ccb18e9335 | ||
|
|
194556a0a1 | ||
|
|
8f828bca80 | ||
|
|
a967234ddb | ||
|
|
bbcf7d1206 | ||
|
|
6904045c7f | ||
|
|
59c4052765 |
@@ -14,13 +14,16 @@ const SCOPES = [
|
||||
|
||||
"mobile",
|
||||
"web",
|
||||
"vericrypt",
|
||||
"desktop",
|
||||
"crypto",
|
||||
"editor",
|
||||
"logger",
|
||||
"theme",
|
||||
"server",
|
||||
"core",
|
||||
"fs",
|
||||
"ui",
|
||||
"clipper",
|
||||
"config",
|
||||
"ci",
|
||||
@@ -30,7 +33,8 @@ const SCOPES = [
|
||||
"misc",
|
||||
"common",
|
||||
"global",
|
||||
"docs"
|
||||
"docs",
|
||||
"themebuilder"
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -19,7 +19,6 @@ runs:
|
||||
packages/crypto/package-lock.json
|
||||
packages/sodium/package-lock.json
|
||||
packages/clipper/package-lock.json
|
||||
packages/crypto-worker/package-lock.json
|
||||
packages/editor-mobile/package-lock.json
|
||||
packages/editor/package-lock.json
|
||||
packages/logger/package-lock.json
|
||||
|
||||
@@ -22,7 +22,9 @@ jobs:
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=mobile
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd apps/mobile/native/android && chmod +x ./gradlew
|
||||
|
||||
|
||||
32
.github/workflows/android.publish.yml
vendored
32
.github/workflows/android.publish.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Publish @notesnook/mobile
|
||||
name: Publish @notesnook/android
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
@@ -22,34 +22,12 @@ jobs:
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=mobile
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd apps/mobile/native/android && chmod +x ./gradlew
|
||||
|
||||
- name: Build unsigned app bundle
|
||||
run: yarn release:android:bundle
|
||||
|
||||
- name: Sign app bundle for Playstore release
|
||||
id: sign_app
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
|
||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Publish to Playstore
|
||||
id: deploy
|
||||
uses: r0adkll/upload-google-play@v1.1.1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: com.streetwriters.notesnook
|
||||
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
track: production
|
||||
status: completed
|
||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||
|
||||
- name: Build apks for Github release
|
||||
run: yarn release:android
|
||||
|
||||
@@ -62,6 +40,8 @@ jobs:
|
||||
alias: ${{ secrets.PUBLIC_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
|
||||
- name: Rename apk files
|
||||
run: |
|
||||
|
||||
3
.github/workflows/core.tests.yml
vendored
3
.github/workflows/core.tests.yml
vendored
@@ -28,7 +28,8 @@ jobs:
|
||||
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
npm ci --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=core
|
||||
- name: Add environment variables
|
||||
run: |
|
||||
echo "USER_EMAIL=${{ secrets.USER_EMAIL }}" >> $GITHUB_ENV
|
||||
|
||||
53
.github/workflows/desktop.publish.yml
vendored
53
.github/workflows/desktop.publish.yml
vendored
@@ -18,6 +18,21 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
description: "Publish on GitHub releases?"
|
||||
build-windows:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for Windows?"
|
||||
build-linux:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for Linux?"
|
||||
build-mac:
|
||||
type: boolean
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for macOS?"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -32,8 +47,8 @@ jobs:
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
@@ -71,6 +86,7 @@ jobs:
|
||||
build-macos:
|
||||
name: Build for macOS
|
||||
needs: build
|
||||
if: inputs.build-mac
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
@@ -97,6 +113,7 @@ jobs:
|
||||
- name: Get App Store Version
|
||||
id: appstore
|
||||
uses: streetwriters/appstore-connect-app-version@develop
|
||||
if: inputs.publish-apple
|
||||
with:
|
||||
app-id: ${{ steps.app_metadata.outputs.apple_app_id }}
|
||||
key-id: ${{ secrets.api_key_id }}
|
||||
@@ -108,12 +125,12 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/desktop/build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --scope=@notesnook/desktop --scope=@notesnook/crypto --scope=@notesnook/sodium -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Install provisioning profile
|
||||
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
|
||||
@@ -162,6 +179,7 @@ jobs:
|
||||
build-linux:
|
||||
name: Build for Linux
|
||||
needs: build
|
||||
if: inputs.build-linux
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
@@ -183,12 +201,12 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/desktop/build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --scope=@notesnook/desktop --scope=@notesnook/crypto --scope=@notesnook/sodium -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Build Electron wrapper
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
@@ -220,12 +238,22 @@ jobs:
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
needs: build
|
||||
if: inputs.build-windows
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET Core SDK
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
|
||||
- name: Install AzureCodeSigning
|
||||
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
|
||||
shell: pwsh
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
@@ -233,12 +261,12 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build
|
||||
path: ./apps/desktop/build
|
||||
path: ./apps/web/build
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --scope=@notesnook/desktop --scope=@notesnook/crypto --scope=@notesnook/sodium -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=desktop
|
||||
|
||||
- name: Build
|
||||
run: npx nx run release --project @notesnook/desktop
|
||||
@@ -246,6 +274,9 @@ jobs:
|
||||
- name: Publish
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
run: |
|
||||
if ($${{ inputs.publish-github }} -eq $true) {
|
||||
yarn electron-builder --win --publish always
|
||||
|
||||
4
.github/workflows/editor.tests.yml
vendored
4
.github/workflows/editor.tests.yml
vendored
@@ -29,8 +29,8 @@ jobs:
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=editor
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
|
||||
35
.github/workflows/help.publish.yml
vendored
Normal file
35
.github/workflows/help.publish.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Publish Notesnook Help
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "docs/help/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install docgen
|
||||
run: cargo install --git https://github.com/thecodrr/docgen
|
||||
|
||||
- name: Build site
|
||||
run: docgen build --release
|
||||
working-directory: docs/help
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Publish on Cloudflare Pages
|
||||
run: npx --yes wrangler pages deploy --project-name notesnook-help ./docs/help/site/ --branch main
|
||||
67
.github/workflows/ios.publish.yml
vendored
Normal file
67
.github/workflows/ios.publish.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Publish @notesnook/ios
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-13
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install node modules
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=mobile
|
||||
|
||||
- name: Build packages
|
||||
run: npx nx run @notesnook/mobile:build
|
||||
|
||||
- name: Install Pods
|
||||
run: npm run prepare:ios
|
||||
|
||||
- name: Build iOS App
|
||||
uses: yukiarrr/ios-build-action@v1.11.2
|
||||
with:
|
||||
bundle-identifier: org.streetwriters.notesnook
|
||||
scheme: Notesnook
|
||||
configuration: "Release"
|
||||
export-options: apps/mobile/native/ios/ExportOptions.plist
|
||||
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
|
||||
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
|
||||
update-targets: |
|
||||
Notesnook
|
||||
Make Note
|
||||
NotesWidgetExtension
|
||||
disable-targets: Notesnook-tvOS,Notesnook-tvOSTests,NotesnookTests
|
||||
code-signing-identity: Apple Distribution
|
||||
team-id: ${{ secrets.APPLE_TEAM_ID }}
|
||||
p12-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
||||
certificate-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
|
||||
app-store-connect-api-key-issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||
app-store-connect-api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-connect-api-key-base64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }}
|
||||
output-path: Notesnook.ipa
|
||||
mobileprovision-base64: |
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_APP }}
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_SHARE }}
|
||||
${{ secrets.APPLE_MOBILE_PROVISION_WIDGET }}
|
||||
|
||||
- name: 'Upload app to TestFlight'
|
||||
uses: apple-actions/upload-testflight-build@v1
|
||||
with:
|
||||
app-path: Notesnook.ipa
|
||||
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||
api-key-id: ${{ secrets.API_KEY_ID }}
|
||||
api-private-key: ${{ secrets.API_KEY }}
|
||||
|
||||
- name: Upload to Github
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Notesnook.ipa
|
||||
path: Notesnook.ipa
|
||||
31
.github/workflows/theme-builder.publish.yml
vendored
Normal file
31
.github/workflows/theme-builder.publish.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Publish @notesnook/theme-builder
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=themebuilder
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate build
|
||||
run: npm run build:theme-builder
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
run: npx --yes wrangler pages deploy --project-name=notesnook-theme-builder ./apps/theme-builder/build/
|
||||
31
.github/workflows/vericrypt.publish.yml
vendored
Normal file
31
.github/workflows/vericrypt.publish.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Publish @notesnook/vericrypt
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=vericrypt
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate build
|
||||
run: npm run build:vericrypt
|
||||
|
||||
- name: Publish on Cloudflare Pages
|
||||
run: npx --yes wrangler pages deploy --project-name=vericrypt ./apps/vericrypt/build/
|
||||
69
.github/workflows/web.beta.publish.yml
vendored
Normal file
69
.github/workflows/web.beta.publish.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Publish @notesnook/web Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '!v3-beta'
|
||||
paths-ignore:
|
||||
- "apps/mobile/**"
|
||||
- "packages/editor-mobile/**"
|
||||
- "apps/desktop/**"
|
||||
- "apps/vericrypt/**"
|
||||
- "servers/**"
|
||||
- "packages/clipper/**"
|
||||
- "extensions/web-clipper/**"
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
- ready_for_review
|
||||
- reopened
|
||||
- opened
|
||||
- synchronize
|
||||
branches:
|
||||
- master
|
||||
- '!v3-beta'
|
||||
paths-ignore:
|
||||
- "apps/mobile/**"
|
||||
- "packages/editor-mobile/**"
|
||||
- "apps/desktop/**"
|
||||
- "apps/vericrypt/**"
|
||||
- "servers/**"
|
||||
- "packages/clipper/**"
|
||||
- "extensions/web-clipper/**"
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate beta build
|
||||
run: npm run build:beta:web
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
|
||||
id: pages
|
||||
with:
|
||||
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
cloudflare-project-name: notesnook-beta-app
|
||||
directory: ./apps/web/build
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}
|
||||
10
.github/workflows/web.publish.yml
vendored
10
.github/workflows/web.publish.yml
vendored
@@ -15,8 +15,8 @@ jobs:
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate desktop build
|
||||
- name: Generate build
|
||||
run: npm run build:web
|
||||
|
||||
- name: Publish on Cloudflare Pages
|
||||
run: npx --yes wrangler pages publish --project-name=notesnook-app ./apps/web/build/
|
||||
- name: Publish to Cloudflare Pages
|
||||
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./apps/web/build/
|
||||
|
||||
6
.github/workflows/web.tests.yml
vendored
6
.github/workflows/web.tests.yml
vendored
@@ -29,8 +29,8 @@ jobs:
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm i --ignore-scripts --prefer-offline --no-audit
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm install -D @playwright/test otplib
|
||||
|
||||
- name: Validate build extraction
|
||||
|
||||
40
.github/workflows/web.v3-beta.publish.yml
vendored
Normal file
40
.github/workflows/web.v3-beta.publish.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Publish @notesnook/web v3 Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm run bootstrap -- --scope=web
|
||||
|
||||
- name: Setup environment
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate beta build
|
||||
run: npm run build:beta:web
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
|
||||
id: pages
|
||||
with:
|
||||
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
cloudflare-project-name: notesnook-v3-app
|
||||
directory: ./apps/web/build
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,4 +9,5 @@ nx-cloud.env
|
||||
.env.local
|
||||
.nyc_output
|
||||
site
|
||||
node_modules.backup
|
||||
node_modules.backup
|
||||
.nx
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"files.eol": "\n",
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"javascript.format.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSaveMode": "modificationsIfAvailable",
|
||||
"eslint.packageManager": "npm",
|
||||
"editor.formatOnSaveMode": "file",
|
||||
"eslint.run": "onSave",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"eslint.execArgv": ["--cache"]
|
||||
"eslint.options": {
|
||||
"cache": true,
|
||||
"cacheStrategy": "content"
|
||||
},
|
||||
"git.rebaseWhenSync": true
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ All commits must include valid scopes in the commit message. **Valid commit scop
|
||||
|
||||
**Packages:**
|
||||
|
||||
1. `crypto`: changes related to the cryptographic core (including `@notesnook/crypto-worker`)
|
||||
1. `crypto`: changes related to the cryptographic core
|
||||
2. `editor`: changes related to the editor (including `@notesnook/editor-mobile`)
|
||||
3. `logger`: changes related to the logger
|
||||
4. `theme`: changes related to the theme
|
||||
|
||||
@@ -27,7 +27,7 @@ Notesnook is built using the following technologies:
|
||||
3. React Native — For mobile apps we are using React Native
|
||||
4. Electron — For desktop app
|
||||
5. NPM — listed here because we **don't** use Yarn or PNPM or XYZ across any of our projects.
|
||||
6. Lerna & Nx — maintaining monorepos is hard but Nx makes it easier.
|
||||
6. Nx — maintaining monorepos is hard but Nx makes it easier.
|
||||
|
||||
> **Note: Each project in the monorepo contains its own architecture details which you can refer to.**
|
||||
|
||||
@@ -46,7 +46,6 @@ Notesnook is built using the following technologies:
|
||||
| `@notesnook/editor-mobile` | [/packages/editor-mobile](/packages/editor-mobile) | A very thin wrapper around `@notesnook/editor` for mobile clients |
|
||||
| `@notesnook/logger` | [/packages/logger](/packages/logger) | Simple & pluggable logger |
|
||||
| `@notesnook/sodium` | [/packages/sodium](/packages/sodium) | Wrapper around libsodium to support Node.js & Browser |
|
||||
| `@notesnook/crypto-worker` | [/packages/crypto-worker](/packages/crypto-worker) | Helpers to use `@notesnook/crypto` from a Worker |
|
||||
| `@notesnook/streamable-fs` | [/packages/streamable-fs](/packages/streamable-fs) | Streaming interface around an IndexedDB based file system |
|
||||
| `@notesnook/theme` | [/packages/theme](/packages/theme) | The core theme used in web & desktop clients |
|
||||
|
||||
|
||||
3
apps/desktop/app-update.yml
Normal file
3
apps/desktop/app-update.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
owner: streetwriters
|
||||
repo: notesnook
|
||||
provider: github
|
||||
31
apps/desktop/global.d.ts
vendored
31
apps/desktop/global.d.ts
vendored
@@ -19,9 +19,40 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/* eslint-disable no-var */
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import {
|
||||
type FormData as FormDataType,
|
||||
type Headers as HeadersType,
|
||||
type Request as RequestType,
|
||||
type Response as ResponseType
|
||||
} from "undici";
|
||||
|
||||
declare global {
|
||||
var window: BrowserWindow | null;
|
||||
var RELEASE: boolean;
|
||||
var MAC_APP_STORE: boolean;
|
||||
|
||||
// Re-export undici fetch function and various classes to global scope.
|
||||
// These are classes and functions expected to be at global scope according to Node.js v18 API
|
||||
// documentation.
|
||||
// See: https://nodejs.org/dist/latest-v18.x/docs/api/globals.html
|
||||
// eslint-disable-next-line no-var
|
||||
export var {
|
||||
FormData,
|
||||
Headers,
|
||||
Request,
|
||||
Response,
|
||||
fetch
|
||||
}: typeof import("undici");
|
||||
|
||||
type FormData = FormDataType;
|
||||
type Headers = HeadersType;
|
||||
type Request = RequestType;
|
||||
type Response = ResponseType;
|
||||
}
|
||||
|
||||
// NOTE: the import in the global block above needs to be a var for this to work properly.
|
||||
globalThis.fetch = fetch;
|
||||
globalThis.FormData = FormData;
|
||||
globalThis.Headers = Headers;
|
||||
globalThis.Request = Request;
|
||||
globalThis.Response = Response;
|
||||
|
||||
2009
apps/desktop/package-lock.json
generated
2009
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.5.5",
|
||||
"version": "2.6.15",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
@@ -11,34 +11,34 @@
|
||||
"repository": "https://github.com/streetwriters/notesnook",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/crypto": "*",
|
||||
"@trpc/client": "^10.29.1",
|
||||
"@trpc/server": "^10.29.1",
|
||||
"diary": "^0.3.1",
|
||||
"electron-trpc": "^0.5.2",
|
||||
"electron-updater": "^5.3.0",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"electron-trpc": "0.5.2",
|
||||
"electron-updater": "6.1.4",
|
||||
"icojs": "^0.17.1",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.6.2",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "^3.2.13",
|
||||
"@types/node": "^18.15.0",
|
||||
"electron": "^24.4.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"electron-reloader": "^1.2.3",
|
||||
"esbuild": "^0.17.19",
|
||||
"node-fetch": "^3.3.1"
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^5.23.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "turbowatch scripts/dev.ts",
|
||||
"staging": "zx scripts/build.mjs --run",
|
||||
"release": "zx scripts/build.mjs",
|
||||
"start": "node scripts/dev.mjs",
|
||||
"staging": "node scripts/build.mjs --run",
|
||||
"release": "node scripts/build.mjs",
|
||||
"build": "tsc",
|
||||
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
|
||||
"bundle:mas": "esbuild electron=./src/main.ts ./src/preload.ts --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
|
||||
@@ -55,11 +55,15 @@
|
||||
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
||||
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
||||
"npmRebuild": false,
|
||||
"asar": false,
|
||||
"files": [
|
||||
"!*.chunk.js.map",
|
||||
"!*.chunk.js.LICENSE.txt",
|
||||
"build/",
|
||||
"!build/screenshots${/*}",
|
||||
"!build/banner.jpg",
|
||||
"!build/*.ico",
|
||||
"!build/*.png",
|
||||
"!node_modules${/*}"
|
||||
],
|
||||
"afterSign": "electron-builder-notarize",
|
||||
@@ -131,6 +135,10 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
],
|
||||
"sign": "./sign.js",
|
||||
"icon": "assets/icons/app.ico"
|
||||
},
|
||||
"portable": {
|
||||
@@ -187,6 +195,7 @@
|
||||
"allowNativeWayland": true
|
||||
},
|
||||
"extraResources": [
|
||||
"app-update.yml",
|
||||
"./assets/**"
|
||||
],
|
||||
"extraMetadata": {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
diff --git a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
|
||||
index ffcc8bd..7e57c9e 100644
|
||||
index fcb7f54..cb1c7f7 100644
|
||||
--- a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
|
||||
+++ b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
|
||||
@@ -105,6 +105,7 @@ class LinuxTargetHelper {
|
||||
@@ -116,6 +116,7 @@ class LinuxTargetHelper {
|
||||
StartupWMClass: appInfo.productName,
|
||||
...extra,
|
||||
...targetSpecificOptions.desktop,
|
||||
+ actions: undefined
|
||||
};
|
||||
const description = this.getDescription(targetSpecificOptions);
|
||||
if (!builder_util_1.isEmptyOrSpaces(description)) {
|
||||
@@ -148,6 +149,23 @@ class LinuxTargetHelper {
|
||||
if (!(0, builder_util_1.isEmptyOrSpaces)(description)) {
|
||||
@@ -159,6 +160,23 @@ class LinuxTargetHelper {
|
||||
data += `\n${name}=${desktopMeta[name]}`;
|
||||
}
|
||||
data += "\n";
|
||||
+
|
||||
+
|
||||
+ if (targetSpecificOptions.desktop.actions) {
|
||||
+ let actionsData = "";
|
||||
+ const validActions = [];
|
||||
@@ -20,9 +20,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import { argv, os } from "zx";
|
||||
import yargs from "yargs-parser";
|
||||
import os from "os";
|
||||
import * as childProcess from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const args = yargs(process.argv);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const sodiumNativePrebuildPath = (arch) =>
|
||||
path.join(
|
||||
`node_modules`,
|
||||
@@ -37,18 +42,19 @@ const sodiumNativePrebuildPath = (arch) =>
|
||||
`${os.platform()}-${arch}`
|
||||
);
|
||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||
if (argv.rebuild) {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||
await exec(`cd ${webAppPath} && npm run build:desktop`);
|
||||
|
||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
}
|
||||
|
||||
if (argv.variant === "mas") {
|
||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
|
||||
if (args.variant === "mas") {
|
||||
await exec(`npm run bundle:mas`);
|
||||
} else {
|
||||
await exec(`npm run bundle`);
|
||||
@@ -92,13 +98,14 @@ if (existsSync(sodiumNativePrebuildPath("arm64"))) {
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.run) {
|
||||
if (args.run) {
|
||||
await exec(`npx electron-builder --dir --x64`);
|
||||
if (process.platform === "win32") {
|
||||
await exec(`npx electron-builder --win --x64`);
|
||||
// await exec(`.\\output\\notesnook_win_x64_portable.exe`);
|
||||
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
||||
} else if (process.platform === "darwin") {
|
||||
await exec(`./output/darwin-unpacked/Notesnook`);
|
||||
} else {
|
||||
await exec(`npx electron-builder --linux AppImage:x64`);
|
||||
await exec(`./output/notesnook_linux_x86_64.AppImage`);
|
||||
await exec(`./output/linux-unpacked/Notesnook`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
197
apps/desktop/scripts/dev.mjs
Normal file
197
apps/desktop/scripts/dev.mjs
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import chokidar from "chokidar";
|
||||
import { execSync, spawn } from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import treekill from "tree-kill";
|
||||
import crypto from "crypto";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const sodiumNativePrebuildPath = path.join(
|
||||
`node_modules`,
|
||||
`@notesnook`,
|
||||
`crypto`,
|
||||
`node_modules`,
|
||||
`@notesnook`,
|
||||
`sodium`,
|
||||
`node_modules`,
|
||||
`sodium-native`,
|
||||
`prebuilds`
|
||||
);
|
||||
const RUNNING_PROCESSES = [];
|
||||
const RESTARTABLE_PROCESSES = [];
|
||||
let lastBundleHash = null;
|
||||
|
||||
await onChange(true);
|
||||
|
||||
console.log("Watching...");
|
||||
|
||||
const watcher = chokidar.watch("src/", { ignoreInitial: true });
|
||||
watcher.on("all", () => {
|
||||
onChange(false);
|
||||
});
|
||||
|
||||
process.on("SIGINT", async (s) => {
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
async function onChange(first) {
|
||||
if (first) {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
}
|
||||
|
||||
await exec(`npm run bundle`);
|
||||
execAsync(`npx`, [`tsc`]);
|
||||
|
||||
if (await isBundleSame()) {
|
||||
console.log("Bundle is same. Doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
await fs.cp(sodiumNativePrebuildPath, "build/prebuilds", {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
}
|
||||
|
||||
if (first) {
|
||||
await spawnAndWaitUntil(
|
||||
["npx", "nx", "start:desktop", "@notesnook/web"],
|
||||
path.join(__dirname, "..", "..", ".."),
|
||||
(data) => data.includes("Network: use --host to expose")
|
||||
);
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
console.log("Restarting...", RESTARTABLE_PROCESSES.length);
|
||||
await killProcesses(RESTARTABLE_PROCESSES);
|
||||
}
|
||||
|
||||
execAsync(
|
||||
"npx",
|
||||
["electron", path.join("build", "electron.js")],
|
||||
true,
|
||||
cleanup
|
||||
);
|
||||
}
|
||||
|
||||
function spawnAndWaitUntil(cmd, cwd, predicate) {
|
||||
return new Promise((resolve) => {
|
||||
console.log(">", ...cmd);
|
||||
|
||||
const s = spawn(cmd[0], cmd.slice(1), {
|
||||
cwd,
|
||||
env: { ...process.env, NO_COLOR: "true" },
|
||||
shell: false
|
||||
});
|
||||
|
||||
RUNNING_PROCESSES.push(s);
|
||||
|
||||
s.stderr.pipe(process.stderr);
|
||||
s.stdout.on("data", (data) => {
|
||||
process.stdout.write(data);
|
||||
if (predicate(data)) resolve(undefined); //
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function exec(cmd) {
|
||||
try {
|
||||
console.log(">", cmd);
|
||||
|
||||
return execSync(cmd, {
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
shell: false
|
||||
});
|
||||
} catch {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
function execAsync(cmd, args, restartable, onExit) {
|
||||
try {
|
||||
console.log(">", cmd, ...args);
|
||||
|
||||
const proc = spawn(cmd, args, {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
shell: false
|
||||
});
|
||||
|
||||
const array = restartable ? RESTARTABLE_PROCESSES : RUNNING_PROCESSES;
|
||||
array.push(proc);
|
||||
proc.on("exit", (code, signal) => {
|
||||
console.log(cmd, ...args, "closed with code", code);
|
||||
if (code === 0 && !signal) {
|
||||
array.splice(array.indexOf(proc), 1);
|
||||
onExit && onExit();
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function killProcesses(processes) {
|
||||
for (const process of processes.slice()) {
|
||||
processes.splice(processes.indexOf(process), 1);
|
||||
await new Promise((resolve, reject) =>
|
||||
treekill(process.pid, (err) => (err ? reject(err) : resolve()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanup() {
|
||||
console.log("Cleaning up");
|
||||
|
||||
await killProcesses(RESTARTABLE_PROCESSES);
|
||||
await killProcesses(RUNNING_PROCESSES);
|
||||
await sleep(1000);
|
||||
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
process.exit();
|
||||
}
|
||||
|
||||
async function isBundleSame() {
|
||||
const bundle = await fs.readFile(
|
||||
path.join(__dirname, "..", "build", "electron.js")
|
||||
);
|
||||
|
||||
const hashSum = crypto.createHash("sha256");
|
||||
hashSum.update(bundle);
|
||||
const hex = hashSum.digest("hex");
|
||||
|
||||
if (!lastBundleHash) {
|
||||
lastBundleHash = hex;
|
||||
return false;
|
||||
}
|
||||
if (hex === lastBundleHash) return true;
|
||||
lastBundleHash = hex;
|
||||
return false;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import { watch } from "turbowatch";
|
||||
import { execSync, spawn } from "child_process";
|
||||
|
||||
const sodiumNativePrebuildPath = path.join(
|
||||
`node_modules`,
|
||||
`@notesnook`,
|
||||
`crypto`,
|
||||
`node_modules`,
|
||||
`@notesnook`,
|
||||
`sodium`,
|
||||
`node_modules`,
|
||||
`sodium-native`,
|
||||
`prebuilds`
|
||||
);
|
||||
let isServerRunning = false;
|
||||
|
||||
async function main() {
|
||||
const { shutdown } = await watch({
|
||||
project: path.join(__dirname, ".."),
|
||||
cwd: path.join(__dirname, ".."),
|
||||
triggers: [
|
||||
{
|
||||
expression: [
|
||||
"allof",
|
||||
["not", ["dirname", "node_modules"]],
|
||||
["match", "*.ts", "basename"]
|
||||
],
|
||||
name: "dev",
|
||||
// retry: { retries: 0 },
|
||||
onTeardown: async () => {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
},
|
||||
onChange: async ({ first }) => {
|
||||
if (first) {
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
}
|
||||
|
||||
await exec(`npm run bundle`);
|
||||
await exec(`npx tsc`);
|
||||
|
||||
if (first) {
|
||||
await fs.cp(sodiumNativePrebuildPath, "build/prebuilds", {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!isServerRunning) {
|
||||
await spawnAndWaitUntil(
|
||||
["npm", "run", "start:desktop"],
|
||||
path.join(__dirname, "..", "..", "web"),
|
||||
(data) => data.includes("Network: use --host to expose")
|
||||
);
|
||||
isServerRunning = true;
|
||||
}
|
||||
|
||||
await exec(`npx electron ${path.join("build", "electron.js")}`);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// SIGINT is the signal sent when we press Ctrl+C
|
||||
process.once("SIGINT", () => {
|
||||
void shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
function spawnAndWaitUntil(
|
||||
cmd: string[],
|
||||
cwd: string,
|
||||
predicate: (data: string) => boolean
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
const s = spawn(cmd[0], cmd.slice(1), {
|
||||
cwd,
|
||||
env: { ...process.env, NO_COLOR: "true" }
|
||||
});
|
||||
|
||||
process.once("beforeExit", () => {
|
||||
s.kill(-1);
|
||||
});
|
||||
|
||||
s.stdout.on("data", (data) => {
|
||||
if (predicate(data)) resolve(undefined); //
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function exec(cmd) {
|
||||
return execSync(cmd, {
|
||||
env: process.env,
|
||||
stdio: "inherit"
|
||||
});
|
||||
}
|
||||
60
apps/desktop/sign.js
Normal file
60
apps/desktop/sign.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const { writeFileSync, rmSync, readFileSync } = require("fs");
|
||||
const { execSync } = require("child_process");
|
||||
const { relative, join } = require("path");
|
||||
|
||||
module.exports = async function (configuration) {
|
||||
const Endpoint = "https://weu.codesigning.azure.net";
|
||||
const CodeSigningAccountName = "Notesnook";
|
||||
const CertificateProfileName = "Notesnook";
|
||||
const FileDigest = configuration.hash.toUpperCase();
|
||||
const TimestampRfc3161 = "http://timestamp.acs.microsoft.com";
|
||||
const TimestampDigest = configuration.hash.toUpperCase();
|
||||
const Description = "The Notesnook app";
|
||||
const DescriptionUrl = "https://notesnook.com/";
|
||||
const FilesCatalog = createCatalog(configuration.path);
|
||||
|
||||
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
|
||||
|
||||
console.debug("Signing", configuration.path, "using command", command);
|
||||
|
||||
psexec(command);
|
||||
|
||||
console.debug("Signed", configuration.path);
|
||||
|
||||
deleteCatalog();
|
||||
};
|
||||
|
||||
function createCatalog(path) {
|
||||
writeFileSync("_catalog", relative(__dirname, path));
|
||||
return join(__dirname, "_catalog");
|
||||
}
|
||||
|
||||
function deleteCatalog() {
|
||||
rmSync("_catalog");
|
||||
}
|
||||
|
||||
function psexec(cmd) {
|
||||
return execSync(cmd, {
|
||||
env: process.env,
|
||||
stdio: "inherit",
|
||||
shell: "pwsh"
|
||||
});
|
||||
}
|
||||
@@ -25,10 +25,12 @@ import { config, DesktopIntegration } from "../utils/config";
|
||||
import { bringToFront } from "../utils/bring-to-front";
|
||||
import { getTheme, setTheme, Theme } from "../utils/theme";
|
||||
import { mkdirSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { platform } from "os";
|
||||
import { dirname } from "path";
|
||||
import { resolvePath } from "../utils/resolve-path";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { AssetManager } from "../utils/asset-manager";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||
|
||||
const t = initTRPC.create();
|
||||
|
||||
@@ -44,6 +46,8 @@ const NotificationOptions = z.object({
|
||||
});
|
||||
|
||||
export const osIntegrationRouter = t.router({
|
||||
isFlatpak: t.procedure.query(() => isFlatpak()),
|
||||
|
||||
zoomFactor: t.procedure.query(() => config.zoomFactor),
|
||||
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
|
||||
globalThis.window?.webContents.setZoomFactor(factor);
|
||||
@@ -52,21 +56,21 @@ export const osIntegrationRouter = t.router({
|
||||
|
||||
privacyMode: t.procedure.query(() => config.privacyMode),
|
||||
setPrivacyMode: t.procedure
|
||||
.input(z.boolean().optional())
|
||||
.mutation(({ input: privacyMode }) => {
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
if (!globalThis.window || !["win32", "darwin"].includes(process.platform))
|
||||
return;
|
||||
|
||||
globalThis.window.setContentProtection(!!privacyMode);
|
||||
globalThis.window.setContentProtection(enabled);
|
||||
|
||||
if (process.platform === "win32") {
|
||||
globalThis.window.setThumbnailClip(
|
||||
privacyMode
|
||||
enabled
|
||||
? { x: 0, y: 0, width: 1, height: 1 }
|
||||
: { x: 0, y: 0, width: 0, height: 0 }
|
||||
);
|
||||
}
|
||||
config.privacyMode = !!privacyMode;
|
||||
config.privacyMode = enabled;
|
||||
}),
|
||||
|
||||
desktopIntegration: t.procedure.query(() => config.desktopSettings),
|
||||
@@ -79,6 +83,7 @@ export const osIntegrationRouter = t.router({
|
||||
AutoLaunch.disable();
|
||||
}
|
||||
config.desktopSettings = settings;
|
||||
setupDesktopIntegration(settings);
|
||||
}),
|
||||
|
||||
selectDirectory: t.procedure
|
||||
@@ -117,15 +122,23 @@ export const osIntegrationRouter = t.router({
|
||||
writeFileSync(resolvedPath, data);
|
||||
}),
|
||||
|
||||
resolvePath: t.procedure
|
||||
.input(z.object({ filePath: z.string() }))
|
||||
.query(({ input }) => {
|
||||
const { filePath } = input;
|
||||
if (!filePath) return;
|
||||
return resolvePath(filePath);
|
||||
}),
|
||||
|
||||
showNotification: t.procedure
|
||||
.input(NotificationOptions)
|
||||
.query(({ input }) => {
|
||||
const notification = new Notification({
|
||||
...input,
|
||||
icon: join(
|
||||
__dirname,
|
||||
platform() === "win32" ? "app.ico" : "favicon-72x72.png"
|
||||
)
|
||||
icon: AssetManager.appIcon({
|
||||
size: 64,
|
||||
format: process.platform === "win32" ? "ico" : "png"
|
||||
})
|
||||
});
|
||||
notification.show();
|
||||
if (input.urgency === "critical") {
|
||||
|
||||
@@ -114,9 +114,9 @@ export const spellCheckerRouter = t.router({
|
||||
globalThis.window?.webContents.session.setSpellCheckerLanguages(languages)
|
||||
),
|
||||
toggle: t.procedure
|
||||
.input(z.boolean().optional())
|
||||
.mutation(({ input: enabled }) => {
|
||||
globalThis.window?.webContents.session.setSpellCheckerEnabled(!!enabled);
|
||||
config.isSpellCheckerEnabled = !!enabled;
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
|
||||
config.isSpellCheckerEnabled = enabled;
|
||||
})
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ import { initTRPC } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { CancellationToken, autoUpdater } from "electron-updater";
|
||||
import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
|
||||
import { z } from "zod";
|
||||
import { config } from "../utils/config";
|
||||
|
||||
type UpdateInfo = { version: string };
|
||||
type Progress = { percent: number };
|
||||
@@ -28,6 +30,7 @@ type Progress = { percent: number };
|
||||
const t = initTRPC.create();
|
||||
|
||||
export const updaterRouter = t.router({
|
||||
autoUpdates: t.procedure.query(() => config.automaticUpdates),
|
||||
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
|
||||
download: t.procedure.query(async () => {
|
||||
const cancellationToken = new CancellationToken();
|
||||
@@ -37,6 +40,12 @@ export const updaterRouter = t.router({
|
||||
await autoUpdater.checkForUpdates();
|
||||
}),
|
||||
|
||||
toggleAutoUpdates: t.procedure
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
config.automaticUpdates = enabled;
|
||||
}),
|
||||
|
||||
onChecking: createSubscription("checking-for-update"),
|
||||
onDownloaded: createSubscription<"update-downloaded", UpdateInfo>(
|
||||
"update-downloaded"
|
||||
|
||||
@@ -27,19 +27,23 @@ export type CLIOptions = {
|
||||
hidden: boolean;
|
||||
};
|
||||
|
||||
export async function parseArguments(): Promise<CLIOptions> {
|
||||
export async function parseArguments(argv: string[]): Promise<CLIOptions> {
|
||||
const result: CLIOptions = {
|
||||
note: false,
|
||||
notebook: false,
|
||||
reminder: false,
|
||||
hidden: false
|
||||
};
|
||||
const { hidden } = await yargs(hideBin(process.argv))
|
||||
const { hidden } = await yargs(hideBin(argv))
|
||||
.boolean("hidden")
|
||||
// have to account for this flag added on Windows when launching
|
||||
// via Jumplist
|
||||
.boolean("allow-file-access-from-files")
|
||||
.command("new", "Create a new item", (yargs) => {
|
||||
return yargs
|
||||
.command("note", "Create a new note", {}, () => {
|
||||
result.note = true;
|
||||
console.log("HERE!");
|
||||
})
|
||||
.command("notebook", "Create a new notebook", {}, () => {
|
||||
result.notebook = true;
|
||||
|
||||
@@ -16,7 +16,6 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/* global MAC_APP_STORE, RELEASE */
|
||||
|
||||
import { app, BrowserWindow, nativeTheme, shell } from "electron";
|
||||
import { isDevelopment } from "./utils";
|
||||
@@ -25,7 +24,6 @@ import { configureAutoUpdater } from "./utils/autoupdater";
|
||||
import { getBackgroundColor, getTheme, setTheme } from "./utils/theme";
|
||||
import { setupMenu } from "./utils/menu";
|
||||
import { WindowState } from "./utils/window-state";
|
||||
import { AutoLaunch } from "./utils/autolaunch";
|
||||
import { setupJumplist } from "./utils/jumplist";
|
||||
import { setupTray } from "./utils/tray";
|
||||
import { CLIOptions, parseArguments } from "./cli";
|
||||
@@ -34,9 +32,14 @@ import { createIPCHandler } from "electron-trpc/main";
|
||||
import { router, api } from "./api";
|
||||
import { config } from "./utils/config";
|
||||
import path from "path";
|
||||
import { bringToFront } from "./utils/bring-to-front";
|
||||
import { bridge } from "./api/bridge";
|
||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||
|
||||
if (!RELEASE) {
|
||||
require("electron-reloader")(module);
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
console.log("Another instance is already running!");
|
||||
app.exit();
|
||||
}
|
||||
|
||||
if (process.platform == "win32" && process.env.PORTABLE_EXECUTABLE_DIR) {
|
||||
@@ -46,77 +49,80 @@ if (process.platform == "win32" && process.env.PORTABLE_EXECUTABLE_DIR) {
|
||||
app.setPath("documents", path.join(root, "Documents"));
|
||||
app.setPath("userData", path.join(root, "UserData"));
|
||||
}
|
||||
// only run a single instance
|
||||
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
app.exit();
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
app.setAppUserModelId(app.name);
|
||||
}
|
||||
|
||||
async function createWindow() {
|
||||
await AssetManager.loadIcons();
|
||||
const cliOptions = await parseArguments();
|
||||
process.on("uncaughtException", (error) => {
|
||||
console.error("uncaughtException:", error);
|
||||
});
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
console.error("unhandledRejection:", reason);
|
||||
});
|
||||
|
||||
const mainWindowState = new WindowState({});
|
||||
app.commandLine.appendSwitch("lang", "en-US");
|
||||
|
||||
async function createWindow() {
|
||||
const cliOptions = await parseArguments(process.argv);
|
||||
setTheme(getTheme());
|
||||
|
||||
const mainWindowState = new WindowState({});
|
||||
const mainWindow = new BrowserWindow({
|
||||
show: false,
|
||||
show: !cliOptions.hidden,
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
darkTheme: getTheme() === "dark",
|
||||
backgroundColor: getBackgroundColor(),
|
||||
|
||||
autoHideMenuBar: true,
|
||||
opacity: 0,
|
||||
autoHideMenuBar: false,
|
||||
icon: AssetManager.appIcon({
|
||||
size: 512,
|
||||
format: process.platform === "win32" ? "ico" : "png"
|
||||
}),
|
||||
webPreferences: {
|
||||
zoomFactor: config.zoomFactor,
|
||||
devTools: true, // isDev,
|
||||
nodeIntegration: true, //true,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
spellcheck: config.isSpellCheckerEnabled,
|
||||
preload: __dirname + "/preload.js"
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on("show", async () => {
|
||||
if (config.privacyMode) {
|
||||
await api.integration.setPrivacyMode(config.privacyMode);
|
||||
}
|
||||
});
|
||||
|
||||
mainWindowState.manage(mainWindow);
|
||||
globalThis.window = mainWindow;
|
||||
setupMenu();
|
||||
setupJumplist();
|
||||
setupDesktopIntegration(cliOptions.hidden);
|
||||
createIPCHandler({ router, windows: [mainWindow] });
|
||||
globalThis.window = mainWindow;
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
mainWindowState.manage(mainWindow);
|
||||
|
||||
if (cliOptions.hidden && !config.desktopSettings.minimizeToSystemTray)
|
||||
mainWindow.minimize();
|
||||
|
||||
await mainWindow.webContents.loadURL(`${createURL(cliOptions, "/")}`);
|
||||
mainWindow.setOpacity(1);
|
||||
|
||||
if (config.privacyMode) {
|
||||
await api.integration.setPrivacyMode({ enabled: config.privacyMode });
|
||||
}
|
||||
|
||||
await AssetManager.loadIcons();
|
||||
setupDesktopIntegration(config.desktopSettings);
|
||||
|
||||
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
||||
"http://dictionaries.notesnook.com/"
|
||||
);
|
||||
|
||||
if (isDevelopment())
|
||||
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
|
||||
|
||||
try {
|
||||
await mainWindow.loadURL(`${createURL(cliOptions)}`);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) console.error(e);
|
||||
}
|
||||
|
||||
mainWindow.on("closed", () => {
|
||||
mainWindow.once("closed", () => {
|
||||
globalThis.window = null;
|
||||
});
|
||||
|
||||
setupMenu();
|
||||
setupJumplist();
|
||||
|
||||
if (isDevelopment())
|
||||
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url);
|
||||
return { action: "deny" };
|
||||
@@ -128,8 +134,7 @@ async function createWindow() {
|
||||
});
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch("lang", "en-US");
|
||||
app.on("ready", async () => {
|
||||
app.once("ready", async () => {
|
||||
console.info("App ready. Opening window.");
|
||||
|
||||
if (!isDevelopment()) registerProtocol();
|
||||
@@ -137,21 +142,31 @@ app.on("ready", async () => {
|
||||
configureAutoUpdater();
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
app.once("window-all-closed", () => {
|
||||
if (process.platform !== "darwin" || MAC_APP_STORE) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("second-instance", async (_ev, argv) => {
|
||||
if (!globalThis.window) return;
|
||||
const cliOptions = await parseArguments(argv);
|
||||
if (cliOptions.note) bridge.onCreateItem("note");
|
||||
if (cliOptions.notebook) bridge.onCreateItem("notebook");
|
||||
if (cliOptions.reminder) bridge.onCreateItem("reminder");
|
||||
bringToFront();
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (globalThis.window === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
function createURL(options: CLIOptions) {
|
||||
function createURL(options: CLIOptions, path = "/") {
|
||||
const url = new URL(isDevelopment() ? "http://localhost:3000" : PROTOCOL_URL);
|
||||
|
||||
url.pathname = path;
|
||||
if (options.note === true) url.hash = "/notes/create/1";
|
||||
else if (options.notebook === true) url.hash = "/notebooks/create";
|
||||
else if (options.reminder === true) url.hash = "/reminders/create";
|
||||
@@ -162,60 +177,3 @@ function createURL(options: CLIOptions) {
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function setupDesktopIntegration(hidden: boolean) {
|
||||
const desktopIntegration = config.desktopSettings;
|
||||
|
||||
if (
|
||||
desktopIntegration.closeToSystemTray ||
|
||||
desktopIntegration.minimizeToSystemTray
|
||||
) {
|
||||
setupTray();
|
||||
}
|
||||
|
||||
globalThis.window?.on("ready-to-show", () => {
|
||||
if (!hidden) {
|
||||
globalThis.window?.show();
|
||||
}
|
||||
|
||||
if (hidden && !desktopIntegration.minimizeToSystemTray) {
|
||||
globalThis.window?.show();
|
||||
globalThis.window?.minimize();
|
||||
}
|
||||
});
|
||||
|
||||
// when close to system tray is enabled, it becomes nigh impossible
|
||||
// to "quit" the app. This is necessary in order to fix that.
|
||||
if (desktopIntegration.closeToSystemTray) {
|
||||
app.on("before-quit", () => app.exit(0));
|
||||
}
|
||||
|
||||
globalThis.window?.on("close", (e) => {
|
||||
if (config.desktopSettings.closeToSystemTray) {
|
||||
e.preventDefault();
|
||||
if (process.platform == "darwin") {
|
||||
// on macOS window cannot be minimized/hidden if it is already fullscreen
|
||||
// so we just close it.
|
||||
if (globalThis.window?.isFullScreen()) app.exit(0);
|
||||
else app.hide();
|
||||
} else {
|
||||
globalThis.window?.minimize();
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.window?.on("minimize", () => {
|
||||
if (config.desktopSettings.minimizeToSystemTray) {
|
||||
if (process.platform == "darwin") {
|
||||
app.hide();
|
||||
} else {
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (desktopIntegration.autoStart) {
|
||||
AutoLaunch.enable(!!desktopIntegration.startMinimized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ declare global {
|
||||
}
|
||||
|
||||
process.once("loaded", async () => {
|
||||
console.log("HELLO!");
|
||||
const electronTRPC: RendererGlobalElectronTRPC = {
|
||||
sendMessage: (operation) =>
|
||||
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),
|
||||
|
||||
@@ -21,8 +21,8 @@ import { NativeImage, nativeImage } from "electron";
|
||||
import path from "path";
|
||||
import { isDevelopment } from "./index";
|
||||
import { parse, ParsedImage } from "icojs";
|
||||
import { readFileSync } from "fs";
|
||||
import { getSystemTheme } from "./theme";
|
||||
import { readFile } from "fs/promises";
|
||||
|
||||
type Formats = "ico" | "png" | "icns";
|
||||
type IconOptions<TFormat extends Formats> = {
|
||||
@@ -70,7 +70,7 @@ export class AssetManager {
|
||||
"icons",
|
||||
`${icon}${prefix}.ico`
|
||||
);
|
||||
const icoBuffer = readFileSync(icoPath);
|
||||
const icoBuffer = await readFile(icoPath);
|
||||
const images = await parse(icoBuffer, "image/png");
|
||||
ALL_ICONS.push({ id: icon, images, prefix });
|
||||
}
|
||||
@@ -80,10 +80,10 @@ export class AssetManager {
|
||||
static appIcon(options: IconOptions<Formats>) {
|
||||
const { size = 32, format = "png" } = options;
|
||||
|
||||
if (format === "ico") return path.join("assets", "icons", "app.ico");
|
||||
if (format === "icns") return path.join("assets", "icons", "app.icns");
|
||||
if (format === "ico") return "assets\\icons\\app.ico";
|
||||
if (format === "icns") return "assets/icons/app.icns";
|
||||
|
||||
return path.join("assets", "icons", `${size}x${size}.png`);
|
||||
return `assets/icons/${size}x${size}.png`;
|
||||
}
|
||||
|
||||
static icon<TFormat extends Formats>(
|
||||
|
||||
@@ -35,6 +35,8 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
|
||||
"autostart"
|
||||
);
|
||||
|
||||
const STARTUP_ARGS = ["--hidden"];
|
||||
|
||||
export class AutoLaunch {
|
||||
static enable(hidden: boolean) {
|
||||
if (process.platform === "linux") {
|
||||
@@ -47,10 +49,14 @@ export class AutoLaunch {
|
||||
LINUX_DESKTOP_ENTRY(hidden)
|
||||
);
|
||||
} else {
|
||||
const loginItemSettings = app.getLoginItemSettings({
|
||||
args: STARTUP_ARGS
|
||||
});
|
||||
if (loginItemSettings.openAtLogin) return;
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
openAsHidden: hidden,
|
||||
args: ["--hidden"]
|
||||
args: STARTUP_ARGS
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { config } from "./config";
|
||||
|
||||
async function configureAutoUpdater() {
|
||||
autoUpdater.setFeedURL({
|
||||
@@ -26,7 +27,7 @@ async function configureAutoUpdater() {
|
||||
useMultipleRangeRequest: false
|
||||
});
|
||||
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.autoDownload = config.automaticUpdates;
|
||||
autoUpdater.allowDowngrade = false;
|
||||
autoUpdater.allowPrerelease = false;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
@@ -38,9 +38,10 @@ export const config = {
|
||||
closeToSystemTray: false
|
||||
},
|
||||
privacyMode: false,
|
||||
isSpellCheckerEnabled: false,
|
||||
zoomFactor: 0,
|
||||
theme: nativeTheme.themeSource
|
||||
isSpellCheckerEnabled: true,
|
||||
zoomFactor: 1,
|
||||
theme: nativeTheme.themeSource,
|
||||
automaticUpdates: true
|
||||
};
|
||||
|
||||
type ConfigKey = keyof typeof config;
|
||||
|
||||
74
apps/desktop/src/utils/desktop-integration.ts
Normal file
74
apps/desktop/src/utils/desktop-integration.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { app } from "electron";
|
||||
import { DesktopIntegration, config } from "./config";
|
||||
import { setupTray, destroyTray } from "./tray";
|
||||
import { AutoLaunch } from "./autolaunch";
|
||||
|
||||
export function setupDesktopIntegration(
|
||||
desktopIntegration: DesktopIntegration
|
||||
) {
|
||||
if (
|
||||
desktopIntegration.closeToSystemTray ||
|
||||
desktopIntegration.minimizeToSystemTray
|
||||
) {
|
||||
setupTray();
|
||||
} else {
|
||||
destroyTray();
|
||||
}
|
||||
|
||||
// when close to system tray is enabled, it becomes nigh impossible
|
||||
// to "quit" the app. This is necessary in order to fix that.
|
||||
app.on("before-quit", () =>
|
||||
desktopIntegration.closeToSystemTray ? app.exit(0) : null
|
||||
);
|
||||
|
||||
globalThis.window?.on("close", (e) => {
|
||||
if (config.desktopSettings.closeToSystemTray) {
|
||||
e.preventDefault();
|
||||
if (process.platform == "darwin") {
|
||||
// on macOS window cannot be minimized/hidden if it is already fullscreen
|
||||
// so we just close it.
|
||||
if (globalThis.window?.isFullScreen()) app.exit(0);
|
||||
else app.hide();
|
||||
} else {
|
||||
try {
|
||||
globalThis.window?.minimize();
|
||||
globalThis.window?.hide();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.window?.on("minimize", () => {
|
||||
if (config.desktopSettings.minimizeToSystemTray) {
|
||||
if (process.platform == "darwin") {
|
||||
app.hide();
|
||||
} else {
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (desktopIntegration.autoStart) {
|
||||
AutoLaunch.enable(!!desktopIntegration.startMinimized);
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { join } from "path";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
export const APP_ICON_PATH = join(
|
||||
__dirname,
|
||||
process.platform === "win32" ? "app.ico" : "favicon-72x72.png"
|
||||
);
|
||||
|
||||
export function isDevelopment() {
|
||||
return process.env.ELECTRON_IS_DEV
|
||||
? Number.parseInt(process.env.ELECTRON_IS_DEV, 10) === 1
|
||||
|
||||
@@ -101,6 +101,26 @@ function setupMenu() {
|
||||
})
|
||||
);
|
||||
|
||||
if (params.linkURL?.length) {
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: "Copy link",
|
||||
click() {
|
||||
clipboard.writeText(params.linkURL);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: "Copy link text",
|
||||
click() {
|
||||
clipboard.writeText(params.linkText);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (params.selectionText.length) {
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
|
||||
@@ -17,16 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { protocol, ProtocolRequest } from "electron";
|
||||
import { protocol } from "electron";
|
||||
import { isDevelopment } from "./index";
|
||||
import { createReadStream, statSync } from "fs";
|
||||
import { extname, join, normalize } from "path";
|
||||
import { createReadStream } from "fs";
|
||||
import { extname, normalize } from "path";
|
||||
import { URL } from "url";
|
||||
import fetch, { Response } from "node-fetch";
|
||||
|
||||
const BASE_PATH = isDevelopment() ? "../public" : "";
|
||||
const HOSTNAME = `app.notesnook.com`;
|
||||
const FILE_NOT_FOUND = -6;
|
||||
const SCHEME = "https";
|
||||
const extensionToMimeType: Record<string, string> = {
|
||||
html: "text/html",
|
||||
@@ -34,102 +32,59 @@ const extensionToMimeType: Record<string, string> = {
|
||||
js: "application/javascript",
|
||||
css: "text/css",
|
||||
svg: "image/svg+xml",
|
||||
png: "image/png"
|
||||
png: "image/png",
|
||||
jpg: "image/jpg",
|
||||
ttf: "font/ttf",
|
||||
woff: "font/woff",
|
||||
woff2: "font/woff2"
|
||||
};
|
||||
|
||||
function registerProtocol() {
|
||||
const protocolInterceptionResult = protocol.interceptStreamProtocol(
|
||||
SCHEME,
|
||||
async (request, callback) => {
|
||||
const url = new URL(request.url);
|
||||
if (shouldInterceptRequest(url)) {
|
||||
console.info("Intercepting request:", request.url);
|
||||
|
||||
const loadIndex = !extname(url.pathname);
|
||||
const absoluteFilePath = normalize(
|
||||
`${__dirname}${
|
||||
loadIndex
|
||||
? `${BASE_PATH}/index.html`
|
||||
: `${BASE_PATH}/${url.pathname}`
|
||||
}`
|
||||
);
|
||||
const filePath = getPath(absoluteFilePath);
|
||||
if (!filePath) {
|
||||
console.error("Local asset file not found at", filePath);
|
||||
callback({ error: FILE_NOT_FOUND });
|
||||
return;
|
||||
}
|
||||
const fileExtension = extname(filePath).replace(".", "");
|
||||
|
||||
const data = createReadStream(filePath);
|
||||
callback({
|
||||
data,
|
||||
mimeType: extensionToMimeType[fileExtension]
|
||||
});
|
||||
} else {
|
||||
let response: Response;
|
||||
try {
|
||||
const body = await getBody(request);
|
||||
response = await fetch(request.url, {
|
||||
...request,
|
||||
body,
|
||||
headers: {
|
||||
...request.headers
|
||||
// origin: `${PROTOCOL}://${HOSTNAME}/`
|
||||
},
|
||||
referrer: request.referrer,
|
||||
redirect: "manual"
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.error(`Error sending request to `, request.url, "Error: ", e);
|
||||
callback({ statusCode: 400 });
|
||||
return;
|
||||
}
|
||||
callback({
|
||||
statusCode: response.status,
|
||||
data: response.body || undefined,
|
||||
headers: Object.fromEntries(response.headers.entries()),
|
||||
mimeType: response.headers.get("Content-Type") || undefined
|
||||
protocol.handle(SCHEME, async (request) => {
|
||||
const url = new URL(request.url);
|
||||
if (shouldInterceptRequest(url)) {
|
||||
console.info("Intercepting request:", request.url);
|
||||
const loadIndex = !extname(url.pathname);
|
||||
const filePath = normalize(
|
||||
`${__dirname}${
|
||||
loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
|
||||
}`
|
||||
);
|
||||
if (!filePath) {
|
||||
console.error("Local asset file not found at", filePath);
|
||||
return new Response(undefined, {
|
||||
status: 404,
|
||||
statusText: "FILE_NOT_FOUND"
|
||||
});
|
||||
}
|
||||
const fileExtension = extname(filePath).replace(".", "");
|
||||
return new Response(createReadStream(filePath), {
|
||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
});
|
||||
} else {
|
||||
if (request.headers.has("X-Content-Length")) {
|
||||
request.headers.set(
|
||||
"Content-Length",
|
||||
request.headers.get("X-Content-Length") || "0"
|
||||
);
|
||||
request.headers.delete("X-Content-Length");
|
||||
}
|
||||
const headers = Object.fromEntries(request.headers.entries());
|
||||
|
||||
return await fetch(request.url, {
|
||||
signal: request.signal,
|
||||
mode: request.mode,
|
||||
headers,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
credentials: request.credentials,
|
||||
referrer: (request as any).referrer,
|
||||
duplex: "half",
|
||||
redirect: "manual"
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
console.info(
|
||||
`${SCHEME} protocol inteception ${
|
||||
protocolInterceptionResult ? "successful" : "failed"
|
||||
}.`
|
||||
);
|
||||
|
||||
// protocol.handle(SCHEME, (request) => {
|
||||
// const url = new URL(request.url);
|
||||
// if (shouldInterceptRequest(url)) {
|
||||
// console.info("Intercepting request:", request.url);
|
||||
// const loadIndex = !extname(url.pathname);
|
||||
// const absoluteFilePath = normalize(
|
||||
// `${__dirname}${
|
||||
// loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
|
||||
// }`
|
||||
// );
|
||||
// const filePath = getPath(absoluteFilePath);
|
||||
// if (!filePath) {
|
||||
// console.error("Local asset file not found at", filePath);
|
||||
// return new Response(undefined, {
|
||||
// status: 404,
|
||||
// statusText: "FILE_NOT_FOUND"
|
||||
// });
|
||||
// }
|
||||
// const fileExtension = extname(filePath).replace(".", "");
|
||||
// const data = createReadStream(filePath);
|
||||
// return new Response(data, {
|
||||
// headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
// });
|
||||
// } else {
|
||||
// return net.fetch(request);
|
||||
// }
|
||||
// });
|
||||
// console.info(`${SCHEME} protocol inteception "successful"`);
|
||||
});
|
||||
console.info(`${SCHEME} protocol inteception "successful"`);
|
||||
}
|
||||
|
||||
const bypassedRoutes: string[] = [];
|
||||
@@ -140,37 +95,3 @@ function shouldInterceptRequest(url: URL) {
|
||||
|
||||
const PROTOCOL_URL = `${SCHEME}://${HOSTNAME}/`;
|
||||
export { registerProtocol, PROTOCOL_URL };
|
||||
|
||||
async function getBody(request: ProtocolRequest) {
|
||||
const session = globalThis?.window?.webContents?.session;
|
||||
|
||||
const blobParts = [];
|
||||
if (!request.uploadData || !request.uploadData.length) return null;
|
||||
for (const data of request.uploadData) {
|
||||
if (data.bytes) {
|
||||
blobParts.push(new Uint8Array(data.bytes));
|
||||
} else if (session && data.blobUUID) {
|
||||
const buffer = await session.getBlobData(data.blobUUID);
|
||||
if (!buffer) continue;
|
||||
blobParts.push(new Uint8Array(buffer));
|
||||
}
|
||||
}
|
||||
const blob = new Blob(blobParts);
|
||||
return blob;
|
||||
}
|
||||
|
||||
function getPath(filePath: string): string | undefined {
|
||||
try {
|
||||
const result = statSync(filePath);
|
||||
|
||||
if (result.isFile()) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
if (result.isDirectory()) {
|
||||
return getPath(join(filePath, "index.html"));
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/mobile/.gitignore
vendored
1
apps/mobile/.gitignore
vendored
@@ -5,7 +5,6 @@ artifacts/
|
||||
.DS_Store
|
||||
|
||||
native/android/app/src/main/assets/
|
||||
|
||||
*Issues.md
|
||||
build_cache/
|
||||
#
|
||||
|
||||
@@ -16,8 +16,13 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import "@azure/core-asynciterator-polyfill";
|
||||
import {
|
||||
THEME_COMPATIBILITY_VERSION,
|
||||
useThemeEngineStore
|
||||
} from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
import { I18nManager, View } from "react-native";
|
||||
import "react-native-gesture-handler";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
@@ -26,12 +31,17 @@ import { withErrorBoundry } from "./components/exception-handler";
|
||||
import GlobalSafeAreaProvider from "./components/globalsafearea";
|
||||
import { useAppEvents } from "./hooks/use-app-events";
|
||||
import { ApplicationHolder } from "./navigation";
|
||||
import { themeTrpcClient } from "./screens/settings/theme-selector";
|
||||
import Notifications from "./services/notifications";
|
||||
import SettingsService from "./services/settings";
|
||||
import { TipManager } from "./services/tip-manager";
|
||||
import { useThemeStore } from "./stores/use-theme-store";
|
||||
import { useUserStore } from "./stores/use-user-store";
|
||||
|
||||
SettingsService.init();
|
||||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
I18nManager.swapLeftAndRightInRTL(false);
|
||||
|
||||
SettingsService.checkOrientation();
|
||||
const App = () => {
|
||||
const init = useAppEvents();
|
||||
@@ -40,6 +50,7 @@ const App = () => {
|
||||
if (appLockMode && appLockMode !== "none") {
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||
init();
|
||||
setTimeout(async () => {
|
||||
SettingsService.onFirstLaunch();
|
||||
@@ -87,4 +98,53 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundry(App, "App");
|
||||
let currTheme =
|
||||
useThemeStore.getState().colorScheme === "dark"
|
||||
? SettingsService.getProperty("darkTheme")
|
||||
: SettingsService.getProperty("lightTheme");
|
||||
useThemeEngineStore.getState().setTheme(currTheme);
|
||||
|
||||
export const withTheme = (Element) => {
|
||||
return function AppWithThemeProvider() {
|
||||
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
|
||||
state.colorScheme,
|
||||
state.darkTheme,
|
||||
state.lightTheme
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
const currentTheme = colorScheme === "dark" ? darkTheme : lightTheme;
|
||||
if (!currentTheme) return;
|
||||
themeTrpcClient.updateTheme
|
||||
.query({
|
||||
version: currentTheme.version,
|
||||
compatibilityVersion: THEME_COMPATIBILITY_VERSION,
|
||||
id: currentTheme.id
|
||||
})
|
||||
.then((theme) => {
|
||||
if (theme) {
|
||||
console.log(theme.version, "theme updated");
|
||||
theme.colorScheme === "dark"
|
||||
? useThemeStore.getState().setDarkTheme(theme)
|
||||
: useThemeStore.getState().setLightTheme(theme);
|
||||
}
|
||||
})
|
||||
.catch(console.log);
|
||||
}, 1000);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const nextTheme = colorScheme === "dark" ? darkTheme : lightTheme;
|
||||
if (JSON.stringify(nextTheme) !== JSON.stringify(currTheme)) {
|
||||
useThemeEngineStore
|
||||
.getState()
|
||||
.setTheme(colorScheme === "dark" ? darkTheme : lightTheme);
|
||||
currTheme = nextTheme;
|
||||
}
|
||||
|
||||
return <Element />;
|
||||
};
|
||||
};
|
||||
|
||||
export default withTheme(withErrorBoundry(App, "App"));
|
||||
|
||||
@@ -126,6 +126,18 @@ export async function decrypt(password, data) {
|
||||
return await Sodium.decrypt(password, _data);
|
||||
}
|
||||
|
||||
export async function decryptMulti(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
|
||||
data = data.map((d) => {
|
||||
d.output = "plain";
|
||||
return d;
|
||||
});
|
||||
return await Sodium.decryptMulti(password, data);
|
||||
}
|
||||
|
||||
export function parseAlgorithm(alg) {
|
||||
if (!alg) return {};
|
||||
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
|
||||
@@ -154,3 +166,24 @@ export async function encrypt(password, data) {
|
||||
alg: getAlgorithm(7)
|
||||
};
|
||||
}
|
||||
|
||||
export async function encryptMulti(password, data) {
|
||||
if (!password.password && !password.key) return undefined;
|
||||
if (password.password && password.password === "" && !password.key)
|
||||
return undefined;
|
||||
|
||||
let results = await Sodium.encryptMulti(
|
||||
password,
|
||||
data.map((item) => ({
|
||||
type: "plain",
|
||||
data: item
|
||||
}))
|
||||
);
|
||||
|
||||
return !results
|
||||
? []
|
||||
: results.map((result) => ({
|
||||
...result,
|
||||
alg: getAlgorithm(7)
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -17,18 +17,37 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { database } from "@notesnook/common";
|
||||
import { initalize, logger as dbLogger } from "@notesnook/core/logger";
|
||||
import { logger as dbLogger } from "@notesnook/core/dist/logger";
|
||||
import { Platform } from "react-native";
|
||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||
import filesystem from "../filesystem";
|
||||
import * as Gzip from "react-native-gzip";
|
||||
import EventSource from "../../utils/sse/even-source-ios";
|
||||
import AndroidEventSource from "../../utils/sse/event-source";
|
||||
import Storage, { KV } from "./storage";
|
||||
import * as Gzip from "react-native-gzip";
|
||||
import filesystem from "../filesystem";
|
||||
import "./logger";
|
||||
import Storage from "./storage";
|
||||
|
||||
const LoggerStorage = new MMKVLoader()
|
||||
.withInstanceID("notesnook_logs")
|
||||
.initialize();
|
||||
database.host(
|
||||
__DEV__
|
||||
? {
|
||||
API_HOST: "https://api.notesnook.com",
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||
// API_HOST: "http://192.168.43.108:5264",
|
||||
// AUTH_HOST: "http://192.168.43.108:8264",
|
||||
// SSE_HOST: "http://192.168.43.108:7264",
|
||||
// SUBSCRIPTIONS_HOST: "http://192.168.43.108:9264",
|
||||
// ISSUES_HOST: "http://192.168.43.108:2624"
|
||||
}
|
||||
: {
|
||||
API_HOST: "https://api.notesnook.com",
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||
}
|
||||
);
|
||||
|
||||
database.setup(
|
||||
Storage,
|
||||
@@ -40,30 +59,5 @@ database.setup(
|
||||
}
|
||||
);
|
||||
|
||||
database.host(
|
||||
__DEV__
|
||||
? {
|
||||
API_HOST: "https://api.notesnook.com",
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||
// API_HOST: "http://192.168.8.101:5264",
|
||||
// AUTH_HOST: "http://192.168.8.101:8264",
|
||||
// SSE_HOST: "http://192.168.8.101:7264",
|
||||
// SUBSCRIPTIONS_HOST: "http://192.168.8.101:9264",
|
||||
// ISSUES_HOST: "http://192.168.8.101:2624"
|
||||
}
|
||||
: {
|
||||
API_HOST: "https://api.notesnook.com",
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co"
|
||||
}
|
||||
);
|
||||
|
||||
initalize(new KV(LoggerStorage), true);
|
||||
|
||||
export const db = database;
|
||||
export const DatabaseLogger = dbLogger;
|
||||
|
||||
29
apps/mobile/app/common/database/logger.js
Normal file
29
apps/mobile/app/common/database/logger.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { initalize } from "@notesnook/core/dist/logger";
|
||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||
import { KV } from "./storage";
|
||||
|
||||
const LoggerStorage = new MMKVLoader()
|
||||
.withInstanceID("notesnook_logs")
|
||||
.initialize();
|
||||
|
||||
initalize(new KV(LoggerStorage));
|
||||
|
||||
export { LoggerStorage };
|
||||
@@ -18,12 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage";
|
||||
import { ProcessingModes, MMKVLoader } from "react-native-mmkv-storage";
|
||||
|
||||
export const MMKV = new MMKVStorage.Loader()
|
||||
export const MMKV = new MMKVLoader()
|
||||
.setProcessingMode(
|
||||
Platform.OS === "ios"
|
||||
? ProcessingModes.MULTI_PROCESS
|
||||
: ProcessingModes.SINGLE_PROCESS
|
||||
)
|
||||
.disableIndexing()
|
||||
.initialize();
|
||||
|
||||
@@ -27,11 +27,16 @@ import {
|
||||
getCryptoKey,
|
||||
getRandomBytes,
|
||||
hash,
|
||||
removeCryptoKey
|
||||
removeCryptoKey,
|
||||
decryptMulti,
|
||||
encryptMulti
|
||||
} from "./encryption";
|
||||
import { MMKV } from "./mmkv";
|
||||
|
||||
export class KV {
|
||||
/**
|
||||
* @type {typeof MMKV}
|
||||
*/
|
||||
storage = null;
|
||||
constructor(storage) {
|
||||
this.storage = storage;
|
||||
@@ -53,6 +58,7 @@ export class KV {
|
||||
key,
|
||||
typeof data === "string" ? data : JSON.stringify(data)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,27 +66,36 @@ export class KV {
|
||||
if (keys.length <= 0) {
|
||||
return [];
|
||||
} else {
|
||||
let data = await this.storage.getMultipleItemsAsync(keys.slice());
|
||||
|
||||
return data.map(([key, value]) => {
|
||||
let obj;
|
||||
try {
|
||||
obj = JSON.parse(value);
|
||||
} catch (e) {
|
||||
obj = value;
|
||||
}
|
||||
|
||||
return [key, obj];
|
||||
});
|
||||
try {
|
||||
let data = await this.storage.getMultipleItemsAsync(
|
||||
keys.slice(),
|
||||
"string"
|
||||
);
|
||||
return data.map(([key, value]) => {
|
||||
let obj;
|
||||
try {
|
||||
obj = JSON.parse(value);
|
||||
} catch (e) {
|
||||
obj = value;
|
||||
}
|
||||
return [key, obj];
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async remove(key) {
|
||||
return await this.storage.removeItem(key);
|
||||
return this.storage.removeItem(key);
|
||||
}
|
||||
|
||||
async removeMulti(keys) {
|
||||
return this.storage.removeItems(...keys);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
return await this.storage.clearStore();
|
||||
return this.storage.clearStore();
|
||||
}
|
||||
|
||||
async getAllKeys() {
|
||||
@@ -96,6 +111,10 @@ export class KV {
|
||||
);
|
||||
return keys;
|
||||
}
|
||||
|
||||
async writeMulti(items) {
|
||||
return this.storage.setMultipleItemsAsync(items, "object");
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultStorage = new KV(MMKV);
|
||||
@@ -129,8 +148,11 @@ export default {
|
||||
remove: (key) => DefaultStorage.remove(key),
|
||||
clear: () => DefaultStorage.clear(),
|
||||
getAllKeys: () => DefaultStorage.getAllKeys(),
|
||||
writeMulti: (items) => DefaultStorage.writeMulti(items),
|
||||
removeMulti: (keys) => DefaultStorage.removeMulti(keys),
|
||||
encrypt,
|
||||
decrypt,
|
||||
decryptMulti,
|
||||
getRandomBytes,
|
||||
checkAndCreateDir,
|
||||
requestPermission,
|
||||
@@ -138,5 +160,6 @@ export default {
|
||||
getCryptoKey,
|
||||
removeCryptoKey,
|
||||
hash,
|
||||
generateCryptoKey
|
||||
generateCryptoKey,
|
||||
encryptMulti
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
getFileNameWithExtension,
|
||||
isImage,
|
||||
isDocument
|
||||
} from "@notesnook/core/utils/filename";
|
||||
} from "@notesnook/core/dist/utils/filename";
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
@@ -34,6 +34,8 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
|
||||
export const FileDownloadStatus = {
|
||||
Success: 1,
|
||||
@@ -62,6 +64,7 @@ export async function downloadAttachments(
|
||||
canceled,
|
||||
groupId
|
||||
) {
|
||||
await createCacheDir();
|
||||
if (!attachments || !attachments.length) return;
|
||||
const result = new Map();
|
||||
|
||||
@@ -179,9 +182,13 @@ export default async function downloadAttachment(
|
||||
silent: false,
|
||||
cache: false,
|
||||
throwError: false,
|
||||
groupId: undefined
|
||||
groupId: undefined,
|
||||
base64: false,
|
||||
text: false
|
||||
}
|
||||
) {
|
||||
await createCacheDir();
|
||||
|
||||
let attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) {
|
||||
console.log("attachment not found");
|
||||
@@ -208,10 +215,17 @@ export default async function downloadAttachment(
|
||||
options.groupId || attachment.metadata.hash,
|
||||
attachment.metadata.hash
|
||||
);
|
||||
if (
|
||||
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
|
||||
)
|
||||
|
||||
if (!(await exists(attachment.metadata.hash))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.base64 || options.text) {
|
||||
return await db.attachments.read(
|
||||
attachment.metadata.hash,
|
||||
options.base64 ? "base64" : "text"
|
||||
);
|
||||
}
|
||||
|
||||
let filename = getFileNameWithExtension(
|
||||
attachment.metadata.filename,
|
||||
@@ -229,7 +243,8 @@ export default async function downloadAttachment(
|
||||
mime: attachment.metadata.type,
|
||||
fileName: options.cache ? undefined : filename,
|
||||
uri: options.cache ? undefined : folder.uri,
|
||||
chunkSize: attachment.chunkSize
|
||||
chunkSize: attachment.chunkSize,
|
||||
appGroupId: IOS_APPGROUPID
|
||||
};
|
||||
|
||||
let fileUri = await Sodium.decryptFile(
|
||||
|
||||
@@ -17,22 +17,25 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import hosts from "@notesnook/core/utils/constants";
|
||||
import hosts from "@notesnook/core/dist/utils/constants";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { cacheDir, fileCheck } from "./utils";
|
||||
import { createCacheDir, exists } from "./io";
|
||||
|
||||
export async function downloadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
let { url, headers } = data;
|
||||
|
||||
await createCacheDir();
|
||||
|
||||
let { url, headers } = data;
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
let exists = await RNFetchBlob.fs.exists(path);
|
||||
if (exists) {
|
||||
if (await exists(filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,44 +63,55 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
console.log("downloading: ", recieved, total);
|
||||
});
|
||||
|
||||
cancelToken.cancel = request.cancel;
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
request.cancel();
|
||||
};
|
||||
let response = await request;
|
||||
await fileCheck(response, totalSize);
|
||||
let status = response.info().status;
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
return status >= 200 && status < 300;
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
if (e.message !== "canceled") {
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "global"
|
||||
});
|
||||
ToastEvent.show({
|
||||
heading: "Error downloading file",
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log("download file error: ", e, url, headers);
|
||||
console.log("Download file error:", e, url, headers);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUploadedFileSize(hash) {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length")
|
||||
);
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAttachment(hash) {
|
||||
|
||||
@@ -20,31 +20,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Platform } from "react-native";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { cacheDir, getRandomId } from "./utils";
|
||||
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||
import { db } from "../database";
|
||||
import { compressToBase64 } from "./compress";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
|
||||
export async function readEncrypted(filename, key, cipherData) {
|
||||
await migrateFilesFromCache();
|
||||
console.log("Read encrypted file...");
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
try {
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
let exists =
|
||||
(await RNFetchBlob.fs.exists(path)) ||
|
||||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
|
||||
|
||||
if (!exists) {
|
||||
if (!(await exists(filename))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const attachment = db.attachments.attachment(filename);
|
||||
const isPng = /(png)/g.test(attachment?.metadata.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
|
||||
const isPng = !attachment.metadata.type
|
||||
? false
|
||||
: /(png)/g.test(attachment?.metadata.type);
|
||||
const isJpeg = !attachment.metadata.type
|
||||
? false
|
||||
: /(jpeg|jpg)/g.test(attachment?.metadata.type);
|
||||
|
||||
let output = await Sodium.decryptFile(
|
||||
key,
|
||||
@@ -59,6 +56,7 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
: "base64"
|
||||
: "text"
|
||||
);
|
||||
console.log("file decrypted...");
|
||||
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
|
||||
const dCachePath = `${cacheDir}/${output}`;
|
||||
output = await compressToBase64(
|
||||
@@ -70,7 +68,7 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
return output;
|
||||
} catch (e) {
|
||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||
console.log(e);
|
||||
console.log("readEncrypted", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -88,6 +86,7 @@ export async function hashBase64(data) {
|
||||
}
|
||||
|
||||
export async function writeEncryptedBase64({ data, key }) {
|
||||
await createCacheDir();
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
let output = await Sodium.encryptFile(key, {
|
||||
@@ -103,6 +102,7 @@ export async function writeEncryptedBase64({ data, key }) {
|
||||
}
|
||||
|
||||
export async function deleteFile(filename, data) {
|
||||
await createCacheDir();
|
||||
let delFilePath = cacheDir + `/${filename}`;
|
||||
if (!data) {
|
||||
if (!filename) return;
|
||||
@@ -128,19 +128,89 @@ export async function deleteFile(filename, data) {
|
||||
export async function clearFileStorage() {
|
||||
try {
|
||||
let files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
|
||||
for (let file of files) {
|
||||
try {
|
||||
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
|
||||
}
|
||||
for (let file of oldCache) {
|
||||
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log("clearFileStorage", e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCacheDir() {
|
||||
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
||||
await RNFetchBlob.fs.mkdir(cacheDir);
|
||||
console.log("Cache directory created");
|
||||
}
|
||||
}
|
||||
|
||||
export async function migrateFilesFromCache() {
|
||||
try {
|
||||
await createCacheDir();
|
||||
const migratedFilesPath = cacheDir + "/.migrated_1";
|
||||
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
|
||||
if (migrated) {
|
||||
console.log("Files migrated already");
|
||||
return;
|
||||
}
|
||||
|
||||
let files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
console.log("Files to migrate:", files.join(","));
|
||||
|
||||
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
for (let file of oldCache) {
|
||||
if (file.startsWith("org.") || file.startsWith("com.")) continue;
|
||||
RNFetchBlob.fs
|
||||
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
|
||||
.catch(console.log);
|
||||
console.log("Moved", file);
|
||||
}
|
||||
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
|
||||
} catch (e) {
|
||||
console.log("migrateFilesFromCache", e);
|
||||
}
|
||||
}
|
||||
|
||||
const ABYTES = 17;
|
||||
export async function exists(filename) {
|
||||
let exists = await RNFetchBlob.fs.exists(`${cacheDir}/${filename}`);
|
||||
let path = `${cacheDir}/${filename}`;
|
||||
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
|
||||
let exists = await RNFetchBlob.fs.exists(path);
|
||||
|
||||
// Check if file is present in app group path.
|
||||
let existsInAppGroup = false;
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
existsInAppGroup = await RNFetchBlob.fs.exists(appGroupPath);
|
||||
}
|
||||
|
||||
if (exists || existsInAppGroup) {
|
||||
const attachment = db.attachments.attachment(filename);
|
||||
const totalChunks = Math.ceil(attachment.length / attachment.chunkSize);
|
||||
const totalAbytes = totalChunks * ABYTES;
|
||||
const expectedFileSize = attachment.length + totalAbytes;
|
||||
|
||||
const stat = await RNFetchBlob.fs.stat(
|
||||
existsInAppGroup ? appGroupPath : path
|
||||
);
|
||||
|
||||
if (stat.size !== expectedFileSize) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(existsInAppGroup ? appGroupPath : path)
|
||||
.catch(console.log);
|
||||
return false;
|
||||
}
|
||||
|
||||
exists = true;
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
@@ -21,14 +21,15 @@ import RNFetchBlob from "react-native-blob-util";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { DatabaseLogger, db } from "../database";
|
||||
import { cacheDir } from "./utils";
|
||||
import { isImage, isDocument } from "@notesnook/core/utils/filename";
|
||||
import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
|
||||
import { Platform } from "react-native";
|
||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||
import { createCacheDir } from "./io";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
let { url, headers } = data;
|
||||
|
||||
await createCacheDir();
|
||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||
|
||||
try {
|
||||
@@ -51,6 +52,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
uploadFilePath = appGroupPath;
|
||||
}
|
||||
DatabaseLogger.info(`Starting upload: ${filename}`);
|
||||
|
||||
let request = RNFetchBlob.config({
|
||||
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
|
||||
})
|
||||
@@ -66,9 +68,15 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(sent, total, filename, 0, "upload");
|
||||
DatabaseLogger.info(`uploading file: ${sent}/${total}`);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${sent}/${total}`
|
||||
);
|
||||
});
|
||||
cancelToken.cancel = request.cancel;
|
||||
|
||||
cancelToken.cancel = () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
request.cancel();
|
||||
};
|
||||
let response = await request;
|
||||
|
||||
let status = response.info().status;
|
||||
@@ -76,6 +84,9 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
let result = status >= 200 && status < 300 && text.length === 0;
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
if (result) {
|
||||
DatabaseLogger.info(
|
||||
`File upload status: ${filename}, ${status}, ${text}`
|
||||
);
|
||||
let attachment = db.attachments.attachment(filename);
|
||||
if (!attachment) return result;
|
||||
if (
|
||||
@@ -84,8 +95,15 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
) {
|
||||
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
|
||||
}
|
||||
} else {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(uploadFilePath);
|
||||
throw new Error(
|
||||
`${status}, ${text}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(response.info())}`
|
||||
);
|
||||
}
|
||||
DatabaseLogger.info(`File uploaded: ${filename}`);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
@@ -21,7 +21,12 @@ import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
|
||||
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
|
||||
|
||||
export const cacheDir =
|
||||
Platform.OS == "ios"
|
||||
? RNFetchBlob.fs.dirs.LibraryDir + "/.cache"
|
||||
: RNFetchBlob.fs.dirs.DocumentDir + "/.cache";
|
||||
|
||||
export function getRandomId(prefix) {
|
||||
return Math.random()
|
||||
@@ -66,9 +71,12 @@ export function cancelable(operation) {
|
||||
}
|
||||
|
||||
export function copyFileAsync(source, dest) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ScopedStorage.copyFile(source, dest, (e, r) => {
|
||||
console.log(e, r);
|
||||
if (e) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,18 +21,18 @@ import React from "react";
|
||||
import { FlatList, View } from "react-native";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { allowedOnPlatform, renderItem } from "./functions";
|
||||
|
||||
export const Announcement = ({ color }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const announcements = useMessageStore((state) => state.announcements);
|
||||
let announcement = announcements.length > 0 ? announcements[0] : null;
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
return !announcement || selectionMode ? null : (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
width: "100%",
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 12,
|
||||
@@ -44,28 +44,26 @@ export const Announcement = ({ color }) => {
|
||||
width: "100%",
|
||||
borderRadius: 10,
|
||||
overflow: "hidden",
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
paddingBottom: 12
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<FlatList
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: 12
|
||||
}}
|
||||
data={announcement?.body.filter((item) =>
|
||||
allowedOnPlatform(item.platforms)
|
||||
)}
|
||||
renderItem={({ item, index }) =>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
marginTop: 12
|
||||
}}
|
||||
>
|
||||
{announcement?.body
|
||||
.filter((item) => allowedOnPlatform(item.platforms))
|
||||
.map((item, index) =>
|
||||
renderItem({
|
||||
item: item,
|
||||
index: index,
|
||||
color: colors[color],
|
||||
inline: true
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -22,7 +22,7 @@ import { Linking, View } from "react-native";
|
||||
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseAnnouncementDialog, eCloseSheet } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
@@ -33,7 +33,7 @@ import { Button } from "../ui/button";
|
||||
import { allowedOnPlatform, getStyle } from "./functions";
|
||||
|
||||
export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
let buttons =
|
||||
actions.filter((item) => allowedOnPlatform(item.platforms)) || [];
|
||||
|
||||
@@ -92,8 +92,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
style={{
|
||||
height: 30,
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0,
|
||||
marginTop: -6
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -111,7 +110,6 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0,
|
||||
marginTop: -6,
|
||||
marginLeft: 12
|
||||
}}
|
||||
textStyle={{
|
||||
@@ -129,9 +127,11 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
||||
title={item.title}
|
||||
fontSize={SIZE.md}
|
||||
buttonType={{
|
||||
color: color ? color : colors.accent,
|
||||
text: colors.light,
|
||||
selected: color ? color : colors.accent,
|
||||
color: color ? color : colors.primary.accent,
|
||||
text: color
|
||||
? colors.static.white
|
||||
: colors.primary.accentForeground,
|
||||
selected: color ? color : colors.primary.accent,
|
||||
opacity: 1
|
||||
}}
|
||||
onPress={() => onPress(item)}
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import {
|
||||
eCloseAnnouncementDialog,
|
||||
eOpenAnnouncementDialog
|
||||
@@ -34,10 +34,69 @@ import BaseDialog from "../dialog/base-dialog";
|
||||
import { allowedOnPlatform, renderItem } from "./functions";
|
||||
import { useCallback } from "react";
|
||||
|
||||
/**
|
||||
* Test announcement
|
||||
* {
|
||||
id: "some-announcement",
|
||||
type: "dialog",
|
||||
body: [
|
||||
{
|
||||
type: "title",
|
||||
text: "This is a title",
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "Most of you are too busy to keep up to date with what's happening in Notesnook. That is unfortunate because Notesnook has come a looooong way.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "To solve this, we are launching the Notesnook Digest — a newsletter to help you stay updated about Notesnook development. And to keep things interesting I'll also sprinkle this newsletter with other interesting stuff like privacy tips & news, interesting books, things I am looking forward to etc.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "So be sure to subscribe. There won't be a proper schedule to this (yet) maybe once or twice a month. I promise no spam — only more awesomeness.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "description",
|
||||
text: "— May privacy reign.",
|
||||
style: {
|
||||
marginBottom: 1
|
||||
},
|
||||
platforms: ["all"]
|
||||
},
|
||||
{
|
||||
type: "callToActions",
|
||||
actions: [
|
||||
{
|
||||
type: "promo",
|
||||
title: "15% Off",
|
||||
platforms: ["android"],
|
||||
data: "com.streetwriters.notesnook.sub.yr.15"
|
||||
}
|
||||
],
|
||||
platforms: ["all"]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
export const AnnouncementDialog = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [info, setInfo] = useState(null);
|
||||
const [info, setInfo] = useState();
|
||||
const remove = useMessageStore((state) => state.remove);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -51,7 +110,9 @@ export const AnnouncementDialog = () => {
|
||||
|
||||
const open = (data) => {
|
||||
setInfo(data);
|
||||
setVisible(true);
|
||||
setImmediate(() => {
|
||||
setVisible(true);
|
||||
});
|
||||
};
|
||||
|
||||
const close = useCallback(() => {
|
||||
@@ -73,7 +134,7 @@ export const AnnouncementDialog = () => {
|
||||
<View
|
||||
style={{
|
||||
width: DDS.isTab ? 600 : "100%",
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
maxHeight: DDS.isTab ? "90%" : "100%",
|
||||
borderRadius: DDS.isTab ? 10 : 0,
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -24,7 +24,7 @@ import { db } from "../../common/database";
|
||||
import { useAppState } from "../../hooks/use-app-state";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { NotesnookModule } from "../../utils/notesnook-module";
|
||||
import { SIZE } from "../../utils/size";
|
||||
@@ -36,7 +36,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
const AppLockedOverlay = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const appLocked = useUserStore((state) => state.appLocked);
|
||||
const lockApp = useUserStore((state) => state.lockApp);
|
||||
@@ -100,7 +100,7 @@ const AppLockedOverlay = () => {
|
||||
appLocked && (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
@@ -134,10 +134,10 @@ const AppLockedOverlay = () => {
|
||||
marginTop: user ? 0 : 50
|
||||
}}
|
||||
onPress={onUnlockAppRequested}
|
||||
color={colors.border}
|
||||
color={colors.primary.border}
|
||||
/>
|
||||
<Heading
|
||||
color={colors.heading}
|
||||
color={colors.primary.heading}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
textAlign: "center"
|
||||
@@ -160,7 +160,7 @@ const AppLockedOverlay = () => {
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: 12,
|
||||
backgroundColor: colors.bg
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
{user ? (
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
} from "../../services/event-manager";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseAttachmentDialog, eCloseSheet } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
@@ -48,8 +48,8 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
|
||||
const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const Actions = ({ attachment, setAttachments, fwdRef, close }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const contextId = attachment.metadata.hash;
|
||||
const [filename, setFilename] = useState(attachment.metadata.filename);
|
||||
const [currentProgress] = useAttachmentProgress(attachment);
|
||||
@@ -64,7 +64,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
name: "Download",
|
||||
onPress: async () => {
|
||||
if (currentProgress) {
|
||||
await db.fs.cancel(attachment.metadata.hash, "download");
|
||||
await db.fs.cancel(attachment.metadata.hash);
|
||||
useAttachmentStore.getState().remove(attachment.metadata.hash);
|
||||
}
|
||||
downloadAttachment(attachment.metadata.hash, false);
|
||||
@@ -117,7 +117,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
});
|
||||
}
|
||||
|
||||
setAttachments([...db.attachments.all]);
|
||||
setAttachments();
|
||||
setLoading({
|
||||
name: null
|
||||
});
|
||||
@@ -140,7 +140,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
filename: value
|
||||
});
|
||||
setFilename(value);
|
||||
setAttachments([...db.attachments.all]);
|
||||
setAttachments();
|
||||
}
|
||||
},
|
||||
positiveText: "Rename"
|
||||
@@ -152,8 +152,8 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
name: "Delete",
|
||||
onPress: async () => {
|
||||
await db.attachments.remove(attachment.metadata.hash, false);
|
||||
setAttachments([...db.attachments.all]);
|
||||
eSendEvent(eCloseSheet, contextId);
|
||||
setAttachments();
|
||||
close();
|
||||
},
|
||||
icon: "delete-outline"
|
||||
}
|
||||
@@ -194,7 +194,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.nav,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
marginBottom: notes && notes.length > 0 ? 0 : 12
|
||||
}}
|
||||
>
|
||||
@@ -219,7 +219,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{attachment.metadata.type}
|
||||
</Paragraph>
|
||||
@@ -228,7 +228,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{formatBytes(attachment.length)}
|
||||
</Paragraph>
|
||||
@@ -239,7 +239,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
marginRight: 10
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{attachment.noteIds.length} note
|
||||
{attachment.noteIds.length > 1 ? "s" : ""}
|
||||
@@ -255,7 +255,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
});
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{attachment.metadata.hash}
|
||||
</Paragraph>
|
||||
@@ -268,7 +268,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.nav,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
marginBottom: 12,
|
||||
paddingVertical: 12
|
||||
}}
|
||||
@@ -322,10 +322,10 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
key={item.name}
|
||||
buttonType={{
|
||||
text: item.on
|
||||
? colors.accent
|
||||
? colors.primary.accent
|
||||
: item.name === "Delete" || item.name === "PermDelete"
|
||||
? colors.errorText
|
||||
: colors.pri
|
||||
? colors.error.paragraph
|
||||
: colors.primary.paragraph
|
||||
}}
|
||||
onPress={item.onPress}
|
||||
title={item.name}
|
||||
@@ -362,8 +362,13 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
|
||||
Actions.present = (attachment, set, context) => {
|
||||
presentSheet({
|
||||
context: context,
|
||||
component: (ref) => (
|
||||
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
|
||||
component: (ref, close) => (
|
||||
<Actions
|
||||
fwdRef={ref}
|
||||
setAttachments={set}
|
||||
close={close}
|
||||
attachment={attachment}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,15 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
import React from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressCircleComponent } from "../ui/svg/lazy";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
@@ -41,17 +40,17 @@ export const AttachmentItem = ({
|
||||
encryption,
|
||||
setAttachments,
|
||||
pressable = true,
|
||||
hideWhenNotDownloading
|
||||
hideWhenNotDownloading,
|
||||
context
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
|
||||
attachment,
|
||||
encryption
|
||||
);
|
||||
|
||||
const onPress = () => {
|
||||
if (!pressable) return;
|
||||
Actions.present(attachment, setAttachments, attachment.metadata.hash);
|
||||
Actions.present(attachment, setAttachments, context);
|
||||
};
|
||||
|
||||
return hideWhenNotDownloading &&
|
||||
@@ -66,11 +65,10 @@ export const AttachmentItem = ({
|
||||
padding: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.nav
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
type="grayBg"
|
||||
>
|
||||
<SheetProvider context={attachment.metadata.hash} />
|
||||
<View
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
@@ -85,12 +83,12 @@ export const AttachmentItem = ({
|
||||
marginLeft: -5
|
||||
}}
|
||||
>
|
||||
<Icon name="file" size={SIZE.xxxl} color={colors.icon} />
|
||||
<Icon name="file" size={SIZE.xxxl} color={colors.primary.icon} />
|
||||
|
||||
<Paragraph
|
||||
adjustsFontSizeToFit
|
||||
size={6}
|
||||
color={colors.light}
|
||||
color={colors.static.white}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
@@ -113,13 +111,13 @@ export const AttachmentItem = ({
|
||||
}}
|
||||
numberOfLines={1}
|
||||
lineBreakMode="middle"
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
>
|
||||
{attachment.metadata.filename}
|
||||
</Paragraph>
|
||||
|
||||
{!hideWhenNotDownloading ? (
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||
{formatBytes(attachment.length)}{" "}
|
||||
{currentProgress?.type
|
||||
? "(" + currentProgress.type + "ing - tap to cancel)"
|
||||
@@ -151,7 +149,7 @@ export const AttachmentItem = ({
|
||||
textStyle={{
|
||||
fontSize: 10
|
||||
}}
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
formatText={(progress) => (progress * 100).toFixed(0)}
|
||||
borderWidth={0}
|
||||
thickness={2}
|
||||
@@ -163,7 +161,7 @@ export const AttachmentItem = ({
|
||||
<IconButton
|
||||
onPress={onPress}
|
||||
name="alert-circle-outline"
|
||||
color={colors.errorText}
|
||||
color={colors.error.paragraph}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -26,12 +26,12 @@ import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { AttachmentItem } from "./attachment-item";
|
||||
|
||||
const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [progress, setProgress] = useState({
|
||||
value: 0,
|
||||
@@ -158,8 +158,8 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
||||
animated={true}
|
||||
useNativeDriver
|
||||
progress={progress.value ? progress.value / attachments.length : 0}
|
||||
unfilledColor={colors.nav}
|
||||
color={colors.accent}
|
||||
unfilledColor={colors.secondary.background}
|
||||
color={colors.primary.accent}
|
||||
borderWidth={0}
|
||||
/>
|
||||
</View>
|
||||
@@ -170,7 +170,7 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
||||
maxHeight: 300,
|
||||
width: "100%",
|
||||
minHeight: 60,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginVertical: 12
|
||||
}}
|
||||
@@ -184,7 +184,9 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
||||
height: 60
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.icon}>No downloads in progress.</Paragraph>
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
No downloads in progress.
|
||||
</Paragraph>
|
||||
</View>
|
||||
}
|
||||
keyExtractor={(item) => item.id}
|
||||
|
||||
@@ -24,7 +24,7 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
@@ -40,12 +40,12 @@ import {
|
||||
isDocument,
|
||||
isImage,
|
||||
isVideo
|
||||
} from "@notesnook/core/utils/filename";
|
||||
} from "@notesnook/core/dist/utils/filename";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
|
||||
export const AttachmentDialog = ({ note }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const { height } = useSettingStore((state) => state.dimensions);
|
||||
const [attachments, setAttachments] = useState(
|
||||
note
|
||||
@@ -68,7 +68,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
!attachmentSearchValue.current ||
|
||||
attachmentSearchValue.current === ""
|
||||
) {
|
||||
setAttachments([...attachments]);
|
||||
setAttachments(filterAttachments(currentFilter));
|
||||
}
|
||||
clearTimeout(searchTimer.current);
|
||||
searchTimer.current = setTimeout(() => {
|
||||
@@ -77,12 +77,19 @@ export const AttachmentDialog = ({ note }) => {
|
||||
attachmentSearchValue.current
|
||||
);
|
||||
if (results.length === 0) return;
|
||||
setAttachments(results);
|
||||
|
||||
setAttachments(filterAttachments(currentFilter, results));
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<AttachmentItem setAttachments={setAttachments} attachment={item} />
|
||||
<AttachmentItem
|
||||
setAttachments={() => {
|
||||
setAttachments(filterAttachments(currentFilter));
|
||||
}}
|
||||
attachment={item}
|
||||
context="attachments-list"
|
||||
/>
|
||||
);
|
||||
|
||||
const onCheck = async () => {
|
||||
@@ -129,11 +136,12 @@ export const AttachmentDialog = ({ note }) => {
|
||||
}
|
||||
];
|
||||
|
||||
const filterAttachments = (type) => {
|
||||
const attachments = note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
isDocument;
|
||||
const filterAttachments = (type, _attachments) => {
|
||||
const attachments =
|
||||
_attachments || note
|
||||
? db.attachments.ofNote(note.id, "all")
|
||||
: [...(db.attachments.all || [])];
|
||||
|
||||
switch (type) {
|
||||
case "all":
|
||||
return attachments;
|
||||
@@ -197,7 +205,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
width: 40,
|
||||
marginRight: 10
|
||||
}}
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
size={SIZE.lg}
|
||||
onPress={onCheck}
|
||||
/>
|
||||
@@ -209,7 +217,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
height: 40,
|
||||
width: 40
|
||||
}}
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
onPress={() => {
|
||||
DownloadAttachments.present(
|
||||
"attachments-list",
|
||||
@@ -237,7 +245,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
width: "100%",
|
||||
height: 50,
|
||||
flexDirection: "row",
|
||||
backgroundColor: colors.bg
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
minWidth: "100%",
|
||||
@@ -260,7 +268,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
borderBottomColor:
|
||||
currentFilter !== item.filterBy
|
||||
? "transparent"
|
||||
: colors.accent
|
||||
: colors.primary.accent
|
||||
}}
|
||||
onPress={() => {
|
||||
setCurrentFilter(item.filterBy);
|
||||
@@ -281,7 +289,7 @@ export const AttachmentDialog = ({ note }) => {
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon name="attachment" size={60} color={colors.icon} />
|
||||
<Icon name="attachment" size={60} color={colors.secondary.icon} />
|
||||
<Paragraph>
|
||||
{note ? "No attachments on this note" : "No attachments"}
|
||||
</Paragraph>
|
||||
@@ -300,14 +308,18 @@ export const AttachmentDialog = ({ note }) => {
|
||||
/>
|
||||
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
|
||||
<Icon
|
||||
name="shield-key-outline"
|
||||
size={SIZE.xs}
|
||||
color={colors.primary.icon}
|
||||
/>
|
||||
{" "}All attachments are end-to-end encrypted.
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
@@ -17,14 +17,16 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
@@ -34,7 +36,6 @@ import { IconButton } from "../ui/icon-button";
|
||||
import { hideAuth, initialAuthMode } from "./common";
|
||||
import { Login } from "./login";
|
||||
import { Signup } from "./signup";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
|
||||
export const AuthMode = {
|
||||
login: 0,
|
||||
@@ -44,7 +45,7 @@ export const AuthMode = {
|
||||
};
|
||||
|
||||
const AuthModal = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [currentAuthMode, setCurrentAuthMode] = useState(AuthMode.login);
|
||||
const actionSheetRef = useRef();
|
||||
@@ -68,6 +69,9 @@ const AuthModal = () => {
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
actionSheetRef.current?.hide();
|
||||
setCurrentAuthMode(AuthMode.login);
|
||||
setVisible(false);
|
||||
@@ -77,24 +81,28 @@ const AuthModal = () => {
|
||||
<BaseDialog
|
||||
overlayOpacity={0}
|
||||
statusBarTranslucent={false}
|
||||
onShow={() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
}}
|
||||
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
|
||||
visible={true}
|
||||
onClose={close}
|
||||
useSafeArea={false}
|
||||
bounce={false}
|
||||
background={colors.bg}
|
||||
background={colors.primary.background}
|
||||
transparent={false}
|
||||
animated={false}
|
||||
centered={false}
|
||||
enableSheetKeyboardHandler
|
||||
>
|
||||
<KeyboardAwareScrollView
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
contentContainerStyle={{
|
||||
height: Platform.OS === "android" ? "100%" : "100%"
|
||||
}}
|
||||
enableAutomaticScroll={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
bounces={false}
|
||||
>
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
<Signup
|
||||
@@ -116,7 +124,7 @@ const AuthModal = () => {
|
||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||
top: 0,
|
||||
zIndex: 999,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
@@ -139,7 +147,7 @@ const AuthModal = () => {
|
||||
onPress={() => {
|
||||
hideAuth();
|
||||
}}
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
@@ -34,7 +34,7 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
export const ForgotPassword = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const email = useRef();
|
||||
const emailInputRef = useRef();
|
||||
const [error, setError] = useState(false);
|
||||
@@ -117,7 +117,7 @@ export const ForgotPassword = () => {
|
||||
width: null,
|
||||
height: null
|
||||
}}
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
name="email"
|
||||
size={50}
|
||||
/>
|
||||
@@ -134,7 +134,7 @@ export const ForgotPassword = () => {
|
||||
<View
|
||||
style={{
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
zIndex: 10,
|
||||
width: "100%",
|
||||
padding: 12
|
||||
|
||||
@@ -65,7 +65,7 @@ const Auth = ({ navigation, route }) => {
|
||||
onPress={() => {
|
||||
hideAuth();
|
||||
}}
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
customStyle={{
|
||||
position: 'absolute',
|
||||
zIndex: 999,
|
||||
|
||||
@@ -18,11 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
@@ -46,7 +46,7 @@ const LoginSteps = {
|
||||
};
|
||||
|
||||
export const Login = ({ changeMode }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [focused, setFocused] = useState(false);
|
||||
const {
|
||||
step,
|
||||
@@ -71,7 +71,7 @@ export const Login = ({ changeMode }) => {
|
||||
}, 5000);
|
||||
});
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
|
||||
const { width, height } = useWindowDimensions();
|
||||
useEffect(() => {
|
||||
async () => {
|
||||
setStep(LoginSteps.emailAuth);
|
||||
@@ -92,29 +92,27 @@ export const Login = ({ changeMode }) => {
|
||||
<View
|
||||
style={{
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
zIndex: 10,
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
height: "100%",
|
||||
minHeight: "100%"
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexGrow: 0.8,
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderBottomWidth: 1,
|
||||
marginBottom: 12,
|
||||
borderBottomColor: colors.border,
|
||||
borderBottomColor: colors.primary.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.border : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.primary.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
width: deviceMode === "mobile" ? null : "50%"
|
||||
width: deviceMode === "mobile" ? null : "50%",
|
||||
minHeight: height * 0.4
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -126,7 +124,7 @@ export const Login = ({ changeMode }) => {
|
||||
style={{
|
||||
width: 100,
|
||||
height: 5,
|
||||
backgroundColor: colors.accent,
|
||||
backgroundColor: colors.primary.accent,
|
||||
borderRadius: 2,
|
||||
marginRight: 7
|
||||
}}
|
||||
@@ -136,7 +134,7 @@ export const Login = ({ changeMode }) => {
|
||||
style={{
|
||||
width: 20,
|
||||
height: 5,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
@@ -162,10 +160,9 @@ export const Login = ({ changeMode }) => {
|
||||
: focused
|
||||
? "100%"
|
||||
: "99.9%",
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 20,
|
||||
flexGrow: 1
|
||||
paddingHorizontal: 20
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
@@ -284,11 +281,14 @@ export const Login = ({ changeMode }) => {
|
||||
paddingVertical: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
|
||||
<Paragraph
|
||||
size={SIZE.xs + 1}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
Don't have an account?{" "}
|
||||
<Paragraph
|
||||
size={SIZE.xs + 1}
|
||||
style={{ color: colors.accent }}
|
||||
style={{ color: colors.primary.accent }}
|
||||
>
|
||||
Sign up
|
||||
</Paragraph>
|
||||
|
||||
@@ -17,18 +17,22 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal, View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import BiometricService from "../../services/biometrics";
|
||||
import {
|
||||
ToastEvent,
|
||||
eSendEvent,
|
||||
eSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { setLoginMessage } from "../../services/message";
|
||||
import SettingsService from "../../services/settings";
|
||||
import Sync from "../../services/sync";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { clearAllStores } from "../../stores";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eLoginSessionExpired, eUserLoggedIn } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
@@ -42,6 +46,7 @@ import Input from "../ui/input";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { LoginSteps, useLogin } from "./use-login";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
|
||||
function getObfuscatedEmail(email) {
|
||||
if (!email) return "";
|
||||
@@ -56,7 +61,7 @@ function getObfuscatedEmail(email) {
|
||||
}
|
||||
|
||||
export const SessionExpired = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [focused, setFocused] = useState(false);
|
||||
const { step, password, email, passwordInputRef, loading, login } = useLogin(
|
||||
@@ -64,16 +69,20 @@ export const SessionExpired = () => {
|
||||
eSendEvent(eUserLoggedIn, true);
|
||||
setVisible(false);
|
||||
setFocused(false);
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await db.user.logout();
|
||||
await BiometricService.resetCredentials();
|
||||
SettingsService.set({
|
||||
introCompleted: true
|
||||
});
|
||||
setLoginMessage();
|
||||
SettingsService.resetSettings();
|
||||
useUserStore.getState().setUser(null);
|
||||
useUserStore.getState().setSyncing(false);
|
||||
MMKV.clearStore();
|
||||
clearAllStores();
|
||||
setVisible(false);
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
@@ -123,22 +132,27 @@ export const SessionExpired = () => {
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<Modal
|
||||
<BaseDialog
|
||||
transparent={false}
|
||||
background={colors.primary.background}
|
||||
bounce={false}
|
||||
animated={false}
|
||||
centered={false}
|
||||
onShow={async () => {
|
||||
await sleep(300);
|
||||
passwordInputRef.current?.focus();
|
||||
setFocused(true);
|
||||
}}
|
||||
enableSheetKeyboardHandler={true}
|
||||
visible={true}
|
||||
>
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<View
|
||||
style={{
|
||||
width: focused ? "100%" : "99.9%",
|
||||
padding: 12,
|
||||
justifyContent: "center",
|
||||
flex: 1,
|
||||
backgroundColor: colors.bg
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -157,10 +171,10 @@ export const SessionExpired = () => {
|
||||
height: 60
|
||||
}}
|
||||
name="alert"
|
||||
color={colors.errorText}
|
||||
color={colors.error.icon}
|
||||
size={50}
|
||||
/>
|
||||
<Heading size={SIZE.xxxl} color={colors.heading}>
|
||||
<Heading size={SIZE.xxxl} color={colors.primary.heading}>
|
||||
Session expired
|
||||
</Heading>
|
||||
<Paragraph
|
||||
@@ -224,7 +238,9 @@ export const SessionExpired = () => {
|
||||
</View>
|
||||
<Toast context="local" />
|
||||
<Dialog context="session_expiry" />
|
||||
</Modal>
|
||||
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
</BaseDialog>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,13 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { TouchableOpacity, View } from "react-native";
|
||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { SIZE } from "../../utils/size";
|
||||
@@ -37,7 +37,7 @@ import { hideAuth } from "./common";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
export const Signup = ({ changeMode, trial }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const email = useRef();
|
||||
const emailInputRef = useRef();
|
||||
const passwordInputRef = useRef();
|
||||
@@ -49,7 +49,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
const setLastSynced = useUserStore((state) => state.setLastSynced);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
|
||||
const { width, height } = useWindowDimensions();
|
||||
const validateInfo = () => {
|
||||
if (!password.current || !email.current || !confirmPassword.current) {
|
||||
ToastEvent.show({
|
||||
@@ -67,6 +67,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
|
||||
const signup = async () => {
|
||||
if (!validateInfo() || error) return;
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await db.user.signup(email.current.toLowerCase(), password.current);
|
||||
@@ -98,28 +99,27 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
<View
|
||||
style={{
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
zIndex: 10,
|
||||
width: "100%",
|
||||
alignSelf: "center",
|
||||
height: "100%"
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
justifyContent: "flex-end",
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
borderBottomColor: colors.primary.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.border : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.primary.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
width: deviceMode === "mobile" ? null : "50%"
|
||||
width: deviceMode === "mobile" ? null : "50%",
|
||||
minHeight: height * 0.4
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -131,7 +131,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
style={{
|
||||
width: 100,
|
||||
height: 5,
|
||||
backgroundColor: colors.accent,
|
||||
backgroundColor: colors.primary.accent,
|
||||
borderRadius: 2,
|
||||
marginRight: 7
|
||||
}}
|
||||
@@ -141,7 +141,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
style={{
|
||||
width: 20,
|
||||
height: 5,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
@@ -162,9 +162,8 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
style={{
|
||||
width: DDS.isTab ? "50%" : "100%",
|
||||
paddingHorizontal: 20,
|
||||
backgroundColor: colors.bg,
|
||||
alignSelf: "center",
|
||||
flexGrow: 0.5
|
||||
backgroundColor: colors.primary.background,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
@@ -232,7 +231,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
marginBottom: 25
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
By signing up, you agree to our{" "}
|
||||
<Paragraph
|
||||
@@ -243,7 +242,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
style={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
>
|
||||
Terms of Service{" "}
|
||||
</Paragraph>
|
||||
@@ -256,7 +255,7 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
style={{
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
>
|
||||
Privacy Policy.
|
||||
</Paragraph>{" "}
|
||||
@@ -289,9 +288,12 @@ export const Signup = ({ changeMode, trial }) => {
|
||||
paddingVertical: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
|
||||
<Paragraph size={SIZE.xs + 1} color={colors.secondary.paragraph}>
|
||||
Already have an account?{" "}
|
||||
<Paragraph size={SIZE.xs + 1} style={{ color: colors.accent }}>
|
||||
<Paragraph
|
||||
size={SIZE.xs + 1}
|
||||
style={{ color: colors.primary.accent }}
|
||||
>
|
||||
Login
|
||||
</Paragraph>
|
||||
</Paragraph>
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
presentSheet,
|
||||
ToastEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
@@ -39,7 +39,7 @@ import Paragraph from "../ui/typography/paragraph";
|
||||
import { useCallback } from "react";
|
||||
|
||||
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const code = useRef();
|
||||
const [currentMethod, setCurrentMethod] = useState({
|
||||
method: mfaInfo?.primaryMethod,
|
||||
@@ -157,7 +157,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
}}
|
||||
size={50}
|
||||
name="key"
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
/>
|
||||
<Heading
|
||||
style={{
|
||||
@@ -214,6 +214,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
code.current = value;
|
||||
//onNext();
|
||||
}}
|
||||
caretHidden
|
||||
inputStyle={{
|
||||
fontSize: SIZE.lg,
|
||||
height: 60,
|
||||
@@ -281,7 +282,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||
marginRight: 10
|
||||
}}
|
||||
size={15}
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
name={item.icon}
|
||||
/>
|
||||
<View
|
||||
|
||||
@@ -33,7 +33,7 @@ export const LoginSteps = {
|
||||
passwordAuth: 3
|
||||
};
|
||||
|
||||
export const useLogin = (onFinishLogin) => {
|
||||
export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
@@ -64,10 +64,12 @@ export const useLogin = (onFinishLogin) => {
|
||||
const login = async () => {
|
||||
if (!validateInfo() || error) return;
|
||||
try {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
switch (step) {
|
||||
case LoginSteps.emailAuth: {
|
||||
const mfaInfo = await db.user.authenticateEmail(email.current);
|
||||
console.log("email auth", mfaInfo);
|
||||
if (mfaInfo) {
|
||||
TwoFactorVerification.present(async (mfa, callback) => {
|
||||
try {
|
||||
@@ -100,7 +102,12 @@ export const useLogin = (onFinishLogin) => {
|
||||
break;
|
||||
}
|
||||
case LoginSteps.passwordAuth: {
|
||||
await db.user.authenticatePassword(email.current, password.current);
|
||||
await db.user.authenticatePassword(
|
||||
email.current,
|
||||
password.current,
|
||||
null,
|
||||
sessionExpired
|
||||
);
|
||||
finishLogin();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -17,24 +17,24 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const ContainerHeader = ({ children }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
export const ContainerHeader = (props: PropsWithChildren) => {
|
||||
const { colors } = useThemeColors();
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
|
||||
return !selectionMode ? (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
width: "100%",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{props.children}
|
||||
</View>
|
||||
) : null;
|
||||
};
|
||||
@@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { Keyboard, Platform, View } from "react-native";
|
||||
import { Keyboard, View } from "react-native";
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedStyle,
|
||||
@@ -35,12 +36,20 @@ import { SIZE, normalize } from "../../utils/size";
|
||||
import NativeTooltip from "../../utils/tooltip";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
|
||||
export const FloatingButton = ({
|
||||
interface FloatingButton {
|
||||
title?: string;
|
||||
onPress: () => void;
|
||||
color?: string;
|
||||
alwaysVisible?: boolean;
|
||||
}
|
||||
|
||||
const FloatingButton = ({
|
||||
title,
|
||||
onPress,
|
||||
color = "accent",
|
||||
shouldShow = false
|
||||
}) => {
|
||||
color,
|
||||
alwaysVisible = false
|
||||
}: FloatingButton) => {
|
||||
const { colors } = useThemeColors();
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const translate = useSharedValue(0);
|
||||
@@ -58,12 +67,8 @@ export const FloatingButton = ({
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
animate(selectionMode ? 150 : 0);
|
||||
}, [animate, selectionMode]);
|
||||
|
||||
const animate = useCallback(
|
||||
(toValue) => {
|
||||
(toValue: number) => {
|
||||
translate.value = withTiming(toValue, {
|
||||
duration: 250,
|
||||
easing: Easing.elastic(1)
|
||||
@@ -72,39 +77,39 @@ export const FloatingButton = ({
|
||||
[translate]
|
||||
);
|
||||
|
||||
const onKeyboardHide = useCallback(async () => {
|
||||
editorState().keyboardState = false;
|
||||
if (deviceMode !== "mobile") return;
|
||||
animate(0);
|
||||
}, [animate, deviceMode]);
|
||||
|
||||
const onKeyboardShow = useCallback(async () => {
|
||||
editorState().keyboardState = true;
|
||||
if (deviceMode !== "mobile") return;
|
||||
animate(150);
|
||||
}, [animate, deviceMode]);
|
||||
useEffect(() => {
|
||||
animate(selectionMode ? 150 : 0);
|
||||
}, [animate, selectionMode]);
|
||||
|
||||
useEffect(() => {
|
||||
let sub1 = Keyboard.addListener("keyboardDidShow", onKeyboardShow);
|
||||
let sub2 = Keyboard.addListener("keyboardDidHide", onKeyboardHide);
|
||||
return () => {
|
||||
sub1?.remove();
|
||||
sub2?.remove();
|
||||
const onKeyboardHide = () => {
|
||||
editorState().keyboardState = false;
|
||||
if (deviceMode !== "mobile") return;
|
||||
animate(0);
|
||||
};
|
||||
}, [deviceMode, onKeyboardHide, onKeyboardShow]);
|
||||
const paddings = {
|
||||
ios: 20,
|
||||
android: 20,
|
||||
iPad: 20
|
||||
};
|
||||
|
||||
return deviceMode !== "mobile" && !shouldShow ? null : (
|
||||
const onKeyboardShow = () => {
|
||||
editorState().keyboardState = true;
|
||||
if (deviceMode !== "mobile") return;
|
||||
animate(150);
|
||||
};
|
||||
|
||||
const sub = [
|
||||
Keyboard.addListener("keyboardDidShow", onKeyboardShow),
|
||||
Keyboard.addListener("keyboardDidHide", onKeyboardHide)
|
||||
];
|
||||
return () => {
|
||||
sub.forEach((sub) => sub?.remove?.());
|
||||
};
|
||||
}, [deviceMode, animate]);
|
||||
|
||||
return deviceMode !== "mobile" && !alwaysVisible ? null : (
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
position: "absolute",
|
||||
right: 12,
|
||||
bottom: paddings[Platform.isPad ? "iPad" : Platform.OS],
|
||||
bottom: 20,
|
||||
zIndex: 10
|
||||
},
|
||||
animatedStyle
|
||||
@@ -113,14 +118,15 @@ export const FloatingButton = ({
|
||||
<PressableButton
|
||||
testID={notesnook.buttons.add}
|
||||
type="accent"
|
||||
accentColor={color || "accent"}
|
||||
accentText="light"
|
||||
accentColor={colors.static[color as keyof typeof colors.static]}
|
||||
customStyle={{
|
||||
...getElevationStyle(5),
|
||||
borderRadius: 100
|
||||
}}
|
||||
onLongPress={(event) => {
|
||||
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
|
||||
if (title) {
|
||||
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
|
||||
}
|
||||
}}
|
||||
onPress={onPress}
|
||||
>
|
||||
@@ -134,7 +140,7 @@ export const FloatingButton = ({
|
||||
>
|
||||
<Icon
|
||||
name={title === "Clear all trash" ? "delete" : "plus"}
|
||||
color="white"
|
||||
color={colors.primary.accentForeground}
|
||||
size={SIZE.xxl}
|
||||
/>
|
||||
</View>
|
||||
@@ -142,3 +148,5 @@ export const FloatingButton = ({
|
||||
</Animated.View>
|
||||
);
|
||||
};
|
||||
|
||||
export { FloatingButton };
|
||||
@@ -17,44 +17,40 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { KeyboardAvoidingView, Platform, View } from "react-native";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { KeyboardAvoidingView, Platform } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { Header } from "../header";
|
||||
import SelectionHeader from "../selection-header";
|
||||
export const Container = ({ children }) => {
|
||||
|
||||
export const Container = ({ children }: PropsWithChildren) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior="padding"
|
||||
enabled={Platform.OS === "ios" && !floating}
|
||||
style={{
|
||||
flex: 1
|
||||
flex: 1,
|
||||
overflow: "hidden",
|
||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: "hidden",
|
||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
|
||||
}}
|
||||
>
|
||||
{!introCompleted ? null : (
|
||||
<>
|
||||
<SelectionHeader />
|
||||
<Header title="Header" screen="Header" />
|
||||
</>
|
||||
)}
|
||||
{!introCompleted ? null : (
|
||||
<>
|
||||
<SelectionHeader />
|
||||
<Header />
|
||||
</>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</View>
|
||||
{children}
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
@@ -20,21 +20,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useMessageStore } from "../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { COLORS_NOTE } from "../../utils/color-scheme";
|
||||
import { hexToRGBA } from "../../utils/color-scheme/utils";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { ColorValues } from "../../utils/colors";
|
||||
import { hexToRGBA } from "../../utils/colors";
|
||||
|
||||
export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const message = useMessageStore((state) => state.message);
|
||||
const annoucements = useMessageStore((state) => state.announcements);
|
||||
const hasAnnoucements = annoucements.length > 0;
|
||||
const shadeColor = color
|
||||
? hexToRGBA(
|
||||
COLORS_NOTE[color?.toLowerCase() as keyof typeof COLORS_NOTE],
|
||||
ColorValues[color?.toLowerCase() as keyof typeof ColorValues],
|
||||
0.15
|
||||
)
|
||||
: colors.shade;
|
||||
: colors.primary.shade;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -50,7 +50,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
height: 100,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
@@ -58,7 +58,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
backgroundColor: colors.transGray,
|
||||
backgroundColor: colors.primary.hover,
|
||||
borderRadius: 5,
|
||||
marginBottom: 10
|
||||
}}
|
||||
@@ -67,7 +67,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 250,
|
||||
height: 14,
|
||||
backgroundColor: colors.transGray,
|
||||
backgroundColor: colors.primary.hover,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
@@ -110,7 +110,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 150,
|
||||
height: 12,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginBottom: 10
|
||||
}}
|
||||
@@ -119,7 +119,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 250,
|
||||
height: 16,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
@@ -131,7 +131,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 30,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 10,
|
||||
marginBottom: 20,
|
||||
padding: 5,
|
||||
@@ -158,7 +158,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 15,
|
||||
height: 15,
|
||||
backgroundColor: colors.transGray,
|
||||
backgroundColor: colors.primary.hover,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
@@ -167,7 +167,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 60,
|
||||
height: 15,
|
||||
backgroundColor: colors.transGray,
|
||||
backgroundColor: colors.primary.hover,
|
||||
borderRadius: 3
|
||||
}}
|
||||
/>
|
||||
@@ -178,7 +178,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 200,
|
||||
height: 16,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
@@ -186,7 +186,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: "85%",
|
||||
height: 13,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
@@ -202,7 +202,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 50,
|
||||
height: 10,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
@@ -210,7 +210,7 @@ export const DefaultPlaceholder = ({ color }: { color: string }) => {
|
||||
style={{
|
||||
width: 60,
|
||||
height: 10,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginLeft: 10
|
||||
}}
|
||||
|
||||
@@ -21,7 +21,7 @@ import React from "react";
|
||||
import { ViewProps } from "react-native";
|
||||
import Animated, { FadeOutUp } from "react-native-reanimated";
|
||||
import { useDelayLayout } from "../../hooks/use-delay-layout";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { DefaultPlaceholder } from "./default-placeholder";
|
||||
import { SettingsPlaceholder } from "./settings-placeholder";
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function DelayLayout({
|
||||
animated = true,
|
||||
...props
|
||||
}: IDelayLayoutProps) {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const loading = useDelayLayout(
|
||||
!props.delay || props.delay < 300 ? 300 : props.delay
|
||||
);
|
||||
@@ -52,12 +52,12 @@ export default function DelayLayout({
|
||||
<Animated.View
|
||||
exiting={animated ? FadeOutUp : undefined}
|
||||
style={{
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
flex: 1,
|
||||
paddingTop: 20
|
||||
}}
|
||||
>
|
||||
<Placeholder color={props.color || colors.accent} />
|
||||
<Placeholder color={props.color || colors.primary.accent} />
|
||||
</Animated.View>
|
||||
) : (
|
||||
<>{props.children}</>
|
||||
|
||||
@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const SettingsPlaceholder = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<View>
|
||||
@@ -30,7 +30,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 100,
|
||||
height: 12,
|
||||
backgroundColor: colors.shade,
|
||||
backgroundColor: colors.primary.shade,
|
||||
borderRadius: 5,
|
||||
marginLeft: 12,
|
||||
marginBottom: 12
|
||||
@@ -51,7 +51,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: colors.transGray,
|
||||
backgroundColor: colors.primary.hover,
|
||||
borderRadius: 100,
|
||||
marginRight: 20
|
||||
}}
|
||||
@@ -61,7 +61,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 150,
|
||||
height: 12,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginBottom: 10
|
||||
}}
|
||||
@@ -70,7 +70,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 250,
|
||||
height: 16,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
@@ -93,7 +93,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: colors.shade,
|
||||
backgroundColor: colors.primary.shade,
|
||||
borderRadius: 100,
|
||||
marginRight: 20
|
||||
}}
|
||||
@@ -103,7 +103,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 150,
|
||||
height: 12,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginBottom: 10
|
||||
}}
|
||||
@@ -112,7 +112,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 250,
|
||||
height: 16,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5
|
||||
}}
|
||||
/>
|
||||
@@ -122,7 +122,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 40,
|
||||
height: 20,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 100,
|
||||
marginLeft: 15,
|
||||
alignItems: "flex-end",
|
||||
@@ -134,7 +134,7 @@ export const SettingsPlaceholder = () => {
|
||||
style={{
|
||||
width: 15,
|
||||
height: 15,
|
||||
backgroundColor: colors.shade,
|
||||
backgroundColor: colors.primary.shade,
|
||||
borderRadius: 100,
|
||||
marginLeft: 15
|
||||
}}
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { useNoteStore } from "../../stores/use-notes-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AnnouncementDialog } from "../announcements";
|
||||
import AuthModal from "../auth/auth-modal";
|
||||
import { SessionExpired } from "../auth/session-expired";
|
||||
@@ -39,7 +39,7 @@ import RestoreDataSheet from "../sheets/restore-data";
|
||||
import PDFPreview from "../dialogs/pdf-preview";
|
||||
|
||||
const DialogProvider = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const loading = useNoteStore((state) => state.loading);
|
||||
|
||||
return (
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Modal,
|
||||
@@ -30,6 +30,10 @@ import {
|
||||
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { BouncingView } from "../ui/transitions/bouncing-view";
|
||||
import { ScopedThemeProvider } from "@notesnook/theme";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { useAppState } from "../../hooks/use-app-state";
|
||||
|
||||
const BaseDialog = ({
|
||||
visible,
|
||||
@@ -47,9 +51,13 @@ const BaseDialog = ({
|
||||
bounce = true,
|
||||
closeOnTouch = true,
|
||||
useSafeArea = true,
|
||||
avoidKeyboardResize = false
|
||||
avoidKeyboardResize = false,
|
||||
enableSheetKeyboardHandler = false
|
||||
}) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const appState = useAppState();
|
||||
const lockEvents = useRef(false);
|
||||
const [internalVisible, setIntervalVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -57,72 +65,99 @@ const BaseDialog = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (useUserStore.getState().disableAppLockRequests) return;
|
||||
if (SettingsService.get().appLockMode === "background") {
|
||||
if (appState === "background") {
|
||||
setIntervalVisible(false);
|
||||
if (useUserStore.getState().appLocked) {
|
||||
lockEvents.current = true;
|
||||
const unsub = useUserStore.subscribe((state) => {
|
||||
if (!state.appLocked) {
|
||||
setIntervalVisible(true);
|
||||
unsub();
|
||||
setTimeout(() => {
|
||||
lockEvents.current = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [appState]);
|
||||
|
||||
const Wrapper = useSafeArea ? SafeAreaView : View;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent={true}
|
||||
animated
|
||||
statusBarTranslucent={statusBarTranslucent}
|
||||
supportedOrientations={[
|
||||
"portrait",
|
||||
"portrait-upside-down",
|
||||
"landscape",
|
||||
"landscape-left",
|
||||
"landscape-right"
|
||||
]}
|
||||
onShow={() => {
|
||||
if (onShow) {
|
||||
onShow();
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
}
|
||||
}}
|
||||
animationType={animation}
|
||||
onRequestClose={() => {
|
||||
if (!closeOnTouch) return null;
|
||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
||||
onRequestClose && onRequestClose();
|
||||
}}
|
||||
>
|
||||
<Wrapper
|
||||
style={{
|
||||
backgroundColor: background
|
||||
? background
|
||||
: transparent
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
<ScopedThemeProvider value="dialog">
|
||||
<Modal
|
||||
visible={visible && internalVisible}
|
||||
transparent={true}
|
||||
statusBarTranslucent={statusBarTranslucent}
|
||||
supportedOrientations={[
|
||||
"portrait",
|
||||
"portrait-upside-down",
|
||||
"landscape",
|
||||
"landscape-left",
|
||||
"landscape-right"
|
||||
]}
|
||||
onShow={() => {
|
||||
if (lockEvents.current) return;
|
||||
if (onShow) {
|
||||
onShow();
|
||||
if (!enableSheetKeyboardHandler) {
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
animationType={animation}
|
||||
onRequestClose={() => {
|
||||
if (lockEvents.current) return;
|
||||
if (!closeOnTouch) return null;
|
||||
useSettingStore.getState().setSheetKeyboardHandler(true);
|
||||
onRequestClose && onRequestClose();
|
||||
}}
|
||||
>
|
||||
<KeyboardAvoidingView
|
||||
enabled={!floating && Platform.OS === "ios" && !avoidKeyboardResize}
|
||||
behavior="padding"
|
||||
<Wrapper
|
||||
style={{
|
||||
backgroundColor: background
|
||||
? background
|
||||
: transparent
|
||||
? "transparent"
|
||||
: "rgba(0,0,0,0.3)"
|
||||
}}
|
||||
>
|
||||
<BouncingView
|
||||
duration={400}
|
||||
animated={animated}
|
||||
initialScale={bounce ? 0.9 : 1}
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
justifyContent: centered
|
||||
? "center"
|
||||
: bottom
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
}
|
||||
]}
|
||||
<KeyboardAvoidingView
|
||||
enabled={!floating && Platform.OS === "ios" && !avoidKeyboardResize}
|
||||
behavior="padding"
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={closeOnTouch ? onRequestClose : null}
|
||||
style={styles.overlayButton}
|
||||
/>
|
||||
{premium}
|
||||
{children}
|
||||
</BouncingView>
|
||||
</KeyboardAvoidingView>
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
<BouncingView
|
||||
duration={400}
|
||||
animated={animated}
|
||||
initialScale={bounce ? 0.9 : 1}
|
||||
style={[
|
||||
styles.backdrop,
|
||||
{
|
||||
alignItems: centered ? "center" : undefined,
|
||||
justifyContent: centered
|
||||
? "center"
|
||||
: bottom
|
||||
? "flex-end"
|
||||
: "flex-start"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={closeOnTouch ? onRequestClose : null}
|
||||
style={styles.overlayButton}
|
||||
/>
|
||||
{premium}
|
||||
{children}
|
||||
</BouncingView>
|
||||
</KeyboardAvoidingView>
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
</ScopedThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -130,8 +165,7 @@ const styles = StyleSheet.create({
|
||||
backdrop: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
justifyContent: "center"
|
||||
},
|
||||
overlayButton: {
|
||||
width: "100%",
|
||||
|
||||
@@ -21,7 +21,7 @@ import React from "react";
|
||||
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
@@ -35,14 +35,14 @@ const DialogButtons = ({
|
||||
doneText,
|
||||
positiveType
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
height: 60,
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
@@ -51,7 +51,7 @@ const DialogButtons = ({
|
||||
]}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator color={colors.accent} size={SIZE.lg} />
|
||||
<ActivityIndicator color={colors.primary.accent} size={SIZE.lg} />
|
||||
) : doneText ? (
|
||||
<View
|
||||
style={{
|
||||
@@ -60,11 +60,11 @@ const DialogButtons = ({
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
name="check-circle-outline"
|
||||
size={SIZE.md}
|
||||
/>
|
||||
<Paragraph color={colors.accent}>{" " + doneText}</Paragraph>
|
||||
<Paragraph color={colors.primary.accent}>{" " + doneText}</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<View />
|
||||
|
||||
@@ -20,11 +20,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
|
||||
const DialogContainer = ({ width, height, ...restProps }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -34,7 +34,7 @@ const DialogContainer = ({ width, height, ...restProps }) => {
|
||||
width: width || DDS.isTab ? 500 : "85%",
|
||||
maxHeight: height || 450,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { Text, View, ViewStyle } from "react-native";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import { PressableButtonProps } from "../ui/pressable";
|
||||
@@ -54,7 +54,7 @@ const DialogHeader = ({
|
||||
titlePart,
|
||||
style
|
||||
}: DialogHeaderProps) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -86,7 +86,7 @@ const DialogHeader = ({
|
||||
>
|
||||
{title}{" "}
|
||||
{titlePart ? (
|
||||
<Text style={{ color: colors.accent }}>{titlePart}</Text>
|
||||
<Text style={{ color: colors.primary.accent }}>{titlePart}</Text>
|
||||
) : null}
|
||||
</Heading>
|
||||
|
||||
@@ -114,7 +114,7 @@ const DialogHeader = ({
|
||||
maxWidth: centered ? "90%" : "100%",
|
||||
alignSelf: centered ? "center" : "flex-start"
|
||||
}}
|
||||
color={paragraphColor || colors.icon}
|
||||
color={paragraphColor || colors.secondary.paragraph}
|
||||
>
|
||||
{paragraph}
|
||||
</Paragraph>
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
|
||||
import { sleep } from "../../utils/time";
|
||||
@@ -38,7 +38,7 @@ import { useCallback } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export const Dialog = ({ context = "global" }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [inputValue, setInputValue] = useState(null);
|
||||
const inputRef = useRef();
|
||||
@@ -51,7 +51,7 @@ export const Dialog = ({ context = "global" }) => {
|
||||
onClose: () => {},
|
||||
positiveType: "transparent",
|
||||
icon: null,
|
||||
paragraphColor: colors.pri,
|
||||
paragraphColor: colors.primary.paragraph,
|
||||
input: false,
|
||||
inputPlaceholder: "Enter some text",
|
||||
defaultValue: "",
|
||||
@@ -115,7 +115,7 @@ export const Dialog = ({ context = "global" }) => {
|
||||
width: DDS.isTab ? 400 : "85%",
|
||||
maxHeight: 450,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useMessageStore } from "../../../stores/use-message-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { getElevationStyle } from "../../../utils/elevation";
|
||||
import {
|
||||
eCloseJumpToDialog,
|
||||
@@ -41,10 +41,10 @@ import { useCallback } from "react";
|
||||
const offsets = [];
|
||||
let timeout = null;
|
||||
const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const notes = data;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [currentIndex, setCurrentIndex] = useState(null);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
const onPress = (item) => {
|
||||
let ind = notes.findIndex(
|
||||
@@ -77,7 +77,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
||||
}
|
||||
timeout = setTimeout(() => {
|
||||
let index = offsets.findIndex((o, i) => o <= y && offsets[i + 1] > y);
|
||||
setCurrentIndex(index);
|
||||
setCurrentIndex(index || 0);
|
||||
}, 200);
|
||||
};
|
||||
|
||||
@@ -113,7 +113,6 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
||||
offsets.push(offset);
|
||||
});
|
||||
}, [notes]);
|
||||
|
||||
return !visible ? null : (
|
||||
<BaseDialog
|
||||
onShow={() => {
|
||||
@@ -126,7 +125,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
||||
style={{
|
||||
...getElevationStyle(5),
|
||||
width: DDS.isTab ? 500 : "85%",
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
zIndex: 100,
|
||||
bottom: 20,
|
||||
maxHeight: "65%",
|
||||
@@ -157,7 +156,7 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
||||
<PressableButton
|
||||
key={item.title}
|
||||
onPress={() => onPress(item, index)}
|
||||
type={currentIndex === index ? "accent" : "transparent"}
|
||||
type={currentIndex === index ? "selected" : "transparent"}
|
||||
customStyle={{
|
||||
minWidth: "20%",
|
||||
width: null,
|
||||
@@ -171,7 +170,9 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
|
||||
<Paragraph
|
||||
size={SIZE.sm}
|
||||
color={
|
||||
currentIndex === index ? colors.light : colors.accent
|
||||
currentIndex === index
|
||||
? colors.selected.accent
|
||||
: colors.primary.accent
|
||||
}
|
||||
style={{
|
||||
textAlign: "center"
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import { ProgressBarComponent } from "../../ui/svg/lazy";
|
||||
import { useEffect } from "react";
|
||||
@@ -30,7 +30,7 @@ import { useState } from "react";
|
||||
import { eCloseLoading, eOpenLoading } from "../../../utils/events";
|
||||
|
||||
export const LoadingDialog = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -58,7 +58,7 @@ export const LoadingDialog = () => {
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
@@ -77,8 +77,8 @@ export const LoadingDialog = () => {
|
||||
useNativeDriver
|
||||
indeterminate
|
||||
indeterminateAnimationDuration={2000}
|
||||
unfilledColor={colors.nav}
|
||||
color={colors.accent}
|
||||
unfilledColor={colors.secondary.background}
|
||||
color={colors.primary.accent}
|
||||
borderWidth={0}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Dialog } from "../../dialog";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
@@ -64,7 +64,7 @@ const usePDFSnapshot = (attachment) => {
|
||||
};
|
||||
|
||||
const PDFPreview = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [pdfSource, setPDFSource] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -172,7 +172,7 @@ const PDFPreview = () => {
|
||||
>
|
||||
<ProgressBarComponent
|
||||
indeterminate={!progress}
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
borderColor="transparent"
|
||||
progress={parseInt(progress?.value || "100") / 100}
|
||||
/>
|
||||
@@ -180,7 +180,7 @@ const PDFPreview = () => {
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
color={colors.light}
|
||||
color={colors.static.white}
|
||||
>
|
||||
Loading {`${progress?.percent ? `(${progress?.percent})` : ""}`}
|
||||
... Please wait
|
||||
@@ -205,7 +205,7 @@ const PDFPreview = () => {
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.light}
|
||||
color={colors.static.white}
|
||||
name="arrow-left"
|
||||
onPress={close}
|
||||
customStyle={{
|
||||
@@ -226,7 +226,7 @@ const PDFPreview = () => {
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
style={{
|
||||
color: colors.pri,
|
||||
color: colors.primary.paragraph,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
@@ -234,7 +234,7 @@ const PDFPreview = () => {
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
@@ -249,7 +249,7 @@ const PDFPreview = () => {
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.light}>/{numPages}</Paragraph>
|
||||
<Paragraph color={colors.static.white}>/{numPages}</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
@@ -258,7 +258,7 @@ const PDFPreview = () => {
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.light}
|
||||
color={colors.static.white}
|
||||
name="download"
|
||||
onPress={() => {
|
||||
downloadAttachment(attachment.metadata.hash, false);
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { getElevationStyle } from "../../../utils/elevation";
|
||||
import { eCloseResultDialog, eOpenResultDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
@@ -36,7 +36,7 @@ import Paragraph from "../../ui/typography/paragraph";
|
||||
import { ProFeatures } from "./pro-features";
|
||||
|
||||
const ResultDialog = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [dialogData, setDialogData] = useState({
|
||||
title: "Thank you for signing up!",
|
||||
@@ -72,7 +72,7 @@ const ResultDialog = () => {
|
||||
width: DDS.isTab ? 350 : "85%",
|
||||
maxHeight: 500,
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 20,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
@@ -94,7 +94,7 @@ const ResultDialog = () => {
|
||||
</Heading>
|
||||
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
size={SIZE.md}
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
@@ -121,7 +121,7 @@ const ResultDialog = () => {
|
||||
<Seperator />
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: "100%",
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
|
||||
@@ -21,7 +21,7 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import {
|
||||
eCloseSheet,
|
||||
eCloseResultDialog,
|
||||
@@ -31,7 +31,7 @@ import { SIZE } from "../../../utils/size";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
export const ProFeatures = ({ count = 6 }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -48,7 +48,7 @@ export const ProFeatures = ({ count = 6 }) => {
|
||||
content: "Instantly sync to unlimited devices"
|
||||
},
|
||||
{
|
||||
content: "A private vault to keep everything imporant always locked"
|
||||
content: "A private vault to keep everything important always locked"
|
||||
},
|
||||
{
|
||||
content:
|
||||
@@ -73,7 +73,7 @@ export const ProFeatures = ({ count = 6 }) => {
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
>
|
||||
<Icon size={SIZE.lg} color={colors.accent} name="check" />
|
||||
<Icon size={SIZE.lg} color={colors.primary.accent} name="check" />
|
||||
<Paragraph style={{ marginLeft: 5, flexShrink: 1 }}>
|
||||
{item.content}
|
||||
</Paragraph>
|
||||
@@ -89,7 +89,7 @@ export const ProFeatures = ({ count = 6 }) => {
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textDecorationLine: "underline",
|
||||
color: colors.icon
|
||||
color: colors.secondary.paragraph
|
||||
}}
|
||||
>
|
||||
See all features included in Notesnook Pro
|
||||
|
||||
@@ -442,6 +442,7 @@ export class VaultDialog extends Component {
|
||||
if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
|
||||
await this._enrollFingerprint(this.password);
|
||||
}
|
||||
|
||||
if (this.state.goToEditor) {
|
||||
this._openInEditor(note);
|
||||
} else if (this.state.share) {
|
||||
@@ -452,7 +453,6 @@ export class VaultDialog extends Component {
|
||||
await this._copyNote(note);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
this._takeErrorAction(e);
|
||||
}
|
||||
};
|
||||
@@ -467,44 +467,40 @@ export class VaultDialog extends Component {
|
||||
}
|
||||
|
||||
async _enrollFingerprint(password) {
|
||||
try {
|
||||
this.setState(
|
||||
{
|
||||
loading: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
await db.vault.unlock(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"Please enter the correct vault password to enable biometrics.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
loading: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
await db.vault.unlock(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastEvent.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this.close();
|
||||
ToastEvent.show({
|
||||
heading: "Incorrect password",
|
||||
message:
|
||||
"Please enter the correct vault password to enable biometrics.",
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
this._takeErrorAction(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async _createVault() {
|
||||
@@ -543,7 +539,7 @@ export class VaultDialog extends Component {
|
||||
.remove(this.state.note.id, this.password)
|
||||
.then(() => {
|
||||
ToastEvent.show({
|
||||
heading: "Note permanantly unlocked.",
|
||||
heading: "Note permanently unlocked.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
@@ -682,7 +678,7 @@ export class VaultDialog extends Component {
|
||||
...getElevationStyle(5),
|
||||
width: DDS.isTab ? 350 : "85%",
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
}}
|
||||
>
|
||||
@@ -867,7 +863,12 @@ export class VaultDialog extends Component {
|
||||
icon="fingerprint"
|
||||
width="100%"
|
||||
title="Biometric unlocking"
|
||||
type={this.state.biometricUnlock ? "transparent" : "gray"}
|
||||
iconColor={
|
||||
this.state.biometricUnlock
|
||||
? colors.selected.icon
|
||||
: colors.primary.icon
|
||||
}
|
||||
type={this.state.biometricUnlock ? "transparent" : "selected"}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -39,9 +39,9 @@ class ExceptionHandler extends React.Component<{
|
||||
error: Error | null;
|
||||
hasError: boolean;
|
||||
} = {
|
||||
hasError: false,
|
||||
error: null
|
||||
};
|
||||
hasError: false,
|
||||
error: null
|
||||
};
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, error: error };
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class ExceptionHandler extends React.Component<{
|
||||
this.props.component
|
||||
)}
|
||||
defaultTitle={this.state.error?.message}
|
||||
issueTitle="An exception occured"
|
||||
issueTitle="An exception occurred"
|
||||
/>
|
||||
<Dialog />
|
||||
</SafeAreaView>
|
||||
|
||||
@@ -27,14 +27,14 @@ import {
|
||||
} from "../../services/event-manager";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { LeftMenus } from "./left-menus";
|
||||
import { RightMenus } from "./right-menus";
|
||||
import { Title } from "./title";
|
||||
|
||||
const _Header = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [hide, setHide] = useState(true);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
@@ -43,7 +43,7 @@ const _Header = () => {
|
||||
);
|
||||
|
||||
const onScroll = useCallback(
|
||||
(data) => {
|
||||
(data: { x: number; y: number }) => {
|
||||
if (data.y > 150) {
|
||||
if (!hide) return;
|
||||
setHide(false);
|
||||
@@ -69,10 +69,12 @@ const _Header = () => {
|
||||
styles.container,
|
||||
{
|
||||
marginTop: Platform.OS === "android" ? insets.top : null,
|
||||
backgroundColor: colors.bg,
|
||||
backgroundColor: colors.primary.background,
|
||||
overflow: "hidden",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: hide ? "transparent" : colors.nav,
|
||||
borderBottomColor: hide
|
||||
? "transparent"
|
||||
: colors.secondary.background,
|
||||
justifyContent: "space-between"
|
||||
}
|
||||
]}
|
||||
@@ -23,12 +23,12 @@ import { DDS } from "../../services/device-detection";
|
||||
import Navigation from "../../services/navigation";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
export const LeftMenus = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const canGoBack = useNavigationStore((state) => state.canGoBack);
|
||||
const isTablet = deviceMode === "tablet";
|
||||
@@ -43,12 +43,6 @@ export const LeftMenus = () => {
|
||||
return;
|
||||
}
|
||||
Navigation.goBack();
|
||||
if (
|
||||
useNavigationStore.getState().currentScreen.name === "Signup" ||
|
||||
useNavigationStore.getState().currentScreen.name === "Login"
|
||||
) {
|
||||
tabBarRef.current.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
return isTablet && !canGoBack ? null : (
|
||||
@@ -61,17 +55,17 @@ export const LeftMenus = () => {
|
||||
width: 40,
|
||||
borderRadius: 100,
|
||||
marginLeft: -5,
|
||||
marginRight: DDS.isLargeTablet() ? 10 : 25
|
||||
marginRight: DDS.isLargeTablet() ? 10 : 7
|
||||
}}
|
||||
left={40}
|
||||
top={40}
|
||||
right={DDS.isLargeTablet() ? 10 : 25}
|
||||
right={DDS.isLargeTablet() ? 10 : 10}
|
||||
onPress={onLeftButtonPress}
|
||||
onLongPress={() => {
|
||||
Navigation.popToTop();
|
||||
}}
|
||||
name={canGoBack ? "arrow-left" : "menu"}
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
iconStyle={{
|
||||
marginLeft: canGoBack ? -5 : 0
|
||||
}}
|
||||
@@ -19,25 +19,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useRef } from "react";
|
||||
import { Platform, StyleSheet, View } from "react-native";
|
||||
//@ts-ignore
|
||||
import Menu from "react-native-reanimated-material-menu";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import Navigation from "../../services/navigation";
|
||||
import SearchService from "../../services/search";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
|
||||
export const RightMenus = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const { colors: contextMenuColors } = useThemeColors("contextMenu");
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const rightButtons = useNavigationStore((state) => state.headerRightButtons);
|
||||
const buttons = useNavigationStore((state) => state.headerRightButtons);
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
|
||||
const buttonAction = useNavigationStore((state) => state.buttonAction);
|
||||
const menuRef = useRef();
|
||||
const menuRef = useRef<{
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
}>();
|
||||
|
||||
return (
|
||||
<View style={styles.rightBtnContainer}>
|
||||
@@ -45,13 +50,16 @@ export const RightMenus = () => {
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
SearchService.prepareSearch();
|
||||
Navigation.navigate({
|
||||
name: "Search"
|
||||
});
|
||||
Navigation.navigate(
|
||||
{
|
||||
name: "Search"
|
||||
},
|
||||
{}
|
||||
);
|
||||
}}
|
||||
testID="icon-search"
|
||||
name="magnify"
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
customStyle={styles.rightBtn}
|
||||
/>
|
||||
) : null}
|
||||
@@ -76,34 +84,34 @@ export const RightMenus = () => {
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.accent
|
||||
borderColor: colors.primary.accent
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{rightButtons && rightButtons.length > 0 ? (
|
||||
{buttons && buttons.length > 0 ? (
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg
|
||||
backgroundColor: contextMenuColors.primary.background
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
button={
|
||||
anchor={
|
||||
<IconButton
|
||||
onPress={() => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
name="dots-vertical"
|
||||
color={colors.pri}
|
||||
color={colors.primary.paragraph}
|
||||
customStyle={styles.rightBtn}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{rightButtons.map((item) => (
|
||||
{buttons.map((item) => (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
@@ -112,7 +120,7 @@ export const RightMenus = () => {
|
||||
}}
|
||||
type="gray"
|
||||
buttonType={{
|
||||
text: colors.pri
|
||||
text: contextMenuColors.primary.paragraph
|
||||
}}
|
||||
key={item.title}
|
||||
title={item.title}
|
||||
@@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import Notebook from "../../screens/notebook";
|
||||
import {
|
||||
@@ -26,28 +27,26 @@ import {
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { eScrollEvent } from "../../utils/events";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import { useCallback } from "react";
|
||||
import Tag from "../ui/tag";
|
||||
import Heading from "../ui/typography/heading";
|
||||
|
||||
const titleState = {};
|
||||
const titleState: { [name: string]: boolean } = {};
|
||||
|
||||
export const Title = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
const isNotebook = currentScreen.name === "Notebook";
|
||||
const isTopic = currentScreen?.name === "TopicNotes";
|
||||
const [hide, setHide] = useState(
|
||||
isNotebook
|
||||
? typeof titleState[currentScreen.id] === "boolean"
|
||||
? titleState[currentScreen.id]
|
||||
? typeof titleState[currentScreen.id as string] === "boolean"
|
||||
? titleState[currentScreen.id as string]
|
||||
: true
|
||||
: false
|
||||
);
|
||||
const isHidden = titleState[currentScreen.id];
|
||||
const isHidden = titleState[currentScreen.id as string];
|
||||
const notebook =
|
||||
isTopic && currentScreen.notebookId
|
||||
? db.notebooks?.notebook(currentScreen.notebookId)?.data
|
||||
@@ -56,18 +55,18 @@ export const Title = () => {
|
||||
const isTag = currentScreen?.name === "TaggedNotes";
|
||||
|
||||
const onScroll = useCallback(
|
||||
(data) => {
|
||||
(data: { x: number; y: number }) => {
|
||||
if (currentScreen.name !== "Notebook") {
|
||||
setHide(false);
|
||||
return;
|
||||
}
|
||||
if (data.y > 150) {
|
||||
if (!hide) return;
|
||||
titleState[currentScreen.id] = false;
|
||||
titleState[currentScreen.id as string] = false;
|
||||
setHide(false);
|
||||
} else {
|
||||
if (hide) return;
|
||||
titleState[currentScreen.id] = true;
|
||||
titleState[currentScreen.id as string] = true;
|
||||
setHide(true);
|
||||
}
|
||||
},
|
||||
@@ -76,13 +75,13 @@ export const Title = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (currentScreen.name === "Notebook") {
|
||||
let value =
|
||||
typeof titleState[currentScreen.id] === "boolean"
|
||||
? titleState[currentScreen.id]
|
||||
const value =
|
||||
typeof titleState[currentScreen.id as string] === "boolean"
|
||||
? titleState[currentScreen.id as string]
|
||||
: true;
|
||||
setHide(value);
|
||||
} else {
|
||||
setHide(titleState[currentScreen.id]);
|
||||
setHide(titleState[currentScreen.id as string]);
|
||||
}
|
||||
}, [currentScreen.id, currentScreen.name]);
|
||||
|
||||
@@ -98,34 +97,34 @@ export const Title = () => {
|
||||
Notebook.navigate(notebook, true);
|
||||
}
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
opacity: 1,
|
||||
flexShrink: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{!hide && !isHidden ? (
|
||||
<Heading
|
||||
onPress={navigateToNotebook}
|
||||
numberOfLines={1}
|
||||
size={SIZE.xl}
|
||||
size={SIZE.lg}
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
marginTop: Platform.OS === "ios" ? -1 : 0
|
||||
}}
|
||||
color={currentScreen.color || colors.heading}
|
||||
color={currentScreen.color || colors.primary.heading}
|
||||
>
|
||||
{isTag ? (
|
||||
<Heading size={SIZE.xl} color={colors.accent}>
|
||||
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
||||
#
|
||||
</Heading>
|
||||
) : null}
|
||||
{title}
|
||||
{title}{" "}
|
||||
<Tag
|
||||
visible={currentScreen.beta}
|
||||
text="BETA"
|
||||
style={{
|
||||
backgroundColor: "transparent"
|
||||
}}
|
||||
textColor={colors.primary.accent}
|
||||
/>
|
||||
</Heading>
|
||||
) : null}
|
||||
<Tag visible={currentScreen.beta} text="BETA" />
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -17,6 +17,9 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import ImageViewer from "react-native-image-zoom-viewer";
|
||||
@@ -26,15 +29,13 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import dataurl from "@notesnook/core/utils/dataurl";
|
||||
|
||||
const ImagePreview = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors("dialog");
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [image, setImage] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -97,7 +98,7 @@ const ImagePreview = () => {
|
||||
>
|
||||
<ProgressBarComponent
|
||||
indeterminate
|
||||
color={colors.accent}
|
||||
color={colors.primary.accent}
|
||||
borderColor="transparent"
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -17,32 +17,34 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import {
|
||||
Linking,
|
||||
ScrollView,
|
||||
TouchableOpacity,
|
||||
useWindowDimensions,
|
||||
View
|
||||
} from "react-native";
|
||||
import { SwiperFlatList } from "react-native-swiper-flatlist";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
|
||||
const Intro = ({ navigation }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const isTelemetryEnabled = useSettingStore(
|
||||
(state) => state.settings.telemetry
|
||||
);
|
||||
const { width } = useWindowDimensions();
|
||||
const { width, height } = useWindowDimensions();
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const renderItem = React.useCallback(
|
||||
({ item }) => (
|
||||
<View
|
||||
@@ -62,7 +64,7 @@ const Intro = ({ navigation }) => {
|
||||
style={{
|
||||
width: 100,
|
||||
height: 5,
|
||||
backgroundColor: colors.accent,
|
||||
backgroundColor: colors.primary.accent,
|
||||
borderRadius: 2,
|
||||
marginRight: 7
|
||||
}}
|
||||
@@ -72,7 +74,7 @@ const Intro = ({ navigation }) => {
|
||||
style={{
|
||||
width: 20,
|
||||
height: 5,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
@@ -80,7 +82,6 @@ const Intro = ({ navigation }) => {
|
||||
<View
|
||||
style={{
|
||||
marginTop: 10,
|
||||
marginBottom: 20,
|
||||
maxWidth: "90%",
|
||||
width: "100%"
|
||||
}}
|
||||
@@ -116,30 +117,31 @@ const Intro = ({ navigation }) => {
|
||||
</View>
|
||||
</View>
|
||||
),
|
||||
[colors.accent, colors.nav, deviceMode, width]
|
||||
[colors.primary.accent, colors.secondary.background, deviceMode, width]
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
<ScrollView
|
||||
testID="notesnook.splashscreen"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 0.65,
|
||||
width: deviceMode !== "mobile" ? width / 2 : "100%",
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
marginBottom: 20,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.border,
|
||||
borderBottomColor: colors.primary.border,
|
||||
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
|
||||
borderWidth: deviceMode !== "mobile" ? 1 : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.border : null,
|
||||
borderColor: deviceMode !== "mobile" ? colors.primary.border : null,
|
||||
borderRadius: deviceMode !== "mobile" ? 20 : null,
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null
|
||||
marginTop: deviceMode !== "mobile" ? 50 : null,
|
||||
paddingTop: insets.top + 10,
|
||||
paddingBottom: insets.top + 10,
|
||||
minHeight: height * 0.7
|
||||
}}
|
||||
>
|
||||
<SwiperFlatList
|
||||
@@ -169,14 +171,14 @@ const Intro = ({ navigation }) => {
|
||||
user: "@andrewsayer on Twitter"
|
||||
}
|
||||
]}
|
||||
paginationActiveColor={colors.accent}
|
||||
paginationActiveColor={colors.primary.accent}
|
||||
paginationStyleItem={{
|
||||
width: 10,
|
||||
height: 5,
|
||||
marginRight: 4,
|
||||
marginLeft: 4
|
||||
}}
|
||||
paginationDefaultColor={colors.border}
|
||||
paginationDefaultColor={colors.primary.border}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
</View>
|
||||
@@ -184,8 +186,8 @@ const Intro = ({ navigation }) => {
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flex: 0.3,
|
||||
justifyContent: "center"
|
||||
justifyContent: "center",
|
||||
minHeight: height * 0.3
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@@ -227,7 +229,9 @@ const Intro = ({ navigation }) => {
|
||||
name={
|
||||
isTelemetryEnabled ? "checkbox-marked" : "checkbox-blank-outline"
|
||||
}
|
||||
color={isTelemetryEnabled ? colors.accent : colors.icon}
|
||||
color={
|
||||
isTelemetryEnabled ? colors.primary.accent : colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<Paragraph
|
||||
@@ -242,7 +246,7 @@ const Intro = ({ navigation }) => {
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,9 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useMessageStore } from "../../../stores/use-message-store";
|
||||
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||
import { ColorValues } from "../../../utils/colors";
|
||||
import { Announcement } from "../../announcements/announcement";
|
||||
import { Card } from "../../list/card";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
@@ -37,7 +37,7 @@ export const Header = React.memo(
|
||||
noAnnouncement,
|
||||
warning
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const announcements = useMessageStore((state) => state.announcements);
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
|
||||
@@ -47,7 +47,7 @@ export const Header = React.memo(
|
||||
<View
|
||||
style={{
|
||||
padding: 12,
|
||||
backgroundColor: colors.errorBg,
|
||||
backgroundColor: colors.error.background,
|
||||
width: "95%",
|
||||
alignSelf: "center",
|
||||
borderRadius: 5,
|
||||
@@ -55,13 +55,18 @@ export const Header = React.memo(
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Icon name="sync-alert" size={SIZE.md} color={colors.red} f />
|
||||
<Paragraph style={{ marginLeft: 5 }} color={colors.red}>
|
||||
<Icon
|
||||
name="sync-alert"
|
||||
size={SIZE.md}
|
||||
color={colors.error.icon}
|
||||
f
|
||||
/>
|
||||
<Paragraph style={{ marginLeft: 5 }} color={colors.error.icon}>
|
||||
{warning.title}
|
||||
</Paragraph>
|
||||
</View>
|
||||
) : announcements.length !== 0 && !noAnnouncement ? (
|
||||
<Announcement color={color || colors.accent} />
|
||||
<Announcement color={color || colors.primary.accent} />
|
||||
) : type === "search" ? null : !shouldShow ? (
|
||||
<View
|
||||
style={{
|
||||
@@ -74,7 +79,9 @@ export const Header = React.memo(
|
||||
>
|
||||
{messageCard ? (
|
||||
<Card
|
||||
color={COLORS_NOTE[color?.toLowerCase()] || colors.accent}
|
||||
color={
|
||||
ColorValues[color?.toLowerCase()] || colors.primary.accent
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useRef, useState } from "react";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useMenuStore } from "../../../stores/use-menu-store";
|
||||
import { ToastEvent } from "../../../services/event-manager";
|
||||
import { getTotalNotes } from "@notesnook/common";
|
||||
@@ -31,7 +31,7 @@ import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const { colors } = useThemeColors();
|
||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
||||
db.shortcuts.exists(notebook.id)
|
||||
);
|
||||
@@ -75,7 +75,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
paddingTop: 25
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||
{new Date(notebook.dateEdited).toLocaleString()}
|
||||
</Paragraph>
|
||||
<View
|
||||
@@ -110,7 +110,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
height: 40
|
||||
}}
|
||||
type={isPinnedToMenu ? "grayBg" : "grayBg"}
|
||||
color={isPinnedToMenu ? colors.accent : colors.icon}
|
||||
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
<IconButton
|
||||
@@ -119,7 +119,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
tooltipText="Edit this notebook"
|
||||
name="pencil"
|
||||
type="grayBg"
|
||||
color={colors.icon}
|
||||
color={colors.primary.icon}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
@@ -129,7 +129,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
</View>
|
||||
|
||||
{notebook.description ? (
|
||||
<Paragraph size={SIZE.md} color={colors.pri}>
|
||||
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
|
||||
{notebook.description}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
@@ -141,7 +141,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
|
||||
fontFamily: null
|
||||
}}
|
||||
size={SIZE.xs}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{notebook.topics.length === 1
|
||||
? "1 topic"
|
||||
|
||||
@@ -17,35 +17,25 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useRef } from "react";
|
||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { ColorValues } from "../../../utils/colors";
|
||||
import { GROUP } from "../../../utils/constants";
|
||||
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||
import { db } from "../../../common/database";
|
||||
import { eOpenJumpToDialog } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Button } from "../../ui/button";
|
||||
import Sort from "../../sheets/sort";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export const SectionHeader = React.memo(
|
||||
function SectionHeader({ item, index, type, color, screen }) {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
function SectionHeader({ item, index, type, color, screen, groupOptions }) {
|
||||
const { colors } = useThemeColors();
|
||||
const { fontScale } = useWindowDimensions();
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions(type)
|
||||
);
|
||||
let groupBy = Object.keys(GROUP).find(
|
||||
(key) => GROUP[key] === groupOptions.groupBy
|
||||
);
|
||||
@@ -65,17 +55,6 @@ export const SectionHeader = React.memo(
|
||||
? "Default"
|
||||
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
|
||||
|
||||
const onUpdate = useCallback(() => {
|
||||
setGroupOptions({ ...db.settings?.getGroupOptions(type) });
|
||||
}, [type]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
};
|
||||
}, [onUpdate]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -85,7 +64,7 @@ export const SectionHeader = React.memo(
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 12,
|
||||
height: 35 * fontScale,
|
||||
backgroundColor: colors.nav,
|
||||
backgroundColor: colors.secondary.background,
|
||||
alignSelf: "center",
|
||||
borderRadius: 5,
|
||||
marginVertical: 5
|
||||
@@ -104,7 +83,7 @@ export const SectionHeader = React.memo(
|
||||
}}
|
||||
>
|
||||
<Heading
|
||||
color={COLORS_NOTE[color?.toLowerCase()] || colors.accent}
|
||||
color={ColorValues[color?.toLowerCase()] || colors.primary.accent}
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
minWidth: 60,
|
||||
@@ -168,7 +147,7 @@ export const SectionHeader = React.memo(
|
||||
: "Switch to compact mode"
|
||||
}
|
||||
fwdRef={compactModeRef}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.icon}
|
||||
name={listMode == "compact" ? "view-list" : "view-list-outline"}
|
||||
onPress={() => {
|
||||
let settings = {};
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getUpcomingReminder } from "@notesnook/core/collections/reminders";
|
||||
import { getUpcomingReminder } from "@notesnook/core/dist/collections/reminders";
|
||||
import { decode, EntityLevel } from "entities";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
@@ -29,9 +29,8 @@ import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { COLORS_NOTE } from "../../../utils/color-scheme";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { ColorValues } from "../../../utils/colors";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { Button } from "../../ui/button";
|
||||
@@ -40,6 +39,8 @@ import { ReminderTime } from "../../ui/reminder-time";
|
||||
import { TimeSince } from "../../ui/time-since";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
|
||||
function navigateToTag(item) {
|
||||
const tag = db.tags.tag(item.id);
|
||||
@@ -98,19 +99,21 @@ const NoteItem = ({
|
||||
dateBy = "dateCreated",
|
||||
noOpen = false
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const notesListMode = useSettingStore(
|
||||
(state) => state.settings.notesListMode
|
||||
const isEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote === item.id
|
||||
);
|
||||
const compactMode = notesListMode === "compact";
|
||||
const { colors } = useThemeColors();
|
||||
const compactMode = useIsCompactModeEnabled(item);
|
||||
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
|
||||
const _update = useRelationStore((state) => state.updater);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
|
||||
const reminders = db.relations.from(item, "reminder");
|
||||
const reminder = getUpcomingReminder(reminders);
|
||||
const noteColor = COLORS_NOTE[item.color?.toLowerCase()];
|
||||
const noteColor = ColorValues[item.color?.toLowerCase()];
|
||||
const tags = getTags(item);
|
||||
const primaryColors = isEditingNote ? colors.selected : colors.primary;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
@@ -151,7 +154,7 @@ const NoteItem = ({
|
||||
borderRadius: 5,
|
||||
marginRight: 5,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.icon,
|
||||
borderColor: primaryColors.border,
|
||||
paddingHorizontal: 6,
|
||||
marginBottom: 5
|
||||
}}
|
||||
@@ -181,7 +184,9 @@ const NoteItem = ({
|
||||
{compactMode ? (
|
||||
<Paragraph
|
||||
numberOfLines={1}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||
color={
|
||||
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
|
||||
}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
@@ -192,7 +197,9 @@ const NoteItem = ({
|
||||
) : (
|
||||
<Heading
|
||||
numberOfLines={1}
|
||||
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
|
||||
color={
|
||||
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
|
||||
}
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
@@ -207,6 +214,7 @@ const NoteItem = ({
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
color={primaryColors.paragraph}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{decode(item.headline, {
|
||||
@@ -235,13 +243,13 @@ const NoteItem = ({
|
||||
marginRight: 6
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
color={colors.red}
|
||||
color={colors.error.accent}
|
||||
/>
|
||||
) : null}
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.icon,
|
||||
color: colors.secondary.paragraph,
|
||||
marginRight: 6
|
||||
}}
|
||||
time={item[dateBy]}
|
||||
@@ -261,9 +269,12 @@ const NoteItem = ({
|
||||
<Icon
|
||||
name="attachment"
|
||||
size={SIZE.md}
|
||||
color={colors.icon}
|
||||
color={primaryColors.icon}
|
||||
/>
|
||||
<Paragraph color={colors.icon} size={SIZE.xs}>
|
||||
<Paragraph
|
||||
color={colors.secondary.paragraph}
|
||||
size={SIZE.xs}
|
||||
>
|
||||
{attachmentCount}
|
||||
</Paragraph>
|
||||
</View>
|
||||
@@ -278,7 +289,8 @@ const NoteItem = ({
|
||||
marginRight: 6
|
||||
}}
|
||||
color={
|
||||
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
|
||||
ColorValues[item.color?.toLowerCase()] ||
|
||||
primaryColors.accent
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
@@ -291,7 +303,7 @@ const NoteItem = ({
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={colors.icon}
|
||||
color={primaryColors.icon}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -336,7 +348,7 @@ const NoteItem = ({
|
||||
) : (
|
||||
<>
|
||||
<Paragraph
|
||||
color={colors.icon}
|
||||
color={colors.secondary.paragraph}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
@@ -349,7 +361,7 @@ const NoteItem = ({
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph
|
||||
color={colors.accent}
|
||||
color={primaryColors.accent}
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
marginRight: 6
|
||||
@@ -377,7 +389,7 @@ const NoteItem = ({
|
||||
marginRight: 6
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
color={colors.red}
|
||||
color={colors.error.accent}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -389,7 +401,7 @@ const NoteItem = ({
|
||||
style={{
|
||||
marginRight: 6
|
||||
}}
|
||||
color={colors.icon}
|
||||
color={colors.secondary.background}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -408,7 +420,7 @@ const NoteItem = ({
|
||||
<TimeSince
|
||||
style={{
|
||||
fontSize: SIZE.xs,
|
||||
color: colors.icon,
|
||||
color: colors.secondary.paragraph,
|
||||
marginRight: 6
|
||||
}}
|
||||
time={item[dateBy]}
|
||||
@@ -419,7 +431,7 @@ const NoteItem = ({
|
||||
|
||||
<IconButton
|
||||
testID={notesnook.listitem.menu}
|
||||
color={colors.pri}
|
||||
color={primaryColors.paragraph}
|
||||
name="dots-horizontal"
|
||||
size={SIZE.xl}
|
||||
onPress={() => !noOpen && showActionSheet(item, isTrash)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user