Compare commits
1349 Commits
v3.0.6-bet
...
gen_stack
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb42a2e1a4 | ||
|
|
12c5b6d227 | ||
|
|
364ae51e36 | ||
|
|
18947e0c93 | ||
|
|
3880db4339 | ||
|
|
ae5b0b6fcc | ||
|
|
2c729f90cc | ||
|
|
800514d769 | ||
|
|
b03b9482dc | ||
|
|
9019490dbd | ||
|
|
4a3e23c288 | ||
|
|
1800ebea2d | ||
|
|
a708c9e9eb | ||
|
|
2aeb875fec | ||
|
|
a8164e9f6c | ||
|
|
0ee8e738e5 | ||
|
|
8fdef2ac7d | ||
|
|
920b1d91f3 | ||
|
|
0ad382aced | ||
|
|
c8aa8e37cf | ||
|
|
9a37f7c506 | ||
|
|
717750d5d4 | ||
|
|
1274ecc3b5 | ||
|
|
4c2ad4567c | ||
|
|
ebc01af34a | ||
|
|
e66d8eee13 | ||
|
|
55e6a4b65a | ||
|
|
0aa1db584d | ||
|
|
a0e6266d28 | ||
|
|
bd0aaed863 | ||
|
|
9410c42f29 | ||
|
|
80186c1812 | ||
|
|
37dfda5c54 | ||
|
|
ec83517dc5 | ||
|
|
2eb4911592 | ||
|
|
cd0a6a8fcb | ||
|
|
b15f192244 | ||
|
|
406cb663c9 | ||
|
|
301a30a50a | ||
|
|
81e3432143 | ||
|
|
c43c2936ac | ||
|
|
fdea1807d2 | ||
|
|
5439578fcf | ||
|
|
e05e52f870 | ||
|
|
1c6243d12e | ||
|
|
a51dfb5b6e | ||
|
|
c98bddd35b | ||
|
|
1eb80ccc88 | ||
|
|
5a346aa7de | ||
|
|
910095a769 | ||
|
|
119059befa | ||
|
|
a8f5dea315 | ||
|
|
ca0a4cb02c | ||
|
|
a04cd6b8dc | ||
|
|
3e4ab0f6df | ||
|
|
a7fe1aca44 | ||
|
|
ff0188f602 | ||
|
|
c846f8abc8 | ||
|
|
8d8acfb99d | ||
|
|
3a5b5772ad | ||
|
|
8046932e4e | ||
|
|
c45aa84739 | ||
|
|
d98be849e3 | ||
|
|
727ac6d907 | ||
|
|
8302f8ba4c | ||
|
|
4db8b0db01 | ||
|
|
3f1614ab9a | ||
|
|
909d8c35f8 | ||
|
|
a73780f0b0 | ||
|
|
286fdbeec0 | ||
|
|
59099552f8 | ||
|
|
0b6013be58 | ||
|
|
2d93a17f34 | ||
|
|
5ef7e39e17 | ||
|
|
f7de4c9084 | ||
|
|
0672c864f7 | ||
|
|
cb3706c150 | ||
|
|
08c6a99336 | ||
|
|
adf961fa08 | ||
|
|
0eb9ae47e9 | ||
|
|
aa706300a4 | ||
|
|
fd183a0e1e | ||
|
|
b27cd4be0f | ||
|
|
9575561832 | ||
|
|
02f96f8d14 | ||
|
|
3cf46e7ad1 | ||
|
|
93551aa0b7 | ||
|
|
d00c35bdaf | ||
|
|
6e6b793568 | ||
|
|
9e064d88c6 | ||
|
|
1658ad8d18 | ||
|
|
c94da3bdf0 | ||
|
|
1f0fc83a7c | ||
|
|
a6af92c330 | ||
|
|
ef93ecc481 | ||
|
|
039709822c | ||
|
|
25c861ce77 | ||
|
|
670f1570b5 | ||
|
|
c93d82d2d6 | ||
|
|
bced20031b | ||
|
|
f8bd089b51 | ||
|
|
2a4d04a26f | ||
|
|
a0c32191f9 | ||
|
|
662d59f60a | ||
|
|
13befe1947 | ||
|
|
21f8b6d557 | ||
|
|
aef53cace9 | ||
|
|
382b5b0240 | ||
|
|
a45b66d449 | ||
|
|
dd67b1a803 | ||
|
|
00568ae839 | ||
|
|
2b2ea5717c | ||
|
|
5672de8565 | ||
|
|
1c70ad8cec | ||
|
|
0c344a2146 | ||
|
|
38f69b514c | ||
|
|
b502f6a08f | ||
|
|
d68c3269d3 | ||
|
|
6d9af85e2a | ||
|
|
1d58cfea3c | ||
|
|
c1fa4d3400 | ||
|
|
df179056d6 | ||
|
|
26f9e08c23 | ||
|
|
f719bebe5a | ||
|
|
9693c8ea54 | ||
|
|
040a677895 | ||
|
|
df2708147e | ||
|
|
faba5f6385 | ||
|
|
eab5bccb99 | ||
|
|
ed399c0d0a | ||
|
|
3c7e962fe0 | ||
|
|
56614ef19d | ||
|
|
169318c549 | ||
|
|
bd6a1b9bff | ||
|
|
443d6625f9 | ||
|
|
b6f95ff7cd | ||
|
|
1b13da54a1 | ||
|
|
adf0f132fd | ||
|
|
73ec04b42d | ||
|
|
54a68e4406 | ||
|
|
bd73ae9df4 | ||
|
|
fe41f7b687 | ||
|
|
c9adaa2442 | ||
|
|
10be5771c6 | ||
|
|
43f4669360 | ||
|
|
3b04925e54 | ||
|
|
ba3959af9e | ||
|
|
552d86d9d2 | ||
|
|
096602150e | ||
|
|
e5795bcbb2 | ||
|
|
9be52a69ad | ||
|
|
5809384f62 | ||
|
|
fff70dce16 | ||
|
|
258140b1e0 | ||
|
|
0a00844e64 | ||
|
|
03cea85efc | ||
|
|
8a16ef9b85 | ||
|
|
c82c30b530 | ||
|
|
46599f66da | ||
|
|
d1b532a4ae | ||
|
|
0baf7afc25 | ||
|
|
a0595a7d23 | ||
|
|
1e6f6ab477 | ||
|
|
62d677f9ad | ||
|
|
8de98d98ab | ||
|
|
5b57614bfe | ||
|
|
ae7c538ad7 | ||
|
|
c6394c9e37 | ||
|
|
acf3740a68 | ||
|
|
8423543904 | ||
|
|
b90cb28637 | ||
|
|
780e1e9db0 | ||
|
|
ca6f8e5e4b | ||
|
|
4c4cda1c39 | ||
|
|
6fcbde772c | ||
|
|
eac1750b1c | ||
|
|
5eea179a58 | ||
|
|
fe1ea6dc27 | ||
|
|
cf1b774255 | ||
|
|
198fa33fc0 | ||
|
|
e722866d62 | ||
|
|
58845e5508 | ||
|
|
543c6f17ed | ||
|
|
533f95fcff | ||
|
|
c02f1cde16 | ||
|
|
043f55ead0 | ||
|
|
fcece06a1c | ||
|
|
da7d79edc6 | ||
|
|
a2e7190a4a | ||
|
|
503f2b5876 | ||
|
|
68d57e2b27 | ||
|
|
400aa574cd | ||
|
|
5028bb1275 | ||
|
|
27ae5bb5a2 | ||
|
|
83316fe299 | ||
|
|
8869224de2 | ||
|
|
c4152f0fc0 | ||
|
|
d803005637 | ||
|
|
53d896be2b | ||
|
|
8c37c6409a | ||
|
|
9195244890 | ||
|
|
152794ccd0 | ||
|
|
2e8b234b63 | ||
|
|
055c647698 | ||
|
|
2b7bcfe7c6 | ||
|
|
3c8b8a7bc9 | ||
|
|
fde45fb6de | ||
|
|
4a616a3e67 | ||
|
|
bd0a338dd6 | ||
|
|
5ab721e3e2 | ||
|
|
cb70bb9429 | ||
|
|
82cadd473c | ||
|
|
0e8ea3ddeb | ||
|
|
511acc5fca | ||
|
|
001cf8ee92 | ||
|
|
a965cd694b | ||
|
|
d3c1c1959b | ||
|
|
9a866a552a | ||
|
|
837c296821 | ||
|
|
9aed6faed8 | ||
|
|
734f491e8d | ||
|
|
0ea13024e9 | ||
|
|
cb27039245 | ||
|
|
714ac2deac | ||
|
|
d605bc29bf | ||
|
|
080101bc45 | ||
|
|
4819e17da3 | ||
|
|
69a5d5a1b6 | ||
|
|
68a6d6c313 | ||
|
|
c51ef4d5f6 | ||
|
|
d9c5c1f893 | ||
|
|
5a75ca73a9 | ||
|
|
626d393376 | ||
|
|
68d06ff3f8 | ||
|
|
79f20d100c | ||
|
|
9d5bec3bfb | ||
|
|
ac96354b4c | ||
|
|
63f44d6fbc | ||
|
|
f9bfa88c83 | ||
|
|
ef192f3d22 | ||
|
|
c9a8134265 | ||
|
|
cd7a5d2ec0 | ||
|
|
b92bc03fa9 | ||
|
|
5f5e472800 | ||
|
|
0951906cac | ||
|
|
6f0fe03760 | ||
|
|
159ce97e42 | ||
|
|
8a9848634c | ||
|
|
282705a683 | ||
|
|
c91b706ed0 | ||
|
|
16afc07b2f | ||
|
|
04f8be5a62 | ||
|
|
0e8a5ca1fa | ||
|
|
1491bf771e | ||
|
|
0f5ce465c3 | ||
|
|
647ff7ae0e | ||
|
|
45a552adef | ||
|
|
8e207d787d | ||
|
|
00754593ac | ||
|
|
b22770f1ae | ||
|
|
523da4e91f | ||
|
|
de6c8154a3 | ||
|
|
9ef32e8081 | ||
|
|
5f53593a26 | ||
|
|
1f404944d9 | ||
|
|
23dca36475 | ||
|
|
3dd8e20e9a | ||
|
|
8f412f9487 | ||
|
|
bf790219db | ||
|
|
c6af310b2d | ||
|
|
8d89b5dca9 | ||
|
|
fa35244607 | ||
|
|
d96815fa36 | ||
|
|
4a9f5b1d8b | ||
|
|
43fac79011 | ||
|
|
f2c62c8d8b | ||
|
|
70c9dd0083 | ||
|
|
ae6d8c5f2b | ||
|
|
38e2250fd1 | ||
|
|
17e2c5a8a2 | ||
|
|
2e226f0f72 | ||
|
|
167c54578b | ||
|
|
fb656af5ca | ||
|
|
ddfab272d2 | ||
|
|
28958311c1 | ||
|
|
f1b8275b4b | ||
|
|
c8c75d40dd | ||
|
|
bace90808d | ||
|
|
fe7c7e8db4 | ||
|
|
519a569366 | ||
|
|
89d602a40f | ||
|
|
a8db65b5af | ||
|
|
716239abfd | ||
|
|
62dd5a5416 | ||
|
|
96558c5df5 | ||
|
|
45997dd2e4 | ||
|
|
bde190f01c | ||
|
|
042c97688f | ||
|
|
95b5114070 | ||
|
|
20742e2a1c | ||
|
|
1e3149284e | ||
|
|
d77c09f1fd | ||
|
|
aa602ea8e9 | ||
|
|
8ab6b6dc31 | ||
|
|
404d136a73 | ||
|
|
cf9aeecd7e | ||
|
|
1adda96ce2 | ||
|
|
6dd6d818c9 | ||
|
|
c7e83961d4 | ||
|
|
03428e5848 | ||
|
|
d0fceed56d | ||
|
|
b8c68b5163 | ||
|
|
2496004896 | ||
|
|
16ec35de25 | ||
|
|
d248b8e2b8 | ||
|
|
641e7484e3 | ||
|
|
a46409ff44 | ||
|
|
5dbabc2706 | ||
|
|
2d4d656dd9 | ||
|
|
ab51b22e4a | ||
|
|
781d09daca | ||
|
|
f21533e5e4 | ||
|
|
2ea9b04863 | ||
|
|
675d517cbc | ||
|
|
c336c57bda | ||
|
|
7b5fa09c71 | ||
|
|
d85974ed7a | ||
|
|
9598ea7542 | ||
|
|
42048ad53a | ||
|
|
1fbd2efb92 | ||
|
|
fe91ef79a0 | ||
|
|
583d585b2f | ||
|
|
94b28f86ca | ||
|
|
f927f05f86 | ||
|
|
bde8dea048 | ||
|
|
511bea9b23 | ||
|
|
eda95530de | ||
|
|
5173decaa5 | ||
|
|
00dcb55f6e | ||
|
|
6dbe1073cd | ||
|
|
9be3e4fcfa | ||
|
|
f9f0a866bd | ||
|
|
daafeb2fa8 | ||
|
|
a92b34b81c | ||
|
|
e29b6838b3 | ||
|
|
16ff7c92e4 | ||
|
|
07e1ea6edf | ||
|
|
b26ede95fb | ||
|
|
971793a3ad | ||
|
|
6a2b5e1499 | ||
|
|
d18b775d06 | ||
|
|
d6cc899d95 | ||
|
|
ea10058276 | ||
|
|
568c88be3e | ||
|
|
f047552f18 | ||
|
|
9a58430b5d | ||
|
|
205885edf5 | ||
|
|
8ffb49b700 | ||
|
|
25b18a2edd | ||
|
|
cfea85c8fb | ||
|
|
82fc6d31c3 | ||
|
|
fb2de1753e | ||
|
|
ceb5ce5cdd | ||
|
|
98d443001c | ||
|
|
5115dee069 | ||
|
|
0dc29c7026 | ||
|
|
a5eb5e9df3 | ||
|
|
b680575328 | ||
|
|
c94ef616a5 | ||
|
|
3ae40c09fc | ||
|
|
5de4e00ce2 | ||
|
|
99e4e61a55 | ||
|
|
1ad7bfb82f | ||
|
|
30ef679432 | ||
|
|
bb97f5e757 | ||
|
|
6cd5a04470 | ||
|
|
0c32ebf163 | ||
|
|
e150e08f29 | ||
|
|
bbd603f10c | ||
|
|
677944cba9 | ||
|
|
dd2cee53d3 | ||
|
|
60c8d5ac89 | ||
|
|
59276de406 | ||
|
|
67981be516 | ||
|
|
77dfcec08c | ||
|
|
c4360ab95f | ||
|
|
17c4b72381 | ||
|
|
e91cc2bdca | ||
|
|
811f2305ec | ||
|
|
98a707b5f5 | ||
|
|
aa9b24fa10 | ||
|
|
0b3060e4c0 | ||
|
|
d523db84cc | ||
|
|
6a6a9ae6b3 | ||
|
|
b1bc28e069 | ||
|
|
f95c068bc1 | ||
|
|
0e4cd087d4 | ||
|
|
f217b8cfec | ||
|
|
d3787efbf0 | ||
|
|
e279878b12 | ||
|
|
154a227be0 | ||
|
|
756259c907 | ||
|
|
453ed4820a | ||
|
|
10ab612366 | ||
|
|
8dc4f7ed3d | ||
|
|
a73538bafc | ||
|
|
014887c7dc | ||
|
|
246e49bd8c | ||
|
|
146ac12935 | ||
|
|
4f5245adcf | ||
|
|
0bc0cc5729 | ||
|
|
1136072eff | ||
|
|
4cc8a538a4 | ||
|
|
271913ba20 | ||
|
|
3efe541869 | ||
|
|
b3bcdd4f9c | ||
|
|
13f739763c | ||
|
|
bde2b26133 | ||
|
|
6eef65952d | ||
|
|
3d85795216 | ||
|
|
f1a1989990 | ||
|
|
76ff5eddd0 | ||
|
|
068ee8dc68 | ||
|
|
bbf0fb641b | ||
|
|
3cae22faf1 | ||
|
|
34a4a27ba0 | ||
|
|
d96a78c741 | ||
|
|
c9e5ee9ddb | ||
|
|
f43c2a40f5 | ||
|
|
b12a0e979c | ||
|
|
d0372b82db | ||
|
|
dac1441add | ||
|
|
a65ebec7ae | ||
|
|
d5cf65e568 | ||
|
|
424073298e | ||
|
|
580c83687e | ||
|
|
de226a8ab6 | ||
|
|
8b94b08066 | ||
|
|
18c460a8b2 | ||
|
|
fbb3a56c0c | ||
|
|
48f602d4fe | ||
|
|
ad2b68c8ed | ||
|
|
c2eafccf2a | ||
|
|
5ca74b6127 | ||
|
|
143a38ade6 | ||
|
|
40cff39713 | ||
|
|
3efa1140b2 | ||
|
|
64f28aedbf | ||
|
|
74e3a28e2a | ||
|
|
58c324b2ac | ||
|
|
25fa2dc406 | ||
|
|
3e76f3b8c6 | ||
|
|
a12a3fbdc8 | ||
|
|
46c492c92a | ||
|
|
dbace71754 | ||
|
|
ece5b56bdd | ||
|
|
c35dd826dc | ||
|
|
675324f6af | ||
|
|
8788a14229 | ||
|
|
c8f697c5b1 | ||
|
|
48a4d67c13 | ||
|
|
e4289bb69e | ||
|
|
7000c67535 | ||
|
|
0eb61c80fa | ||
|
|
4bbd43e54b | ||
|
|
ce949d94f2 | ||
|
|
bdbf205745 | ||
|
|
899ab2ddb1 | ||
|
|
a302276af9 | ||
|
|
ee14a292ab | ||
|
|
646949c96c | ||
|
|
ebfb5c29dd | ||
|
|
3932ae62b0 | ||
|
|
f671d2dec0 | ||
|
|
6261f3f2c6 | ||
|
|
1b9802b8e0 | ||
|
|
ca95af7043 | ||
|
|
0aac2eb1e0 | ||
|
|
c10d083dab | ||
|
|
27c6cdec47 | ||
|
|
8fd0f97dfe | ||
|
|
59d65a2cbb | ||
|
|
9bd978b8c6 | ||
|
|
d6e22f2e3e | ||
|
|
a9a1290530 | ||
|
|
b8eab5bddc | ||
|
|
6836d6551f | ||
|
|
8e07ae8b26 | ||
|
|
b013a1cfa7 | ||
|
|
0650d00ca1 | ||
|
|
c048f789e7 | ||
|
|
9c158dfa53 | ||
|
|
168be74277 | ||
|
|
016532ef42 | ||
|
|
7368796a72 | ||
|
|
b3e10de0a1 | ||
|
|
dd674d38df | ||
|
|
a427e32796 | ||
|
|
b44d2e5c9b | ||
|
|
c42d046188 | ||
|
|
ae5f9f0f4a | ||
|
|
1681a587b9 | ||
|
|
b43d51aa72 | ||
|
|
f99d4bc013 | ||
|
|
430c2f51f6 | ||
|
|
b55b521def | ||
|
|
c26e2d800d | ||
|
|
310de9f950 | ||
|
|
9109c5180a | ||
|
|
db57c125d6 | ||
|
|
3ec177c631 | ||
|
|
fa561df14f | ||
|
|
b0abf56f20 | ||
|
|
a4ac051af0 | ||
|
|
7a2db59516 | ||
|
|
76c33e3cbc | ||
|
|
fd295b226a | ||
|
|
9bb82aebfe | ||
|
|
38c6dd3953 | ||
|
|
0517f61164 | ||
|
|
17289f2df6 | ||
|
|
44a7f057ac | ||
|
|
be5d0820d5 | ||
|
|
23bd4a564f | ||
|
|
be41cf1252 | ||
|
|
cf643fe0b0 | ||
|
|
988efa4577 | ||
|
|
631160c2c9 | ||
|
|
ebe3f2b771 | ||
|
|
3e300d98c4 | ||
|
|
1a1e673a6c | ||
|
|
6ce08b5aa3 | ||
|
|
2ef0d5b5cd | ||
|
|
a15978bf6a | ||
|
|
8fb8c54157 | ||
|
|
9f4647be47 | ||
|
|
1a38da30f4 | ||
|
|
c3d244b3b6 | ||
|
|
751af3c2d1 | ||
|
|
ec00779062 | ||
|
|
165ba064a9 | ||
|
|
2763068e21 | ||
|
|
210bdeaed0 | ||
|
|
3037a153dd | ||
|
|
474fb597c6 | ||
|
|
6c19968ef6 | ||
|
|
38dedcbc3c | ||
|
|
0e066d5d63 | ||
|
|
d45a65edea | ||
|
|
98e3c2955b | ||
|
|
9c0eca0891 | ||
|
|
32c377a0ea | ||
|
|
02145ec17e | ||
|
|
9e1a864f54 | ||
|
|
4989dc4301 | ||
|
|
4f43e4eab5 | ||
|
|
d59219e62b | ||
|
|
5f5209af8c | ||
|
|
6a9e6065d1 | ||
|
|
00119dbefe | ||
|
|
c466af60a6 | ||
|
|
e5512974d5 | ||
|
|
7861fa0b11 | ||
|
|
c20dbf1158 | ||
|
|
9e7c302693 | ||
|
|
e007f2592c | ||
|
|
3a5ea3e8ef | ||
|
|
0a0edd4814 | ||
|
|
62bbea01e6 | ||
|
|
3730596030 | ||
|
|
7f176942ba | ||
|
|
72a09f48e0 | ||
|
|
632291e901 | ||
|
|
17352e783b | ||
|
|
d8729f8171 | ||
|
|
8ac8f8f56a | ||
|
|
a2cfa8873f | ||
|
|
13a7f39d27 | ||
|
|
4e22a5605b | ||
|
|
0afcf8cbab | ||
|
|
c54759e766 | ||
|
|
b1d13c125f | ||
|
|
85aab9506c | ||
|
|
728b63b001 | ||
|
|
b91567e2cf | ||
|
|
c3179542a6 | ||
|
|
7ea575a1b8 | ||
|
|
d2eeb93339 | ||
|
|
ca59670b68 | ||
|
|
a1117d77f3 | ||
|
|
e7ee455ee3 | ||
|
|
9ad9c0c633 | ||
|
|
ec2a6f26e6 | ||
|
|
ead4f59ba7 | ||
|
|
c4f0cd18ad | ||
|
|
77d5bd71f9 | ||
|
|
eb0aff2fc3 | ||
|
|
e4e1eb0ced | ||
|
|
116a017938 | ||
|
|
6dfbb27c33 | ||
|
|
097c6915fb | ||
|
|
97490a5e37 | ||
|
|
799678efca | ||
|
|
e3afb1d068 | ||
|
|
d32db74ccc | ||
|
|
e24452c386 | ||
|
|
68e97f17d0 | ||
|
|
9040a4561d | ||
|
|
1ed8205f45 | ||
|
|
feaa207f68 | ||
|
|
76e766583c | ||
|
|
f15446b1d6 | ||
|
|
18be9fda41 | ||
|
|
9b3430b5d9 | ||
|
|
5b79fbd7b5 | ||
|
|
cc0be4565d | ||
|
|
41865fb286 | ||
|
|
37507d4f0a | ||
|
|
755a8d45e9 | ||
|
|
357d872362 | ||
|
|
63be4088a5 | ||
|
|
7e4d123a51 | ||
|
|
1a2ce6ba72 | ||
|
|
adb3805ec7 | ||
|
|
328f324d9e | ||
|
|
108c9e2944 | ||
|
|
f2a5ed6642 | ||
|
|
e1339617b5 | ||
|
|
4f22010c56 | ||
|
|
831b8497b2 | ||
|
|
37dd691270 | ||
|
|
4349445910 | ||
|
|
669849018a | ||
|
|
71ad15b4b6 | ||
|
|
732185a69d | ||
|
|
479dec6998 | ||
|
|
ff6b65c55c | ||
|
|
6e5493f149 | ||
|
|
b185338be2 | ||
|
|
b4161644d0 | ||
|
|
61e0fdea50 | ||
|
|
cb124c7445 | ||
|
|
fce0d50061 | ||
|
|
dac2a22824 | ||
|
|
655bdf3798 | ||
|
|
907bfd04ed | ||
|
|
38ca451d7e | ||
|
|
52800b39e4 | ||
|
|
9d700f6fd1 | ||
|
|
0a5f0d407e | ||
|
|
a84f3a4434 | ||
|
|
1dbd2d8f5f | ||
|
|
ec1001067d | ||
|
|
0374aa17c4 | ||
|
|
78cbce5ca0 | ||
|
|
65c988cf36 | ||
|
|
1487bf027a | ||
|
|
c5ec5f127d | ||
|
|
2627c83e79 | ||
|
|
d54f2351f6 | ||
|
|
460c2b4662 | ||
|
|
cf5fac7d41 | ||
|
|
8d4ae8e970 | ||
|
|
f9db4d49b1 | ||
|
|
728b22f337 | ||
|
|
de42a895dd | ||
|
|
0b813b3fa1 | ||
|
|
d13ff079a4 | ||
|
|
eca9f221ac | ||
|
|
48a3f11215 | ||
|
|
dc7fcb67f0 | ||
|
|
78e8f932c2 | ||
|
|
65678a0995 | ||
|
|
2b9381d40e | ||
|
|
0f795ff913 | ||
|
|
43cb5d278c | ||
|
|
c7142e409b | ||
|
|
3876cb5ec3 | ||
|
|
47862c3182 | ||
|
|
533bef014d | ||
|
|
f5ac0b93f6 | ||
|
|
d5a27b052c | ||
|
|
4a36606121 | ||
|
|
b69933444c | ||
|
|
48866efa57 | ||
|
|
c56445f817 | ||
|
|
18afc0a49c | ||
|
|
f38c4bbbac | ||
|
|
de125a33d1 | ||
|
|
81b7e34b0d | ||
|
|
5d2853d002 | ||
|
|
ad41544403 | ||
|
|
c8bf1fc2cd | ||
|
|
3d25a1d5c2 | ||
|
|
a8896b847f | ||
|
|
a1b3327aa8 | ||
|
|
e9431af138 | ||
|
|
8b07508204 | ||
|
|
3e0d9f92af | ||
|
|
0e4a2c7593 | ||
|
|
a7a2dcf791 | ||
|
|
47221dc81b | ||
|
|
6fb8f11d2f | ||
|
|
0cfd607806 | ||
|
|
a86565a8ec | ||
|
|
3ee231e459 | ||
|
|
335021090d | ||
|
|
b001107135 | ||
|
|
dba1d8645d | ||
|
|
9dff23bdd5 | ||
|
|
90e48cbc16 | ||
|
|
4a1a912d0f | ||
|
|
8a6328ef07 | ||
|
|
a66c72e099 | ||
|
|
652cbcda52 | ||
|
|
199c5d3e64 | ||
|
|
8b1b569fee | ||
|
|
d869a69477 | ||
|
|
00995431f4 | ||
|
|
cec7c5f3b1 | ||
|
|
47ae03ba8a | ||
|
|
3aa3858981 | ||
|
|
67fa1d6bb4 | ||
|
|
00ecb149df | ||
|
|
d931d20006 | ||
|
|
b4a0e97e3f | ||
|
|
8edcfcf2f1 | ||
|
|
6a8ccf9401 | ||
|
|
e2eab24574 | ||
|
|
6a4e31e972 | ||
|
|
04f54f79fa | ||
|
|
c148c6b9cf | ||
|
|
030cbefe3f | ||
|
|
85342e5f74 | ||
|
|
46565aa45c | ||
|
|
8496423b50 | ||
|
|
e39be83f70 | ||
|
|
3a6d1e7a76 | ||
|
|
15fc1d0cca | ||
|
|
685322f02a | ||
|
|
b849861146 | ||
|
|
91e041108c | ||
|
|
76f2ac2c1d | ||
|
|
bc11999201 | ||
|
|
103b1b88f5 | ||
|
|
5b703fe53a | ||
|
|
ab33aa7ebd | ||
|
|
4db043b111 | ||
|
|
3efe977c49 | ||
|
|
08c03bc3a4 | ||
|
|
5cb373f730 | ||
|
|
123f1a2658 | ||
|
|
72645ce0ad | ||
|
|
3f375065fc | ||
|
|
3d816751f6 | ||
|
|
f44c553749 | ||
|
|
9c0ee7e63c | ||
|
|
ae3a73c334 | ||
|
|
fe0129e1a4 | ||
|
|
274395d01b | ||
|
|
5deb5253c1 | ||
|
|
55e3213e5c | ||
|
|
bc9b5c39ee | ||
|
|
7a48c7c1e3 | ||
|
|
324256a499 | ||
|
|
eaea7bdd2a | ||
|
|
5985e22777 | ||
|
|
5de7fc5bd7 | ||
|
|
b54457f522 | ||
|
|
f94341facc | ||
|
|
90a19e7152 | ||
|
|
32998b9c8c | ||
|
|
88532ae13b | ||
|
|
64a138fa80 | ||
|
|
8fe2774d8f | ||
|
|
8ef42dd605 | ||
|
|
5b44206931 | ||
|
|
29f92a0806 | ||
|
|
03f8332c67 | ||
|
|
144d362527 | ||
|
|
7a6ccb932d | ||
|
|
94c1556a5c | ||
|
|
5974ffc0d4 | ||
|
|
6184281694 | ||
|
|
c51406ca04 | ||
|
|
07db957f67 | ||
|
|
d31194bb39 | ||
|
|
44827d13fe | ||
|
|
c847ca0817 | ||
|
|
bd102ea346 | ||
|
|
688dde9890 | ||
|
|
adfef8faef | ||
|
|
7785bcd5a8 | ||
|
|
569f4537e7 | ||
|
|
43a03079a1 | ||
|
|
852802da4b | ||
|
|
ce4e0a1916 | ||
|
|
ce53495b76 | ||
|
|
42571b0794 | ||
|
|
3a2eba3242 | ||
|
|
2d9f41e114 | ||
|
|
8dada6d936 | ||
|
|
2c4170bacc | ||
|
|
86ff1890f1 | ||
|
|
2ef17b800c | ||
|
|
0c0d91f8fd | ||
|
|
ba7b88f5d8 | ||
|
|
47a396ffaf | ||
|
|
f60fcefd1c | ||
|
|
57e1629b0c | ||
|
|
5988640d3f | ||
|
|
6512ace33b | ||
|
|
0a36b5ba5b | ||
|
|
832652730a | ||
|
|
ae7908e000 | ||
|
|
b40fe805e7 | ||
|
|
09f37ae872 | ||
|
|
ef6c4bb6fb | ||
|
|
4fc5ec00ae | ||
|
|
9267433d74 | ||
|
|
dbda1eff82 | ||
|
|
c8f9925973 | ||
|
|
ff87bebe8f | ||
|
|
54f1945a58 | ||
|
|
2a547c879b | ||
|
|
cfa98578ea | ||
|
|
270cb16d30 | ||
|
|
06113db2ff | ||
|
|
3efb79308a | ||
|
|
b0b5bd235f | ||
|
|
4a2fcf950e | ||
|
|
e166a634f2 | ||
|
|
b97ec3948b | ||
|
|
e14bb065dc | ||
|
|
80a77871a0 | ||
|
|
51963f799c | ||
|
|
71d94853ba | ||
|
|
c33c365b62 | ||
|
|
2ce31fc81c | ||
|
|
d9521b5058 | ||
|
|
366948cd28 | ||
|
|
ad338aeefc | ||
|
|
6b6ee76c39 | ||
|
|
3f23507a74 | ||
|
|
815c9eb7de | ||
|
|
aab2b0307e | ||
|
|
b9e7cbd608 | ||
|
|
0cf0b00c58 | ||
|
|
29e07036b1 | ||
|
|
61b1aa919a | ||
|
|
90b7b5f87b | ||
|
|
7103167693 | ||
|
|
77e0f5af17 | ||
|
|
0618341cf8 | ||
|
|
42a4acb688 | ||
|
|
131b1a0aa6 | ||
|
|
e327c92e1a | ||
|
|
d22301ccf9 | ||
|
|
5d2c615a30 | ||
|
|
4a2088ec1e | ||
|
|
238510132f | ||
|
|
b7a8277749 | ||
|
|
0251363d65 | ||
|
|
a784f47430 | ||
|
|
cf28b27da6 | ||
|
|
1fa020c569 | ||
|
|
ead87f7068 | ||
|
|
e35f3c5a96 | ||
|
|
c5282c9ddd | ||
|
|
fc21a8be97 | ||
|
|
c3e315a2ae | ||
|
|
ca30433fd5 | ||
|
|
03240345eb | ||
|
|
156b844fd9 | ||
|
|
f1a6662f01 | ||
|
|
4e5b5e6e3f | ||
|
|
b80dd8c81e | ||
|
|
2cee5020dd | ||
|
|
1a00fc6e30 | ||
|
|
64697a7771 | ||
|
|
d97be2d4fb | ||
|
|
6504beda78 | ||
|
|
3795c69a7b | ||
|
|
77b6ad5408 | ||
|
|
480b4398da | ||
|
|
7ed7d26d6f | ||
|
|
2463ad2820 | ||
|
|
85c50bf0fa | ||
|
|
54ba654b97 | ||
|
|
e58fc38a6d | ||
|
|
f0ef9e072e | ||
|
|
cd62069b9b | ||
|
|
8089f7e181 | ||
|
|
c1ec580fdc | ||
|
|
ae43d58a38 | ||
|
|
2713f26f90 | ||
|
|
783a97e340 | ||
|
|
dcea1544b9 | ||
|
|
9e0286052f | ||
|
|
a41b7d8fd8 | ||
|
|
9473c30426 | ||
|
|
8bf7609d2f | ||
|
|
c63572abd5 | ||
|
|
a003e396b8 | ||
|
|
c4323b7814 | ||
|
|
f27c7e9cf4 | ||
|
|
613c1ca30f | ||
|
|
b4542a532b | ||
|
|
6d8e5a1d1d | ||
|
|
951f628351 | ||
|
|
3ff6b5afd1 | ||
|
|
5aef851eb0 | ||
|
|
f14a72ec1c | ||
|
|
484fc4f5a4 | ||
|
|
6b9567a0be | ||
|
|
0f2a88124b | ||
|
|
740a7b4918 | ||
|
|
6fd3c77348 | ||
|
|
70818c24cd | ||
|
|
69c20045c2 | ||
|
|
c65a6e5e62 | ||
|
|
4a2da72fa3 | ||
|
|
2d83fde2c5 | ||
|
|
3898cc7752 | ||
|
|
c633864073 | ||
|
|
77ee56dd14 | ||
|
|
6c47a485f0 | ||
|
|
9f241959cf | ||
|
|
da20e7977a | ||
|
|
0931c0d5f2 | ||
|
|
cdebfdd7c0 | ||
|
|
ef6ff34ec6 | ||
|
|
9a42273a50 | ||
|
|
75a358a4ce | ||
|
|
6c122c6498 | ||
|
|
963f331290 | ||
|
|
9798ada335 | ||
|
|
2f17ad69a1 | ||
|
|
505d71f1c7 | ||
|
|
4ed6bdd668 | ||
|
|
b87dce6a51 | ||
|
|
295eb8ffec | ||
|
|
c7b2046e46 | ||
|
|
254c2acede | ||
|
|
4ea4bbd782 | ||
|
|
e3f08668ed | ||
|
|
d6113f75ca | ||
|
|
1dcd227014 | ||
|
|
8af190e83d | ||
|
|
dd06e5fd52 | ||
|
|
cd55f443d9 | ||
|
|
974221d938 | ||
|
|
02c084ed6d | ||
|
|
456927548a | ||
|
|
53490cc28b | ||
|
|
15014d2d49 | ||
|
|
3ee14b9463 | ||
|
|
9e46f1ec06 | ||
|
|
47c433b2c9 | ||
|
|
72d5072cac | ||
|
|
6b14b29103 | ||
|
|
e3ef25714a | ||
|
|
ac1bf9e84e | ||
|
|
54abea488f | ||
|
|
9d1ff3231a | ||
|
|
eef8b98b62 | ||
|
|
a4e0fecfe9 | ||
|
|
370837278b | ||
|
|
c03d25ece6 | ||
|
|
34be765ac5 | ||
|
|
bf208921ad | ||
|
|
14b469d8b6 | ||
|
|
cc239d927c | ||
|
|
1c7e619b55 | ||
|
|
3205dae30c | ||
|
|
57da24821f | ||
|
|
16f4184e25 | ||
|
|
4c51fbf816 | ||
|
|
f7835f43f6 | ||
|
|
aeabd9929c | ||
|
|
94a0d841ac | ||
|
|
3172d5a552 | ||
|
|
a042f6d418 | ||
|
|
d9ff5e0053 | ||
|
|
7ce9121f8e | ||
|
|
e0c1d6344d | ||
|
|
6c378a0122 | ||
|
|
1371b14005 | ||
|
|
bf7f3eb86f | ||
|
|
c8716df6a6 | ||
|
|
fd12d4bd00 | ||
|
|
29d0f40c1a | ||
|
|
63fd526db8 | ||
|
|
0fbfc709da | ||
|
|
19f0488745 | ||
|
|
f81c30222a | ||
|
|
26ae76d644 | ||
|
|
f370118498 | ||
|
|
d3a32421ee | ||
|
|
af9a83117b | ||
|
|
ce418487ad | ||
|
|
9bd7dbc940 | ||
|
|
bc91a41bbe | ||
|
|
7efbc666a0 | ||
|
|
5ecd70e121 | ||
|
|
e4d165edb7 | ||
|
|
d24f29e98f | ||
|
|
5559738654 | ||
|
|
54a321cb4c | ||
|
|
b2f0e74ade | ||
|
|
a105c09841 | ||
|
|
6ad8d7ba1b | ||
|
|
c58bb57de0 | ||
|
|
aec183bd78 | ||
|
|
75ed6de07f | ||
|
|
8f56953915 | ||
|
|
7d98f93dd6 | ||
|
|
e74123bc9c | ||
|
|
d21b4c021d | ||
|
|
ca05865021 | ||
|
|
b30ed83f78 | ||
|
|
a4d0b2d0dc | ||
|
|
fa1c48be41 | ||
|
|
ec29cc0801 | ||
|
|
44e9a69faa | ||
|
|
920598f943 | ||
|
|
5afdd88eb7 | ||
|
|
81ebc3906f | ||
|
|
ec7d581c3d | ||
|
|
60e1da412c | ||
|
|
27cf0a3c6b | ||
|
|
e24beebd43 | ||
|
|
9bea47e358 | ||
|
|
779f000f3e | ||
|
|
37eeeb1d64 | ||
|
|
c231c07633 | ||
|
|
4ee82b4fd6 | ||
|
|
d0f0c32ffa | ||
|
|
2c931d72f9 | ||
|
|
1cf832f0fe | ||
|
|
1e49fd8145 | ||
|
|
bd93048aaa | ||
|
|
1b5dc9eb14 | ||
|
|
e93490acfc | ||
|
|
487306b943 | ||
|
|
573a37f671 | ||
|
|
8bfee794c8 | ||
|
|
aea31102f6 | ||
|
|
bfe9aa5330 | ||
|
|
a45c5db34e | ||
|
|
3e588e243c | ||
|
|
03dba9f624 | ||
|
|
b707690817 | ||
|
|
3b7660834d | ||
|
|
f4b14e37ae | ||
|
|
146223e2b6 | ||
|
|
d6b037c178 | ||
|
|
3df8cc53e3 | ||
|
|
63ab54cc42 | ||
|
|
569954b767 | ||
|
|
9722fab8f3 | ||
|
|
bad138d2fa | ||
|
|
a7e57354dd | ||
|
|
45fa486a6d | ||
|
|
a65d271cbd | ||
|
|
feba11626d | ||
|
|
a5afc47d82 | ||
|
|
9edf6d165d | ||
|
|
12b8a9e487 | ||
|
|
9e8b5487b2 | ||
|
|
305a522a65 | ||
|
|
2f77390a8e | ||
|
|
0ab704b115 | ||
|
|
c353f7a872 | ||
|
|
9a242f63eb | ||
|
|
cc07cedd4a | ||
|
|
f1f4cd67f7 | ||
|
|
ceb948bd1f | ||
|
|
90a0878600 | ||
|
|
26a63591bd | ||
|
|
87ee599da1 | ||
|
|
7843a153a7 | ||
|
|
72331faee7 | ||
|
|
28470d60c6 | ||
|
|
ab47c89ef7 | ||
|
|
b086e652c3 | ||
|
|
ef2d8bf5af | ||
|
|
d3221abd7e | ||
|
|
448d19e9fd | ||
|
|
0a973cd83a | ||
|
|
4bff11bf12 | ||
|
|
f127203a5e | ||
|
|
dc3caa16d0 | ||
|
|
1f33ea9db3 | ||
|
|
4e157455c8 | ||
|
|
2bb3652be2 | ||
|
|
becc00d2ee | ||
|
|
e6186c0bbd | ||
|
|
6538540f3b | ||
|
|
a437125730 | ||
|
|
339e5965bb | ||
|
|
e3eb3c8000 | ||
|
|
2ef0f41612 | ||
|
|
0a38a734f5 | ||
|
|
c2d11cdc79 | ||
|
|
0fcab638a3 | ||
|
|
b37853866f | ||
|
|
e6642ac9ab | ||
|
|
c294e80230 | ||
|
|
996f4d29be | ||
|
|
0c5a5c7438 | ||
|
|
9926c0b6da | ||
|
|
8e691769ae | ||
|
|
add2d8af9b | ||
|
|
bc72c8b87c | ||
|
|
b73964afb9 | ||
|
|
96b6115e7b | ||
|
|
ea46488d94 | ||
|
|
df0e8ed567 | ||
|
|
1e86b4d772 | ||
|
|
b1e1ed982a | ||
|
|
500388a634 | ||
|
|
7a60ac17ce | ||
|
|
a6a2f848d7 | ||
|
|
25e6350c04 | ||
|
|
e0329c273f | ||
|
|
fd7b33fe30 | ||
|
|
6d70f8d2f9 | ||
|
|
c6bb28c139 | ||
|
|
6796206099 | ||
|
|
ca40342978 | ||
|
|
36150fb55b | ||
|
|
7b3f671159 | ||
|
|
0cd6191190 | ||
|
|
cbccb97d42 | ||
|
|
2ce9386759 | ||
|
|
f5b85a82d0 | ||
|
|
0123da10f7 | ||
|
|
1228f1b6fa | ||
|
|
3126c2d325 | ||
|
|
a9c9ca29c8 | ||
|
|
665f44bcee | ||
|
|
c444f7ddb8 | ||
|
|
787c36c754 | ||
|
|
3502469c1d | ||
|
|
c58b16cba6 | ||
|
|
15ea57b692 | ||
|
|
655cf0617e | ||
|
|
57311397e0 | ||
|
|
94db2387c8 | ||
|
|
bd1cf0fadc | ||
|
|
cfd53a8b2c | ||
|
|
baa2d1a389 | ||
|
|
8cd963a893 | ||
|
|
6e3ee9171b | ||
|
|
088ef2285f | ||
|
|
e127b8f4ab | ||
|
|
e218962c55 | ||
|
|
fa080b5ce2 | ||
|
|
d7d019f870 | ||
|
|
74b5c4d894 | ||
|
|
4ebc3af1e4 | ||
|
|
3188b33d19 | ||
|
|
c2c69c282e | ||
|
|
f57c7b5998 | ||
|
|
56faf3ed48 | ||
|
|
2fe8c87caa | ||
|
|
b7ecb7b6fb | ||
|
|
569b6c7939 | ||
|
|
e2f9b9c58d | ||
|
|
94150a92aa | ||
|
|
f81814a426 | ||
|
|
443b6ad7e9 | ||
|
|
2ceb68ec87 | ||
|
|
29f66d457d | ||
|
|
304d69f8b5 | ||
|
|
900a5b69a5 | ||
|
|
ebe1b641fd | ||
|
|
f26ea526e8 | ||
|
|
b36e6faac5 | ||
|
|
528fb3092b | ||
|
|
d5ee925c52 | ||
|
|
208e32d7ea | ||
|
|
5429161f91 | ||
|
|
81cdfcf61a | ||
|
|
6186e22006 | ||
|
|
4ff051130b | ||
|
|
97ae36dcda | ||
|
|
800b5f9d9a | ||
|
|
d4b1f32516 | ||
|
|
6626b856fb | ||
|
|
44df437c4e | ||
|
|
189a9da5b5 | ||
|
|
73def8a682 | ||
|
|
7c97db74dc | ||
|
|
ceb6e94d0c | ||
|
|
1a61c4ee8f | ||
|
|
d4aa4fdb90 | ||
|
|
3e7db7051b | ||
|
|
6b6e903322 | ||
|
|
2f62c72cfe | ||
|
|
8951352f63 | ||
|
|
6c9e265a11 | ||
|
|
1a14d37c72 | ||
|
|
5bfad0149b | ||
|
|
2168609577 | ||
|
|
7e8e981145 | ||
|
|
300e65f5c4 | ||
|
|
69590f9547 | ||
|
|
ae023bdf5b | ||
|
|
4556e4d230 | ||
|
|
a77c88bddd | ||
|
|
8672f92942 | ||
|
|
a8e81a32bf | ||
|
|
95b638a135 | ||
|
|
4302cdf0ea | ||
|
|
36d097f86c | ||
|
|
b364178fc0 | ||
|
|
2c7927bb08 | ||
|
|
af6041128b | ||
|
|
6ccee9f6eb | ||
|
|
404f2072a1 | ||
|
|
01e774bff5 | ||
|
|
df9fb59f40 | ||
|
|
9ac9e21645 | ||
|
|
44846cc7b5 | ||
|
|
96e36a6f73 | ||
|
|
37ad3ea31a | ||
|
|
07d2a701c7 | ||
|
|
94f7e8cd08 | ||
|
|
51819e9d4a | ||
|
|
860ec7fea8 | ||
|
|
21b3866df4 | ||
|
|
a91717aca3 | ||
|
|
91a272ee3d | ||
|
|
d51079fc88 | ||
|
|
37ae106713 | ||
|
|
b93b5cc8d6 | ||
|
|
9d6733deff | ||
|
|
86652230bc | ||
|
|
588e37aa20 | ||
|
|
79b75c5ba7 | ||
|
|
cc60558839 | ||
|
|
c7a6cd8964 | ||
|
|
5b537ab470 | ||
|
|
48af6c460f | ||
|
|
89c9c58631 | ||
|
|
c10f518e3a | ||
|
|
1f4cd6504a | ||
|
|
9ef40c01ce | ||
|
|
843be2a62c | ||
|
|
a881a6d51e | ||
|
|
22221afb7a | ||
|
|
21a7c64b0b | ||
|
|
41ea254301 | ||
|
|
3b15717150 | ||
|
|
94f02cdb07 | ||
|
|
061433d8cd | ||
|
|
c8ba88bb99 | ||
|
|
3b181cd9b2 | ||
|
|
5384bed382 | ||
|
|
ac0d831f22 | ||
|
|
8776058655 | ||
|
|
5c8c806228 | ||
|
|
aa2110b736 | ||
|
|
cbfaf6ae18 | ||
|
|
d815608701 | ||
|
|
696c280eaf | ||
|
|
b8c785fa3f | ||
|
|
d80fdfbfc1 | ||
|
|
f783dad211 | ||
|
|
c335d10167 | ||
|
|
bf7a02df5e | ||
|
|
bf12385099 | ||
|
|
f98b53e9b8 | ||
|
|
8f379d253e | ||
|
|
e1f07618d3 | ||
|
|
9a0c129844 | ||
|
|
aa2a8ab6f4 | ||
|
|
8e59ff1318 | ||
|
|
ad99c87f72 | ||
|
|
ba776b5854 | ||
|
|
e526145e48 | ||
|
|
45c5e65bf6 | ||
|
|
87bec4e2ab | ||
|
|
433e67a6dc | ||
|
|
6acba51b88 | ||
|
|
cf1322952d | ||
|
|
b5ac9dd430 | ||
|
|
a3b590bcb7 | ||
|
|
d4f7e3ef07 | ||
|
|
16119f6958 | ||
|
|
e39c2583a5 | ||
|
|
eecff009dd | ||
|
|
d9ed61a7da | ||
|
|
58db265702 | ||
|
|
bd39f0cbf8 | ||
|
|
255015108a | ||
|
|
b80f366f8d | ||
|
|
c845c7fce6 | ||
|
|
6e7b7ce0e1 | ||
|
|
9b728902d4 | ||
|
|
9a1f54c41a | ||
|
|
b3251eb016 | ||
|
|
f4064b7939 | ||
|
|
f014f08ff4 | ||
|
|
dee1cf4b5a | ||
|
|
f35a3d8fa3 | ||
|
|
ca9a8d14fa | ||
|
|
35c35fa9d7 | ||
|
|
09fff95d58 | ||
|
|
287307a1ba | ||
|
|
d628e62302 | ||
|
|
a070bc46ad | ||
|
|
9027c44511 | ||
|
|
722aa7b9c6 | ||
|
|
a4e777b24b | ||
|
|
af7120e953 | ||
|
|
d5adc3bbac | ||
|
|
aa3d778ad1 | ||
|
|
3465a816c6 | ||
|
|
3d1726c5aa | ||
|
|
488de2d583 | ||
|
|
c415b114a6 | ||
|
|
da36784e9c | ||
|
|
78fafdc272 | ||
|
|
7d57055f73 | ||
|
|
b84cc747cb | ||
|
|
9a4f3ccbad | ||
|
|
68de3cc928 | ||
|
|
6e135cdd36 | ||
|
|
696116ac85 | ||
|
|
08a13ff35a | ||
|
|
af1cea81dc | ||
|
|
2c22a7ca90 | ||
|
|
6fd6bd0375 | ||
|
|
1a96dc965a | ||
|
|
9550350f25 | ||
|
|
0ed605a5b9 | ||
|
|
18910d931c | ||
|
|
87648b7d8b | ||
|
|
7e7a1aeecb | ||
|
|
cb353358b7 | ||
|
|
ba1619711b | ||
|
|
b3cc9475ad | ||
|
|
ee5c4077fe | ||
|
|
47bbfc618e | ||
|
|
b377329a8b | ||
|
|
ae8bcf3865 |
10
.github/workflows/android.publish.internal.yml
vendored
@@ -147,8 +147,10 @@ jobs:
|
|||||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
|
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
|
||||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
||||||
|
|
||||||
- name: Upload signed aab to Github
|
- name: Upload sourcemaps
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Notesnook.aab
|
name: sourcemaps
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
path: |
|
||||||
|
apps/mobile/native/android/app/build/**/*.map
|
||||||
|
packages/editor-mobile/sourcemaps/*.map
|
||||||
|
|||||||
11
.github/workflows/android.publish.yml
vendored
@@ -133,7 +133,6 @@ jobs:
|
|||||||
status: completed
|
status: completed
|
||||||
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
whatsNewDirectory: apps/mobile/native/android/releasenotes/
|
||||||
|
|
||||||
|
|
||||||
- name: Create release draft on Github
|
- name: Create release draft on Github
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
@@ -148,8 +147,10 @@ jobs:
|
|||||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
|
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
|
||||||
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
|
||||||
|
|
||||||
- name: Upload signed aab to Github
|
- name: Upload sourcemaps
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Notesnook.aab
|
name: sourcemaps
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
path: |
|
||||||
|
apps/mobile/native/android/app/build/**/*.map
|
||||||
|
packages/editor-mobile/sourcemaps/*.map
|
||||||
|
|||||||
19
.github/workflows/desktop.publish.yml
vendored
@@ -97,6 +97,9 @@ jobs:
|
|||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: ./.github/actions/setup-node-with-cache
|
uses: ./.github/actions/setup-node-with-cache
|
||||||
|
|
||||||
|
- name: Install setuptools
|
||||||
|
run: brew install python-setuptools
|
||||||
|
|
||||||
- name: Setup notarization
|
- name: Setup notarization
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/private_keys/
|
mkdir -p ~/private_keys/
|
||||||
@@ -144,7 +147,7 @@ jobs:
|
|||||||
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
||||||
run: |
|
run: |
|
||||||
npx nx run release --project @notesnook/desktop -- --variant=mas
|
npx nx run release --project @notesnook/desktop -- --variant=mas
|
||||||
npx electron-builder --mac mas --universal -p never
|
yarn electron-builder --mac mas --universal -p never
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Build zip and dmg
|
- name: Build zip and dmg
|
||||||
@@ -158,9 +161,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
npx nx run release --project @notesnook/desktop
|
npx nx run release --project @notesnook/desktop
|
||||||
if [ ${{ inputs.publish-github }} == true ]; then
|
if [ ${{ inputs.publish-github }} == true ]; then
|
||||||
npx electron-builder --mac zip dmg --arm64 --x64 -p always
|
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
|
||||||
else
|
else
|
||||||
npx electron-builder --mac zip dmg --arm64 --x64 -p never
|
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
|
||||||
fi
|
fi
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
@@ -216,7 +219,7 @@ jobs:
|
|||||||
- name: Build snap
|
- name: Build snap
|
||||||
if: inputs.publish-snap
|
if: inputs.publish-snap
|
||||||
run: |
|
run: |
|
||||||
npx electron-builder --linux snap:x64 -p never
|
yarn electron-builder --linux snap:x64 -p never
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
@@ -224,9 +227,9 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
if [ ${{ inputs.publish-github }} == true ]; then
|
if [ ${{ inputs.publish-github }} == true ]; then
|
||||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||||
else
|
else
|
||||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||||
fi
|
fi
|
||||||
working-directory: ./apps/desktop
|
working-directory: ./apps/desktop
|
||||||
|
|
||||||
@@ -251,8 +254,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: 6.0.x
|
dotnet-version: 6.0.x
|
||||||
|
|
||||||
- name: Install AzureCodeSigning
|
- name: Install TrustedSigning
|
||||||
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
|
run: Install-Module -Name TrustedSigning -RequiredVersion 0.3.15 -Force -Repository PSGallery
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
|
|||||||
23
.github/workflows/ios.publish.yml
vendored
@@ -89,16 +89,19 @@ jobs:
|
|||||||
- name: CCache Stats After Build
|
- name: CCache Stats After Build
|
||||||
run: ccache -sv
|
run: ccache -sv
|
||||||
|
|
||||||
- name: "Upload app to TestFlight"
|
# - name: "Upload app to TestFlight"
|
||||||
uses: apple-actions/upload-testflight-build@v1
|
# uses: apple-actions/upload-testflight-build@v1
|
||||||
with:
|
# with:
|
||||||
app-path: Notesnook.ipa
|
# app-path: Notesnook.ipa
|
||||||
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
# issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
|
||||||
api-key-id: ${{ secrets.API_KEY_ID }}
|
# api-key-id: ${{ secrets.API_KEY_ID }}
|
||||||
api-private-key: ${{ secrets.API_KEY }}
|
# api-private-key: ${{ secrets.API_KEY }}
|
||||||
|
|
||||||
- name: Upload Notesnook.ipa to Github
|
- name: Upload Notesnook.ipa to Github
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Notesnook.ipa
|
name: Notesnook.zip
|
||||||
path: Notesnook.ipa
|
path: |
|
||||||
|
Notesnook.ipa
|
||||||
|
apps/mobile/native/ios/**/*.map
|
||||||
|
packages/editor-mobile/sourcemaps/*.map
|
||||||
|
|||||||
69
.github/workflows/web.beta.publish.yml
vendored
@@ -1,69 +0,0 @@
|
|||||||
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' }}
|
|
||||||
8
.github/workflows/web.tests.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
run: npm run build:test:web
|
run: npm run build:test:web
|
||||||
|
|
||||||
- name: Archive build artifact
|
- name: Archive build artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: apps/web/build/**/*
|
path: apps/web/build/**/*
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Download build
|
- name: Download build
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build
|
name: build
|
||||||
path: ./apps/web/build
|
path: ./apps/web/build
|
||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||||
npm install -D @playwright/test otplib
|
npm install -D @playwright/test otplib dayjs @zip.js/zip.js
|
||||||
|
|
||||||
- name: Validate build extraction
|
- name: Validate build extraction
|
||||||
run: ls -ld ./apps/web/build/index.html
|
run: ls -ld ./apps/web/build/index.html
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
working-directory: apps/web
|
working-directory: apps/web
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ matrix.shard }}
|
name: test-results-${{ matrix.shard }}
|
||||||
|
|||||||
32
.github/workflows/web.v3-beta.publish.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
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: Build
|
|
||||||
run: npm run build:web
|
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages
|
|
||||||
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/
|
|
||||||
2
.gitignore
vendored
@@ -10,4 +10,4 @@ nx-cloud.env
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
site
|
site
|
||||||
node_modules.backup
|
node_modules.backup
|
||||||
.nx/cache
|
.nx
|
||||||
|
|||||||
@@ -40,12 +40,35 @@ Once you are inside the `./notesnook` directory, run the preparation step:
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can finally start the desktop app:
|
Now you can finally start the desktop app for development:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run start:desktop
|
npm run start:desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To run the app in release mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run staging -- --rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile and run the app in production mode but it won't generate any packages. To create the final packages, you'll have to run the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run release -- --rebuild
|
||||||
|
|
||||||
|
# For macOS
|
||||||
|
npx electron-builder --mac dmg --arm64 --x64 -publish never
|
||||||
|
|
||||||
|
# For Linux (AppImage)
|
||||||
|
npx electron-builder --linux AppImage:x64 AppImage:arm64 -publish never
|
||||||
|
|
||||||
|
# For Windows
|
||||||
|
npx electron-builder --win --publish never
|
||||||
|
```
|
||||||
|
|
||||||
|
Feel free to play around with the `electron-builder` command to get the packages you need. `npx electron-builder --help` is a great resource to learn different commands & platforms supported by `electron-builder`.
|
||||||
|
|
||||||
## Developer guide
|
## Developer guide
|
||||||
|
|
||||||
### The tech stack
|
### The tech stack
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
owner: streetwriters
|
owner: streetwriters
|
||||||
repo: notesnook
|
repo: notesnook
|
||||||
provider: github
|
provider: github
|
||||||
|
updaterCacheDirName: Notesnook
|
||||||
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 543 B |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 763 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.3 KiB |
934
apps/desktop/package-lock.json
generated
@@ -2,7 +2,7 @@
|
|||||||
"name": "@notesnook/desktop",
|
"name": "@notesnook/desktop",
|
||||||
"productName": "Notesnook",
|
"productName": "Notesnook",
|
||||||
"description": "Your private note taking space",
|
"description": "Your private note taking space",
|
||||||
"version": "3.0.6-beta",
|
"version": "3.0.13",
|
||||||
"appAppleId": "1544027013",
|
"appAppleId": "1544027013",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
"@notesnook/crypto": "file:../../packages/crypto",
|
"@notesnook/crypto": "file:../../packages/crypto",
|
||||||
"@trpc/client": "10.38.3",
|
"@trpc/client": "10.38.3",
|
||||||
"@trpc/server": "10.38.3",
|
"@trpc/server": "10.38.3",
|
||||||
"better-sqlite3-multiple-ciphers": "^9.4.0",
|
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
||||||
"electron-trpc": "0.5.2",
|
"electron-trpc": "0.5.2",
|
||||||
"electron-updater": "6.1.4",
|
"electron-updater": "^6.2.1",
|
||||||
"icojs": "^0.17.1",
|
"icojs": "^0.17.1",
|
||||||
"sodium-native": "^4.0.6",
|
"sodium-native": "^4.1.1",
|
||||||
"typed-emitter": "^2.1.0",
|
"typed-emitter": "^2.1.0",
|
||||||
"yargs": "^17.6.2",
|
"yargs": "^17.6.2",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
@@ -27,11 +27,14 @@
|
|||||||
"@types/node": "18.16.1",
|
"@types/node": "18.16.1",
|
||||||
"@types/yargs": "^17.0.24",
|
"@types/yargs": "^17.0.24",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"electron": "^28.2.1",
|
"electron": "^29.3.1",
|
||||||
"electron-builder": "^24.9.1",
|
"electron-builder": "^24.13.3",
|
||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.23.0",
|
||||||
|
"kysely": "^0.27.3",
|
||||||
|
"node-gyp-build": "^4.8.0",
|
||||||
|
"prebuildify": "^6.0.1",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
"undici": "^6.6.1"
|
"undici": "^6.14.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"dmg-license": "^1.0.11"
|
"dmg-license": "^1.0.11"
|
||||||
@@ -55,8 +58,8 @@
|
|||||||
"productName": "Notesnook",
|
"productName": "Notesnook",
|
||||||
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
||||||
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
||||||
"asar": false,
|
|
||||||
"generateUpdatesFilesForAllChannels": true,
|
"generateUpdatesFilesForAllChannels": true,
|
||||||
|
"asar": false,
|
||||||
"files": [
|
"files": [
|
||||||
"!*.chunk.js.map",
|
"!*.chunk.js.map",
|
||||||
"!*.chunk.js.LICENSE.txt",
|
"!*.chunk.js.LICENSE.txt",
|
||||||
@@ -219,8 +222,7 @@
|
|||||||
{
|
{
|
||||||
"provider": "github",
|
"provider": "github",
|
||||||
"repo": "notesnook",
|
"repo": "notesnook",
|
||||||
"owner": "streetwriters",
|
"owner": "streetwriters"
|
||||||
"channel": "beta"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
46
apps/desktop/patches/node-gyp-build+4.8.0.patch
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
diff --git a/node_modules/node-gyp-build/bin.js b/node_modules/node-gyp-build/bin.js
|
||||||
|
index 3fbcdf0..7ca3ab5 100644
|
||||||
|
--- a/node_modules/node-gyp-build/bin.js
|
||||||
|
+++ b/node_modules/node-gyp-build/bin.js
|
||||||
|
@@ -16,7 +16,8 @@ if (!buildFromSource()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
function build () {
|
||||||
|
- var args = [os.platform() === 'win32' ? 'node-gyp.cmd' : 'node-gyp', 'rebuild']
|
||||||
|
+ var win32 = os.platform() === 'win32'
|
||||||
|
+ var args = [win32 ? 'node-gyp.cmd' : 'node-gyp', 'rebuild']
|
||||||
|
|
||||||
|
try {
|
||||||
|
var pkg = require('node-gyp/package.json')
|
||||||
|
@@ -27,7 +28,7 @@ function build () {
|
||||||
|
]
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
- proc.spawn(args[0], args.slice(1), { stdio: 'inherit' }).on('exit', function (code) {
|
||||||
|
+ proc.spawn(args[0], args.slice(1), { stdio: 'inherit', shell: win32, windowsHide: true }).on('exit', function (code) {
|
||||||
|
if (code || !process.argv[3]) process.exit(code)
|
||||||
|
exec(process.argv[3]).on('exit', function (code) {
|
||||||
|
process.exit(code)
|
||||||
|
@@ -45,15 +46,18 @@ function preinstall () {
|
||||||
|
|
||||||
|
function exec (cmd) {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
- var shell = os.platform() === 'android' ? 'sh' : '/bin/sh'
|
||||||
|
- return proc.spawn(shell, ['-c', '--', cmd], {
|
||||||
|
+ var shell = os.platform() === 'android' ? 'sh' : true
|
||||||
|
+ return proc.spawn(cmd, [], {
|
||||||
|
+ shell,
|
||||||
|
stdio: 'inherit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
- return proc.spawn(process.env.comspec || 'cmd.exe', ['/s', '/c', '"' + cmd + '"'], {
|
||||||
|
+ return proc.spawn(cmd, [], {
|
||||||
|
windowsVerbatimArguments: true,
|
||||||
|
- stdio: 'inherit'
|
||||||
|
+ stdio: 'inherit',
|
||||||
|
+ shell: true,
|
||||||
|
+ windowsHide: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs, { readFile, writeFile } from "fs/promises";
|
import fs, { readFile, writeFile } from "fs/promises";
|
||||||
import { existsSync } from "fs";
|
import { existsSync, readFileSync } from "fs";
|
||||||
import yargs from "yargs-parser";
|
import yargs from "yargs-parser";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import * as childProcess from "child_process";
|
import * as childProcess from "child_process";
|
||||||
@@ -28,23 +28,29 @@ import { fileURLToPath } from "url";
|
|||||||
const args = yargs(process.argv);
|
const args = yargs(process.argv);
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")
|
||||||
|
);
|
||||||
|
|
||||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||||
|
|
||||||
await fs.rm("./build/", { force: true, recursive: true });
|
await fs.rm("./build/", { force: true, recursive: true });
|
||||||
|
|
||||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||||
await exec(`cd ${webAppPath} && npm run build:desktop`);
|
await exec(
|
||||||
|
"yarn nx build:desktop @notesnook/web",
|
||||||
|
path.join(__dirname, "..", "..", "..")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary until there's support for prebuilt binaries for windows ARM & linux ARM
|
// temporary until there's support for prebuilt binaries for linux ARM
|
||||||
await patchBetterSQLite3();
|
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||||
|
|
||||||
if (os.platform() === "win32")
|
// if (os.platform() === "win32")
|
||||||
await exec(
|
// await exec(
|
||||||
"npx prebuildify --arch=arm64 --strip",
|
// `npx prebuildify --arch=arm64 --strip -t electron@${packageJson.devDependencies.electron}`,
|
||||||
path.join(__dirname, "..", "node_modules", "sodium-native")
|
// path.join(__dirname, "..", "node_modules", "sodium-native")
|
||||||
);
|
// );
|
||||||
|
|
||||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@@ -52,19 +58,19 @@ await fs.cp(path.join(webAppPath, "build"), "build", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (args.variant === "mas") {
|
if (args.variant === "mas") {
|
||||||
await exec(`npm run bundle:mas`);
|
await exec(`yarn run bundle:mas`);
|
||||||
} else {
|
} else {
|
||||||
await exec(`npm run bundle`);
|
await exec(`yarn run bundle`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await exec(`npx tsc`);
|
await exec(`yarn tsc`);
|
||||||
|
|
||||||
if (args.run) {
|
if (args.run) {
|
||||||
await exec(`npx electron-builder --dir --x64`);
|
await exec(`yarn electron-builder --dir --x64`);
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
||||||
} else if (process.platform === "darwin") {
|
} else if (process.platform === "darwin") {
|
||||||
await exec(`./output/darwin-unpacked/Notesnook`);
|
await exec(`./output/mac/Notesnook.app/Contents/MacOS/Notesnook`);
|
||||||
} else {
|
} else {
|
||||||
await exec(`./output/linux-unpacked/Notesnook`);
|
await exec(`./output/linux-unpacked/Notesnook`);
|
||||||
}
|
}
|
||||||
@@ -88,7 +94,7 @@ async function patchBetterSQLite3() {
|
|||||||
);
|
);
|
||||||
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
|
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
|
||||||
|
|
||||||
json.version = "9.4.1";
|
json.version = "9.5.1";
|
||||||
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
|
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
|
||||||
json.repository.url =
|
json.repository.url =
|
||||||
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
|
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ async function onChange(first) {
|
|||||||
if (first) {
|
if (first) {
|
||||||
await fs.rm("./build/", { force: true, recursive: true });
|
await fs.rm("./build/", { force: true, recursive: true });
|
||||||
|
|
||||||
await exec("npx electron-rebuild");
|
await exec("yarn electron-builder install-app-deps");
|
||||||
}
|
}
|
||||||
|
|
||||||
await exec(`npm run bundle`);
|
await exec(`yarn run bundle`);
|
||||||
execAsync(`npx`, [`tsc`]);
|
execAsync(`yarn`, [`tsc`]);
|
||||||
|
|
||||||
if (await isBundleSame()) {
|
if (await isBundleSame()) {
|
||||||
console.log("Bundle is same. Doing nothing.");
|
console.log("Bundle is same. Doing nothing.");
|
||||||
@@ -66,7 +66,7 @@ async function onChange(first) {
|
|||||||
|
|
||||||
if (first) {
|
if (first) {
|
||||||
await spawnAndWaitUntil(
|
await spawnAndWaitUntil(
|
||||||
["npx", "nx", "start:desktop", "@notesnook/web"],
|
["yarn", "nx", "start:desktop", "@notesnook/web"],
|
||||||
path.join(__dirname, "..", "..", ".."),
|
path.join(__dirname, "..", "..", ".."),
|
||||||
(data) => data.includes("Network: use --host to expose")
|
(data) => data.includes("Network: use --host to expose")
|
||||||
);
|
);
|
||||||
@@ -78,7 +78,7 @@ async function onChange(first) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
execAsync(
|
execAsync(
|
||||||
"npx",
|
"yarn",
|
||||||
["electron", path.join("build", "electron.js")],
|
["electron", path.join("build", "electron.js")],
|
||||||
true,
|
true,
|
||||||
cleanup
|
cleanup
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ module.exports = async function (configuration) {
|
|||||||
const DescriptionUrl = "https://notesnook.com/";
|
const DescriptionUrl = "https://notesnook.com/";
|
||||||
const FilesCatalog = createCatalog(configuration.path);
|
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}"`;
|
const command = `Invoke-TrustedSigning -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);
|
console.debug("Signing", configuration.path, "using command", command);
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { spellCheckerRouter } from "./spell-checker";
|
|||||||
import { updaterRouter } from "./updater";
|
import { updaterRouter } from "./updater";
|
||||||
import { bridgeRouter } from "./bridge";
|
import { bridgeRouter } from "./bridge";
|
||||||
import { safeStorageRouter } from "./safe-storage";
|
import { safeStorageRouter } from "./safe-storage";
|
||||||
|
import { windowRouter } from "./window";
|
||||||
|
|
||||||
const t = initTRPC.create();
|
const t = initTRPC.create();
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ export const router = t.router({
|
|||||||
spellChecker: spellCheckerRouter,
|
spellChecker: spellCheckerRouter,
|
||||||
updater: updaterRouter,
|
updater: updaterRouter,
|
||||||
bridge: bridgeRouter,
|
bridge: bridgeRouter,
|
||||||
safeStorage: safeStorageRouter
|
safeStorage: safeStorageRouter,
|
||||||
|
window: windowRouter
|
||||||
});
|
});
|
||||||
|
|
||||||
export const api = router.createCaller({});
|
export const api = router.createCaller({});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { dialog, nativeTheme, Notification, shell } from "electron";
|
import { app, dialog, nativeTheme, Notification, shell } from "electron";
|
||||||
import { AutoLaunch } from "../utils/autolaunch";
|
import { AutoLaunch } from "../utils/autolaunch";
|
||||||
import { config, DesktopIntegration } from "../utils/config";
|
import { config, DesktopIntegration } from "../utils/config";
|
||||||
import { bringToFront } from "../utils/bring-to-front";
|
import { bringToFront } from "../utils/bring-to-front";
|
||||||
@@ -32,6 +32,7 @@ import { AssetManager } from "../utils/asset-manager";
|
|||||||
import { isFlatpak } from "../utils";
|
import { isFlatpak } from "../utils";
|
||||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||||
import { rm } from "fs/promises";
|
import { rm } from "fs/promises";
|
||||||
|
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
||||||
|
|
||||||
const t = initTRPC.create();
|
const t = initTRPC.create();
|
||||||
|
|
||||||
@@ -55,6 +56,23 @@ export const osIntegrationRouter = t.router({
|
|||||||
config.zoomFactor = factor;
|
config.zoomFactor = factor;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
customDns: t.procedure.query(() => config.customDns),
|
||||||
|
setCustomDns: t.procedure
|
||||||
|
.input(z.boolean().optional())
|
||||||
|
.mutation(({ input: customDns }) => {
|
||||||
|
if (customDns) enableCustomDns();
|
||||||
|
else disableCustomDns();
|
||||||
|
config.customDns = !!customDns;
|
||||||
|
}),
|
||||||
|
|
||||||
|
proxyRules: t.procedure.query(() => config.proxyRules),
|
||||||
|
setProxyRules: t.procedure
|
||||||
|
.input(z.string().optional())
|
||||||
|
.mutation(({ input: proxyRules }) => {
|
||||||
|
globalThis.window?.webContents.session.setProxy({ proxyRules });
|
||||||
|
config.proxyRules = proxyRules || "";
|
||||||
|
}),
|
||||||
|
|
||||||
privacyMode: t.procedure.query(() => config.privacyMode),
|
privacyMode: t.procedure.query(() => config.privacyMode),
|
||||||
setPrivacyMode: t.procedure
|
setPrivacyMode: t.procedure
|
||||||
.input(z.object({ enabled: z.boolean() }))
|
.input(z.object({ enabled: z.boolean() }))
|
||||||
@@ -134,6 +152,10 @@ export const osIntegrationRouter = t.router({
|
|||||||
await rm(input);
|
await rm(input);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
restart: t.procedure.query(() => {
|
||||||
|
app.relaunch();
|
||||||
|
app.exit();
|
||||||
|
}),
|
||||||
showNotification: t.procedure
|
showNotification: t.procedure
|
||||||
.input(NotificationOptions)
|
.input(NotificationOptions)
|
||||||
.query(({ input }) => {
|
.query(({ input }) => {
|
||||||
@@ -162,16 +184,45 @@ export const osIntegrationRouter = t.router({
|
|||||||
}),
|
}),
|
||||||
bringToFront: t.procedure.query(() => bringToFront()),
|
bringToFront: t.procedure.query(() => bringToFront()),
|
||||||
changeTheme: t.procedure
|
changeTheme: t.procedure
|
||||||
.input(Theme)
|
.input(
|
||||||
.mutation(({ input }) => setTheme(input)),
|
z.object({
|
||||||
|
theme: Theme,
|
||||||
|
windowControlsIconColor: z.string().optional(),
|
||||||
|
backgroundColor: z.string().optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(
|
||||||
|
({ input: { theme, windowControlsIconColor, backgroundColor } }) => {
|
||||||
|
if (windowControlsIconColor) {
|
||||||
|
config.windowControlsIconColor = windowControlsIconColor;
|
||||||
|
if (
|
||||||
|
process.platform === "win32" &&
|
||||||
|
!config.desktopSettings.nativeTitlebar
|
||||||
|
)
|
||||||
|
globalThis.window?.setTitleBarOverlay({
|
||||||
|
symbolColor: windowControlsIconColor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backgroundColor) {
|
||||||
|
config.backgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(theme);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
onThemeChanged: t.procedure.subscription(() =>
|
onThemeChanged: t.procedure.subscription(() =>
|
||||||
observable<"dark" | "light">((emit) => {
|
observable<"dark" | "light">((emit) => {
|
||||||
nativeTheme.on("updated", () => {
|
const updated = () => {
|
||||||
if (getTheme() === "system") {
|
if (getTheme() === "system") {
|
||||||
emit.next(nativeTheme.shouldUseDarkColors ? "dark" : "light");
|
emit.next(nativeTheme.shouldUseDarkColors ? "dark" : "light");
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
nativeTheme.on("updated", updated);
|
||||||
|
return () => {
|
||||||
|
nativeTheme.off("updated", updated);
|
||||||
|
};
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -118,5 +118,13 @@ export const spellCheckerRouter = t.router({
|
|||||||
.mutation(({ input: { enabled } }) => {
|
.mutation(({ input: { enabled } }) => {
|
||||||
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
|
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
|
||||||
config.isSpellCheckerEnabled = enabled;
|
config.isSpellCheckerEnabled = enabled;
|
||||||
})
|
}),
|
||||||
|
words: t.procedure.query(() =>
|
||||||
|
globalThis.window?.webContents.session.listWordsInSpellCheckerDictionary()
|
||||||
|
),
|
||||||
|
deleteWord: t.procedure.input(z.string()).mutation(({ input: word }) => {
|
||||||
|
globalThis.window?.webContents.session.removeWordFromSpellCheckerDictionary(
|
||||||
|
word
|
||||||
|
);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
143
apps/desktop/src/api/sqlite-kysely.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
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 type { Database, Statement } from "better-sqlite3-multiple-ciphers";
|
||||||
|
import type { QueryResult } from "kysely";
|
||||||
|
|
||||||
|
type SQLiteCompatibleType =
|
||||||
|
| number
|
||||||
|
| string
|
||||||
|
| Uint8Array
|
||||||
|
| Array<number>
|
||||||
|
| bigint
|
||||||
|
| null;
|
||||||
|
|
||||||
|
export class SQLite {
|
||||||
|
sqlite?: Database;
|
||||||
|
initialized = false;
|
||||||
|
preparedStatements: Map<string, Statement<unknown[]>> = new Map();
|
||||||
|
retryCounter: Record<string, number> = {};
|
||||||
|
constructor() {
|
||||||
|
console.log("new sqlite worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(filePath: string) {
|
||||||
|
if (this.sqlite) {
|
||||||
|
console.error("Database is already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sqlite = require("better-sqlite3-multiple-ciphers")(
|
||||||
|
filePath
|
||||||
|
).unsafeMode(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper function for preparing SQL statements with caching
|
||||||
|
* to avoid unnecessary computations.
|
||||||
|
*/
|
||||||
|
async prepare(sql: string): Promise<Statement | undefined> {
|
||||||
|
if (!this.sqlite) throw new Error("Database is not initialized.");
|
||||||
|
try {
|
||||||
|
const cached = this.preparedStatements.get(sql);
|
||||||
|
if (cached !== undefined) return cached;
|
||||||
|
|
||||||
|
const prepared = this.sqlite.prepare(sql);
|
||||||
|
if (!prepared) return;
|
||||||
|
|
||||||
|
this.preparedStatements.set(sql, prepared);
|
||||||
|
|
||||||
|
// reset retry count on success
|
||||||
|
this.retryCounter[sql] = 0;
|
||||||
|
return prepared;
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
|
||||||
|
// statement prepare process can be flaky so retry at least 5 times
|
||||||
|
// before giving up.
|
||||||
|
if (this.retryCounter[sql] < 5) {
|
||||||
|
this.retryCounter[sql] = (this.retryCounter[sql] || 0) + 1;
|
||||||
|
console.warn("Failed to prepare statement. Retrying:", sql);
|
||||||
|
return this.prepare(sql);
|
||||||
|
} else this.retryCounter[sql] = 0;
|
||||||
|
|
||||||
|
if (ex instanceof Error) ex.message += ` (query: ${sql})`;
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec<R>(
|
||||||
|
sql: string,
|
||||||
|
parameters: SQLiteCompatibleType[] = []
|
||||||
|
): Promise<QueryResult<R>> {
|
||||||
|
const prepared = await this.prepare(sql);
|
||||||
|
if (!prepared) return { rows: [] };
|
||||||
|
try {
|
||||||
|
if (prepared.reader) {
|
||||||
|
return {
|
||||||
|
rows: prepared.all(parameters) as R[]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const { changes, lastInsertRowid } = prepared.run(parameters);
|
||||||
|
const numAffectedRows =
|
||||||
|
changes !== undefined && changes !== null && !isNaN(changes)
|
||||||
|
? BigInt(changes)
|
||||||
|
: undefined;
|
||||||
|
return {
|
||||||
|
numAffectedRows,
|
||||||
|
insertId:
|
||||||
|
lastInsertRowid !== undefined && lastInsertRowid !== null
|
||||||
|
? typeof lastInsertRowid === "bigint"
|
||||||
|
? lastInsertRowid
|
||||||
|
: BigInt(lastInsertRowid)
|
||||||
|
: undefined,
|
||||||
|
rows: [] as R[]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) e.message += ` (query: ${sql})`;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async run<R>(
|
||||||
|
sql: string,
|
||||||
|
parameters?: SQLiteCompatibleType[]
|
||||||
|
): Promise<QueryResult<R>> {
|
||||||
|
if (!this.sqlite) throw new Error("No database is not opened.");
|
||||||
|
return await this.exec(sql, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
if (!this.sqlite) return;
|
||||||
|
|
||||||
|
this.preparedStatements.clear();
|
||||||
|
this.sqlite.close();
|
||||||
|
this.sqlite = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(filePath: string) {
|
||||||
|
await this.close();
|
||||||
|
await require("fs/promises").rm(filePath, {
|
||||||
|
force: true,
|
||||||
|
maxRetries: 5,
|
||||||
|
retryDelay: 500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,16 +28,20 @@ type UpdateInfo = { version: string };
|
|||||||
type Progress = { percent: number };
|
type Progress = { percent: number };
|
||||||
|
|
||||||
const t = initTRPC.create();
|
const t = initTRPC.create();
|
||||||
|
let cancellationToken: CancellationToken | undefined = undefined;
|
||||||
|
|
||||||
export const updaterRouter = t.router({
|
export const updaterRouter = t.router({
|
||||||
autoUpdates: t.procedure.query(() => config.automaticUpdates),
|
autoUpdates: t.procedure.query(() => config.automaticUpdates),
|
||||||
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
|
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
|
||||||
download: t.procedure.query(async () => {
|
download: t.procedure.query(async () => {
|
||||||
const cancellationToken = new CancellationToken();
|
if (cancellationToken) return;
|
||||||
await autoUpdater.downloadUpdate(cancellationToken);
|
cancellationToken = new CancellationToken();
|
||||||
|
await autoUpdater
|
||||||
|
.downloadUpdate(cancellationToken)
|
||||||
|
.finally(() => (cancellationToken = undefined));
|
||||||
}),
|
}),
|
||||||
check: t.procedure.query(async () => {
|
check: t.procedure.query(async () => {
|
||||||
await autoUpdater.checkForUpdates();
|
await autoUpdater.checkForUpdates().catch(console.error);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
toggleAutoUpdates: t.procedure
|
toggleAutoUpdates: t.procedure
|
||||||
@@ -57,7 +61,18 @@ export const updaterRouter = t.router({
|
|||||||
"update-not-available"
|
"update-not-available"
|
||||||
),
|
),
|
||||||
onAvailable: createSubscription<"update-available", UpdateInfo>(
|
onAvailable: createSubscription<"update-available", UpdateInfo>(
|
||||||
"update-available"
|
"update-available",
|
||||||
|
() => {
|
||||||
|
if (!config.automaticUpdates) return false;
|
||||||
|
autoUpdater.emit("download-progress", {
|
||||||
|
bytesPerSecond: 0,
|
||||||
|
delta: 0,
|
||||||
|
percent: 0,
|
||||||
|
total: 100,
|
||||||
|
transferred: 0
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
),
|
),
|
||||||
onError: createSubscription("error")
|
onError: createSubscription("error")
|
||||||
});
|
});
|
||||||
@@ -65,12 +80,14 @@ export const updaterRouter = t.router({
|
|||||||
function createSubscription<
|
function createSubscription<
|
||||||
TName extends keyof AppUpdaterEvents,
|
TName extends keyof AppUpdaterEvents,
|
||||||
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
|
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
|
||||||
>(eventName: TName) {
|
>(eventName: TName, handler?: (args: TReturnType) => boolean) {
|
||||||
return t.procedure.subscription(() => {
|
return t.procedure.subscription(() => {
|
||||||
return observable<TReturnType>((emit) => {
|
return observable<TReturnType>((emit) => {
|
||||||
const listener: AppUpdaterEvents[TName] = (...args: any[]) => {
|
const listener: AppUpdaterEvents[TName] = (...args: any[]) => {
|
||||||
|
if (handler?.(args[0])) return;
|
||||||
emit.next(args[0]);
|
emit.next(args[0]);
|
||||||
};
|
};
|
||||||
|
autoUpdater.removeAllListeners(eventName);
|
||||||
autoUpdater.addListener(eventName, listener);
|
autoUpdater.addListener(eventName, listener);
|
||||||
return () => {
|
return () => {
|
||||||
autoUpdater.removeListener(eventName, listener);
|
autoUpdater.removeListener(eventName, listener);
|
||||||
|
|||||||
83
apps/desktop/src/api/window.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
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 { initTRPC } from "@trpc/server";
|
||||||
|
import { observable } from "@trpc/server/observable";
|
||||||
|
|
||||||
|
const t = initTRPC.create();
|
||||||
|
|
||||||
|
export const windowRouter = t.router({
|
||||||
|
maximize: t.procedure.mutation(() => {
|
||||||
|
globalThis.window?.maximize();
|
||||||
|
}),
|
||||||
|
restore: t.procedure.mutation(() => {
|
||||||
|
globalThis.window?.restore();
|
||||||
|
}),
|
||||||
|
minimze: t.procedure.mutation(() => {
|
||||||
|
globalThis.window?.minimize();
|
||||||
|
}),
|
||||||
|
maximized: t.procedure.query(() => globalThis.window?.isMaximized()),
|
||||||
|
fullscreen: t.procedure.query(() => globalThis.window?.isFullScreen()),
|
||||||
|
onWindowStateChanged: t.procedure.subscription(() => {
|
||||||
|
return observable<{ maximized: boolean; fullscreen: boolean }>((emit) => {
|
||||||
|
function listener() {
|
||||||
|
emit.next({
|
||||||
|
maximized: !!globalThis.window?.isMaximized(),
|
||||||
|
fullscreen: !!globalThis.window?.isFullScreen()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function enterFullscreen() {
|
||||||
|
emit.next({
|
||||||
|
maximized: !!globalThis.window?.isMaximized(),
|
||||||
|
fullscreen: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function leaveFullscreen() {
|
||||||
|
emit.next({
|
||||||
|
maximized: !!globalThis.window?.isMaximized(),
|
||||||
|
fullscreen: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
globalThis.window?.addListener("maximize", listener);
|
||||||
|
globalThis.window?.addListener("minimize", listener);
|
||||||
|
globalThis.window?.addListener("unmaximize", listener);
|
||||||
|
globalThis.window?.addListener("restore", listener);
|
||||||
|
globalThis.window?.addListener("enter-full-screen", enterFullscreen);
|
||||||
|
globalThis.window?.addListener("leave-full-screen", leaveFullscreen);
|
||||||
|
globalThis.window?.addListener("leave-html-full-screen", leaveFullscreen);
|
||||||
|
globalThis.window?.addListener("enter-html-full-screen", enterFullscreen);
|
||||||
|
return () => {
|
||||||
|
globalThis.window?.removeListener("maximize", listener);
|
||||||
|
globalThis.window?.removeListener("minimize", listener);
|
||||||
|
globalThis.window?.removeListener("unmaximize", listener);
|
||||||
|
globalThis.window?.removeListener("restore", listener);
|
||||||
|
globalThis.window?.removeListener("enter-full-screen", enterFullscreen);
|
||||||
|
globalThis.window?.removeListener("leave-full-screen", leaveFullscreen);
|
||||||
|
globalThis.window?.removeListener(
|
||||||
|
"leave-html-full-screen",
|
||||||
|
leaveFullscreen
|
||||||
|
);
|
||||||
|
globalThis.window?.removeListener(
|
||||||
|
"enter-html-full-screen",
|
||||||
|
enterFullscreen
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -21,3 +21,4 @@ export * from "./constants";
|
|||||||
export type { AppRouter } from "./api";
|
export type { AppRouter } from "./api";
|
||||||
export { type UpdateInfo } from "builder-util-runtime";
|
export { type UpdateInfo } from "builder-util-runtime";
|
||||||
export { type DesktopIntegration } from "./utils/config";
|
export { type DesktopIntegration } from "./utils/config";
|
||||||
|
export { SQLite } from "./api/sqlite-kysely.js";
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import path from "path";
|
|||||||
import { bringToFront } from "./utils/bring-to-front";
|
import { bringToFront } from "./utils/bring-to-front";
|
||||||
import { bridge } from "./api/bridge";
|
import { bridge } from "./api/bridge";
|
||||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||||
|
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
|
||||||
|
|
||||||
// only run a single instance
|
// only run a single instance
|
||||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||||
@@ -82,10 +83,28 @@ async function createWindow() {
|
|||||||
size: 512,
|
size: 512,
|
||||||
format: process.platform === "win32" ? "ico" : "png"
|
format: process.platform === "win32" ? "ico" : "png"
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
...(config.desktopSettings.nativeTitlebar
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
titleBarStyle: "hidden",
|
||||||
|
frame: process.platform === "win32" || process.platform === "darwin",
|
||||||
|
titleBarOverlay: {
|
||||||
|
height: 37,
|
||||||
|
color: "#00000000",
|
||||||
|
symbolColor: config.windowControlsIconColor
|
||||||
|
},
|
||||||
|
trafficLightPosition: {
|
||||||
|
x: 16,
|
||||||
|
y: 12
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
zoomFactor: config.zoomFactor,
|
zoomFactor: config.zoomFactor,
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
|
nodeIntegrationInWorker: true,
|
||||||
spellcheck: config.isSpellCheckerEnabled,
|
spellcheck: config.isSpellCheckerEnabled,
|
||||||
preload: __dirname + "/preload.js"
|
preload: __dirname + "/preload.js"
|
||||||
}
|
}
|
||||||
@@ -112,6 +131,7 @@ async function createWindow() {
|
|||||||
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
|
||||||
"http://dictionaries.notesnook.com/"
|
"http://dictionaries.notesnook.com/"
|
||||||
);
|
);
|
||||||
|
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
|
||||||
|
|
||||||
mainWindow.once("closed", () => {
|
mainWindow.once("closed", () => {
|
||||||
globalThis.window = null;
|
globalThis.window = null;
|
||||||
@@ -121,7 +141,7 @@ async function createWindow() {
|
|||||||
setupJumplist();
|
setupJumplist();
|
||||||
|
|
||||||
if (isDevelopment())
|
if (isDevelopment())
|
||||||
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
|
mainWindow.webContents.openDevTools({ mode: "bottom", activate: true });
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
shell.openExternal(details.url);
|
shell.openExternal(details.url);
|
||||||
@@ -137,6 +157,9 @@ async function createWindow() {
|
|||||||
app.once("ready", async () => {
|
app.once("ready", async () => {
|
||||||
console.info("App ready. Opening window.");
|
console.info("App ready. Opening window.");
|
||||||
|
|
||||||
|
if (config.customDns) enableCustomDns();
|
||||||
|
else disableCustomDns();
|
||||||
|
|
||||||
if (!isDevelopment()) registerProtocol();
|
if (!isDevelopment()) registerProtocol();
|
||||||
await createWindow();
|
await createWindow();
|
||||||
configureAutoUpdater();
|
configureAutoUpdater();
|
||||||
|
|||||||
@@ -23,13 +23,11 @@ import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
|
|||||||
import type { NNCrypto } from "@notesnook/crypto";
|
import type { NNCrypto } from "@notesnook/crypto";
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { platform } from "os";
|
import { platform } from "os";
|
||||||
import sqlite3, { Database } from "better-sqlite3-multiple-ciphers";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var os: () => "mas" | ReturnType<typeof platform>;
|
var os: () => "mas" | ReturnType<typeof platform>;
|
||||||
var electronTRPC: RendererGlobalElectronTRPC;
|
var electronTRPC: RendererGlobalElectronTRPC;
|
||||||
var NativeNNCrypto: (new () => NNCrypto) | undefined;
|
var NativeNNCrypto: (new () => NNCrypto) | undefined;
|
||||||
var createSQLite3Database: (filename: string) => Database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process.once("loaded", async () => {
|
process.once("loaded", async () => {
|
||||||
@@ -43,5 +41,4 @@ process.once("loaded", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
|
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
|
||||||
globalThis.createSQLite3Database = (filename) => sqlite3(filename);
|
|
||||||
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());
|
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ Type=Application
|
|||||||
Version=${app.getVersion()}
|
Version=${app.getVersion()}
|
||||||
Name=${app.getName()}
|
Name=${app.getName()}
|
||||||
Comment=${app.getName()} startup script
|
Comment=${app.getName()} startup script
|
||||||
Exec=${process.execPath}${hidden ? " --hidden" : ""}
|
Exec=${
|
||||||
|
process.env.APPIMAGE
|
||||||
|
? `${process.env.APPIMAGE}${hidden ? " --hidden" : ""}`
|
||||||
|
: `${process.execPath}${hidden ? " --hidden" : ""}`
|
||||||
|
}
|
||||||
StartupNotify=false
|
StartupNotify=false
|
||||||
Terminal=false`;
|
Terminal=false`;
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ async function configureAutoUpdater() {
|
|||||||
autoUpdater.allowDowngrade = false;
|
autoUpdater.allowDowngrade = false;
|
||||||
autoUpdater.allowPrerelease = false;
|
autoUpdater.allowPrerelease = false;
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
autoUpdater.disableWebInstaller = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { configureAutoUpdater };
|
export { configureAutoUpdater };
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const DesktopIntegration = z.object({
|
|||||||
autoStart: z.boolean().optional(),
|
autoStart: z.boolean().optional(),
|
||||||
startMinimized: z.boolean().optional(),
|
startMinimized: z.boolean().optional(),
|
||||||
minimizeToSystemTray: z.boolean().optional(),
|
minimizeToSystemTray: z.boolean().optional(),
|
||||||
closeToSystemTray: z.boolean().optional()
|
closeToSystemTray: z.boolean().optional(),
|
||||||
|
nativeTitlebar: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
|
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
|
||||||
@@ -35,13 +36,20 @@ export const config = {
|
|||||||
autoStart: false,
|
autoStart: false,
|
||||||
startMinimized: false,
|
startMinimized: false,
|
||||||
minimizeToSystemTray: false,
|
minimizeToSystemTray: false,
|
||||||
closeToSystemTray: false
|
closeToSystemTray: false,
|
||||||
|
nativeTitlebar: false
|
||||||
},
|
},
|
||||||
privacyMode: false,
|
privacyMode: false,
|
||||||
isSpellCheckerEnabled: true,
|
isSpellCheckerEnabled: true,
|
||||||
zoomFactor: 1,
|
zoomFactor: 1,
|
||||||
theme: nativeTheme.themeSource,
|
theme: nativeTheme.themeSource,
|
||||||
automaticUpdates: true
|
automaticUpdates: true,
|
||||||
|
proxyRules: "",
|
||||||
|
customDns: true,
|
||||||
|
|
||||||
|
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||||
|
windowControlsIconColor:
|
||||||
|
nativeTheme.themeSource === "dark" ? "#ffffff" : "#000000"
|
||||||
};
|
};
|
||||||
|
|
||||||
type ConfigKey = keyof typeof config;
|
type ConfigKey = keyof typeof config;
|
||||||
|
|||||||
37
apps/desktop/src/utils/custom-dns.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
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";
|
||||||
|
|
||||||
|
export function enableCustomDns() {
|
||||||
|
app.configureHostResolver({
|
||||||
|
secureDnsServers: [
|
||||||
|
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||||
|
"https://dns.quad9.net/dns-query"
|
||||||
|
],
|
||||||
|
enableBuiltInResolver: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disableCustomDns() {
|
||||||
|
app.configureHostResolver({
|
||||||
|
secureDnsServers: [],
|
||||||
|
enableBuiltInResolver: true
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -131,6 +131,17 @@ function setupMenu() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.mediaType === "image")
|
||||||
|
menu.append(
|
||||||
|
new MenuItem({
|
||||||
|
id: "copy-image",
|
||||||
|
label: "Copy Image",
|
||||||
|
click() {
|
||||||
|
globalThis.window?.webContents.copyImageAt(params.x, params.y);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (params.isEditable)
|
if (params.isEditable)
|
||||||
menu.append(
|
menu.append(
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
|
|||||||
@@ -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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { protocol } from "electron";
|
import { net, protocol } from "electron";
|
||||||
import { isDevelopment } from "./index";
|
import { isDevelopment } from "./index";
|
||||||
import { createReadStream } from "fs";
|
import { createReadStream } from "fs";
|
||||||
import { extname, normalize } from "path";
|
import { extname, normalize } from "path";
|
||||||
@@ -62,26 +62,7 @@ function registerProtocol() {
|
|||||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (request.headers.has("X-Content-Length")) {
|
return net.fetch(request, { bypassCustomProtocolHandlers: true });
|
||||||
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 "successful"`);
|
console.info(`${SCHEME} protocol inteception "successful"`);
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ function setTheme(theme: Theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getBackgroundColor() {
|
function getBackgroundColor() {
|
||||||
return nativeTheme.shouldUseDarkColors ? "#0f0f0f" : "#ffffff";
|
return (
|
||||||
|
config.backgroundColor ||
|
||||||
|
(nativeTheme.shouldUseDarkColors ? "#0f0f0f" : "#ffffff")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemTheme() {
|
function getSystemTheme() {
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import "@azure/core-asynciterator-polyfill";
|
|
||||||
import "react-native-gesture-handler";
|
|
||||||
import {
|
import {
|
||||||
THEME_COMPATIBILITY_VERSION,
|
THEME_COMPATIBILITY_VERSION,
|
||||||
useThemeEngineStore
|
useThemeEngineStore
|
||||||
@@ -42,42 +40,25 @@ import { useUserStore } from "./stores/use-user-store";
|
|||||||
I18nManager.allowRTL(false);
|
I18nManager.allowRTL(false);
|
||||||
I18nManager.forceRTL(false);
|
I18nManager.forceRTL(false);
|
||||||
I18nManager.swapLeftAndRightInRTL(false);
|
I18nManager.swapLeftAndRightInRTL(false);
|
||||||
|
const { appLockEnabled, appLockMode } = SettingsService.get();
|
||||||
// How app lock works
|
if (appLockEnabled || appLockMode !== "none") {
|
||||||
// 1. User goes to settings and setup app lock with a Pin/Password.
|
useUserStore.getState().lockApp(true);
|
||||||
// 2. The Pin/Password is used to encrypt a random value or user's encryption key.
|
}
|
||||||
// 3. The encrypted value is stored in MMKV
|
|
||||||
// 4. When the app launches, the same value is decrypted with user provided key, if it works, we launch the app otherwise it remains locked.
|
|
||||||
// 5. If Biometrics are enabled, the app lock pin/password is stored in keychain. the value can be accessed if fingerprint auth works ONLY.
|
|
||||||
// 6. User can manually enter the pin if biometrics fails.
|
|
||||||
// 7. There is no way to enter the app if user forgets the PIN. The only way is to reset app data and start fresh again.
|
|
||||||
|
|
||||||
// How to handle app lock for existing users...
|
|
||||||
// 1.
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const init = useAppEvents();
|
useAppEvents();
|
||||||
|
//@ts-ignore
|
||||||
|
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { appLockEnabled, appLockMode } = SettingsService.get();
|
SettingsService.onFirstLaunch();
|
||||||
if (appLockEnabled || appLockMode !== "none") {
|
|
||||||
useUserStore.getState().lockApp(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
|
||||||
init();
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
SettingsService.onFirstLaunch();
|
|
||||||
await Notifications.get();
|
await Notifications.get();
|
||||||
if (SettingsService.get().notifNotes) {
|
if (SettingsService.get().notifNotes) {
|
||||||
Notifications.pinQuickNote(true);
|
Notifications.pinQuickNote(true);
|
||||||
}
|
}
|
||||||
TipManager.init();
|
TipManager.init();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 108 KiB |
@@ -25,6 +25,7 @@ import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
|
|||||||
import { generateSecureRandom } from "react-native-securerandom";
|
import { generateSecureRandom } from "react-native-securerandom";
|
||||||
import { DatabaseLogger } from ".";
|
import { DatabaseLogger } from ".";
|
||||||
import { MMKV } from "./mmkv";
|
import { MMKV } from "./mmkv";
|
||||||
|
import { ToastManager } from "../../services/event-manager";
|
||||||
|
|
||||||
// Database key cipher is persisted across different user sessions hence it has
|
// Database key cipher is persisted across different user sessions hence it has
|
||||||
// it's independent storage which we will never clear. This is only used when application has
|
// it's independent storage which we will never clear. This is only used when application has
|
||||||
@@ -43,9 +44,8 @@ const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
|
|||||||
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
|
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
|
||||||
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
|
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
|
||||||
|
|
||||||
const NOTESNOOK_APPLOCK_KEY_SALT = "3dqclWbOYllfk9kk";
|
const NOTESNOOK_APPLOCK_KEY_SALT = "kBwr1Kre86ebOZ8ThLu2OA";
|
||||||
const NOTESNOOK_DB_KEY_SALT = "2rcgSprDmRvZ1AAa";
|
const NOTESNOOK_DB_KEY_SALT = "SNuzOcEK3amoqL0WvPeKqw";
|
||||||
const NOTESNOOK_USER_KEY_SALT = "7qO4qeoM6PbsAJ0Q";
|
|
||||||
|
|
||||||
const DB_KEY_CIPHER = "databaseKeyCipher";
|
const DB_KEY_CIPHER = "databaseKeyCipher";
|
||||||
const USER_KEY_CIPHER = "userKeyCipher";
|
const USER_KEY_CIPHER = "userKeyCipher";
|
||||||
@@ -117,9 +117,7 @@ export async function setAppLockVerificationCipher(appLockPassword) {
|
|||||||
NOTESNOOK_APPLOCK_KEY_SALT
|
NOTESNOOK_APPLOCK_KEY_SALT
|
||||||
);
|
);
|
||||||
const encrypted = await encrypt(appLockCredentials, generatePassword());
|
const encrypted = await encrypt(appLockCredentials, generatePassword());
|
||||||
|
|
||||||
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
|
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
|
||||||
|
|
||||||
DatabaseLogger.info("setAppLockVerificationCipher");
|
DatabaseLogger.info("setAppLockVerificationCipher");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
@@ -135,12 +133,9 @@ export async function validateAppLockPassword(appLockPassword) {
|
|||||||
try {
|
try {
|
||||||
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
|
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
|
||||||
if (!appLockCipher) return true;
|
if (!appLockCipher) return true;
|
||||||
const decrypted = await decrypt(
|
const key = await Sodium.deriveKey(appLockPassword, appLockCipher.salt);
|
||||||
{
|
const decrypted = await decrypt(key, appLockCipher);
|
||||||
password: appLockPassword
|
|
||||||
},
|
|
||||||
appLockCipher
|
|
||||||
);
|
|
||||||
DatabaseLogger.info(
|
DatabaseLogger.info(
|
||||||
`validateAppLockPassword: ${typeof decrypted === "string"}`
|
`validateAppLockPassword: ${typeof decrypted === "string"}`
|
||||||
);
|
);
|
||||||
@@ -215,7 +210,7 @@ export async function getDatabaseKey(appLockPassword) {
|
|||||||
const userKeyCipher = await encrypt(
|
const userKeyCipher = await encrypt(
|
||||||
{
|
{
|
||||||
key: DB_KEY,
|
key: DB_KEY,
|
||||||
salt: NOTESNOOK_USER_KEY_SALT
|
salt: NOTESNOOK_DB_KEY_SALT
|
||||||
},
|
},
|
||||||
userKeyCredentials.password
|
userKeyCredentials.password
|
||||||
);
|
);
|
||||||
@@ -228,6 +223,7 @@ export async function getDatabaseKey(appLockPassword) {
|
|||||||
|
|
||||||
return DB_KEY;
|
return DB_KEY;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
ToastManager.error(e, "Error getting database key");
|
||||||
console.log(e, "error");
|
console.log(e, "error");
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e);
|
||||||
return null;
|
return null;
|
||||||
@@ -240,7 +236,7 @@ export async function deriveCryptoKey(data) {
|
|||||||
const userKeyCipher = await encrypt(
|
const userKeyCipher = await encrypt(
|
||||||
{
|
{
|
||||||
key: await getDatabaseKey(),
|
key: await getDatabaseKey(),
|
||||||
salt: NOTESNOOK_USER_KEY_SALT
|
salt: NOTESNOOK_DB_KEY_SALT
|
||||||
},
|
},
|
||||||
credentials.key
|
credentials.key
|
||||||
);
|
);
|
||||||
@@ -265,13 +261,12 @@ export async function getCryptoKey(_name) {
|
|||||||
|
|
||||||
const key = await decrypt(
|
const key = await decrypt(
|
||||||
{
|
{
|
||||||
key: await getDatabaseKey()
|
key: await getDatabaseKey(),
|
||||||
|
salt: keyCipher.salt
|
||||||
},
|
},
|
||||||
keyCipher
|
keyCipher
|
||||||
);
|
);
|
||||||
|
|
||||||
DatabaseLogger.info("User key decrypted: ", !!key);
|
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("getCryptoKey", e);
|
console.log("getCryptoKey", e);
|
||||||
@@ -319,7 +314,6 @@ export async function decrypt(password, data) {
|
|||||||
_data.output = "plain";
|
_data.output = "plain";
|
||||||
|
|
||||||
if (!password.salt) password.salt = data.salt;
|
if (!password.salt) password.salt = data.salt;
|
||||||
|
|
||||||
return await Sodium.decrypt(password, _data);
|
return await Sodium.decrypt(password, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,4 +85,10 @@ export async function setupDatabase(password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const db = database;
|
export const db = database;
|
||||||
export const DatabaseLogger = dbLogger;
|
let DatabaseLogger = dbLogger.scope(Platform.OS);
|
||||||
|
|
||||||
|
const setLogger = () => {
|
||||||
|
DatabaseLogger = dbLogger.scope(Platform.OS);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { DatabaseLogger, setLogger };
|
||||||
|
|||||||
@@ -16,14 +16,30 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
|
||||||
import { initialize } from "@notesnook/core/dist/logger";
|
import { initialize } from "@notesnook/core/dist/logger";
|
||||||
import { KV } from "./storage";
|
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { setLogger } from ".";
|
||||||
|
import { RNSqliteDriver } from "./sqlite.kysely";
|
||||||
|
|
||||||
const LoggerStorage = new MMKVLoader()
|
let loggerLoaded = false;
|
||||||
.withInstanceID("notesnook_logs")
|
const initializeLogger = async () => {
|
||||||
.initialize();
|
if (loggerLoaded) return;
|
||||||
|
await initialize({
|
||||||
|
dialect: (name) => ({
|
||||||
|
createDriver: () => {
|
||||||
|
return new RNSqliteDriver({ async: true, dbName: name });
|
||||||
|
},
|
||||||
|
createAdapter: () => new SqliteAdapter(),
|
||||||
|
createIntrospector: (db) => new SqliteIntrospector(db),
|
||||||
|
createQueryCompiler: () => new SqliteQueryCompiler()
|
||||||
|
}),
|
||||||
|
tempStore: "memory",
|
||||||
|
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL"
|
||||||
|
});
|
||||||
|
setLogger();
|
||||||
|
loggerLoaded = true;
|
||||||
|
};
|
||||||
|
|
||||||
initialize(new KV(LoggerStorage));
|
export { initializeLogger };
|
||||||
|
|
||||||
export { LoggerStorage };
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import RNFetchBlob from "react-native-blob-util";
|
|||||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||||
import { ToastManager, presentSheet } from "../../services/event-manager";
|
import { ToastManager, presentSheet } from "../../services/event-manager";
|
||||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { db } from "../database";
|
import { DatabaseLogger, db } from "../database";
|
||||||
import Storage from "../database/storage";
|
import Storage from "../database/storage";
|
||||||
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
|
||||||
import { createCacheDir, exists } from "./io";
|
import { createCacheDir, exists } from "./io";
|
||||||
@@ -84,8 +84,10 @@ export async function downloadAttachments(
|
|||||||
Platform.OS === "ios"
|
Platform.OS === "ios"
|
||||||
? `${outputFolder}/notesnook-attachments-${Date.now()}.zip`
|
? `${outputFolder}/notesnook-attachments-${Date.now()}.zip`
|
||||||
: `${cacheDir}/notesnook-attachments.zip`;
|
: `${cacheDir}/notesnook-attachments.zip`;
|
||||||
if (await RNFetchBlob.fs.exists(zipSourceFolder))
|
if (await RNFetchBlob.fs.exists(zipSourceFolder)) {
|
||||||
await RNFetchBlob.fs.unlink(zipSourceFolder);
|
await RNFetchBlob.fs.unlink(zipSourceFolder);
|
||||||
|
}
|
||||||
|
|
||||||
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
await RNFetchBlob.fs.mkdir(zipSourceFolder);
|
||||||
|
|
||||||
for (let i = 0; i < attachments.length; i++) {
|
for (let i = 0; i < attachments.length; i++) {
|
||||||
@@ -127,7 +129,7 @@ export async function downloadAttachments(
|
|||||||
status: FileDownloadStatus.Fail,
|
status: FileDownloadStatus.Fail,
|
||||||
reason: e
|
reason: e
|
||||||
});
|
});
|
||||||
console.log("Error downloading attachment", e);
|
ToastManager.error(e, "Error downloading attachment");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canceled.current) {
|
if (canceled.current) {
|
||||||
@@ -164,7 +166,7 @@ export async function downloadAttachments(
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
releasePermissions(outputFolder);
|
releasePermissions(outputFolder);
|
||||||
sub?.remove();
|
sub?.remove();
|
||||||
console.log("Error zipping attachments", e);
|
ToastManager.error(e, "Error zipping attachments");
|
||||||
}
|
}
|
||||||
// Remove source & zip file from cache.
|
// Remove source & zip file from cache.
|
||||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
|
||||||
@@ -192,7 +194,7 @@ export default async function downloadAttachment(
|
|||||||
|
|
||||||
let attachment = await db.attachments.attachment(hash);
|
let attachment = await db.attachments.attachment(hash);
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
console.log("attachment not found");
|
DatabaseLogger.log("Attachment not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,19 +209,16 @@ export default async function downloadAttachment(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(
|
|
||||||
"starting download attachment",
|
|
||||||
attachment.hash,
|
|
||||||
options.groupId
|
|
||||||
);
|
|
||||||
await db
|
await db
|
||||||
.fs()
|
.fs()
|
||||||
.downloadFile(options.groupId || attachment.hash, attachment.hash);
|
.downloadFile(options.groupId || attachment.hash, attachment.hash);
|
||||||
if (!(await exists(attachment.hash))) {
|
if (!(await exists(attachment.hash))) {
|
||||||
|
DatabaseLogger.log("Attachment does not exist after download.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.base64 || options.text) {
|
if (options.base64 || options.text) {
|
||||||
|
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
|
||||||
return await db.attachments.read(
|
return await db.attachments.read(
|
||||||
attachment.hash,
|
attachment.hash,
|
||||||
options.base64 ? "base64" : "text"
|
options.base64 ? "base64" : "text"
|
||||||
@@ -232,6 +231,7 @@ export default async function downloadAttachment(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let key = await db.attachments.decryptKey(attachment.key);
|
let key = await db.attachments.decryptKey(attachment.key);
|
||||||
|
|
||||||
let info = {
|
let info = {
|
||||||
iv: attachment.iv,
|
iv: attachment.iv,
|
||||||
salt: attachment.salt,
|
salt: attachment.salt,
|
||||||
@@ -245,7 +245,6 @@ export default async function downloadAttachment(
|
|||||||
chunkSize: attachment.chunkSize,
|
chunkSize: attachment.chunkSize,
|
||||||
appGroupId: IOS_APPGROUPID
|
appGroupId: IOS_APPGROUPID
|
||||||
};
|
};
|
||||||
|
|
||||||
let fileUri = await Sodium.decryptFile(
|
let fileUri = await Sodium.decryptFile(
|
||||||
key,
|
key,
|
||||||
info,
|
info,
|
||||||
@@ -288,7 +287,7 @@ export default async function downloadAttachment(
|
|||||||
|
|
||||||
return fileUri;
|
return fileUri;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("download attachment error: ", e);
|
DatabaseLogger.error(e);
|
||||||
if (attachment.dateUploaded) {
|
if (attachment.dateUploaded) {
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
|
||||||
|
|||||||
@@ -22,20 +22,24 @@ import NetInfo from "@react-native-community/netinfo";
|
|||||||
import RNFetchBlob from "react-native-blob-util";
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
import { ToastManager } from "../../services/event-manager";
|
import { ToastManager } from "../../services/event-manager";
|
||||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||||
import { db } from "../database";
|
import { DatabaseLogger, db } from "../database";
|
||||||
import { cacheDir, fileCheck } from "./utils";
|
import { cacheDir, fileCheck } from "./utils";
|
||||||
import { createCacheDir, exists } from "./io";
|
import { createCacheDir, exists } from "./io";
|
||||||
|
|
||||||
export async function downloadFile(filename, data, cancelToken) {
|
export async function downloadFile(filename, data, cancelToken) {
|
||||||
if (!data) return false;
|
if (!data) {
|
||||||
|
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseLogger.log(`Downloading ${filename}`);
|
||||||
await createCacheDir();
|
await createCacheDir();
|
||||||
|
|
||||||
let { url, headers } = data;
|
let { url, headers } = data;
|
||||||
let path = `${cacheDir}/${filename}`;
|
let path = `${cacheDir}/${filename}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await exists(filename)) {
|
if (await exists(filename)) {
|
||||||
|
DatabaseLogger.log(`File Exists already: ${filename}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,13 +47,24 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
if (!res.ok)
|
|
||||||
|
if (!res.ok) {
|
||||||
|
DatabaseLogger.log(
|
||||||
|
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
|
||||||
|
);
|
||||||
throw new Error(`${res.status}: Unable to resolve download url`);
|
throw new Error(`${res.status}: Unable to resolve download url`);
|
||||||
|
}
|
||||||
|
|
||||||
const downloadUrl = await res.text();
|
const downloadUrl = await res.text();
|
||||||
|
|
||||||
if (!downloadUrl) throw new Error("Unable to resolve download url");
|
if (!downloadUrl) {
|
||||||
|
DatabaseLogger.log(
|
||||||
|
`Error downloading file: ${filename}, reason: Unable to resolve download url`
|
||||||
|
);
|
||||||
|
throw new Error("Unable to resolve download url");
|
||||||
|
}
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
console.log("Download starting");
|
DatabaseLogger.log(`Download starting: ${filename}`);
|
||||||
let request = RNFetchBlob.config({
|
let request = RNFetchBlob.config({
|
||||||
path: path,
|
path: path,
|
||||||
IOSBackgroundTask: true
|
IOSBackgroundTask: true
|
||||||
@@ -60,13 +75,15 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
.getState()
|
.getState()
|
||||||
.setProgress(0, total, filename, recieved, "download");
|
.setProgress(0, total, filename, recieved, "download");
|
||||||
totalSize = total;
|
totalSize = total;
|
||||||
console.log("downloading: ", recieved, total);
|
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelToken.cancel = () => {
|
cancelToken.cancel = () => {
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
request.cancel();
|
request.cancel();
|
||||||
|
DatabaseLogger.log(`Download cancelled: ${filename}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await request;
|
let response = await request;
|
||||||
await fileCheck(response, totalSize);
|
await fileCheck(response, totalSize);
|
||||||
let status = response.info().status;
|
let status = response.info().status;
|
||||||
@@ -90,7 +107,10 @@ export async function downloadFile(filename, data, cancelToken) {
|
|||||||
|
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||||
console.log("Download file error:", e, url, headers);
|
DatabaseLogger.error(e, {
|
||||||
|
url,
|
||||||
|
headers
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,18 +119,17 @@ export async function getUploadedFileSize(hash) {
|
|||||||
try {
|
try {
|
||||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||||
const token = await db.tokenManager.getAccessToken();
|
const token = await db.tokenManager.getAccessToken();
|
||||||
|
|
||||||
const attachmentInfo = await fetch(url, {
|
const attachmentInfo = await fetch(url, {
|
||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
|
|
||||||
const contentLength = parseInt(
|
const contentLength = parseInt(
|
||||||
attachmentInfo.headers?.get("content-length")
|
attachmentInfo.headers?.get("content-length")
|
||||||
);
|
);
|
||||||
return isNaN(contentLength) ? 0 : contentLength;
|
return isNaN(contentLength) ? 0 : contentLength;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0;
|
DatabaseLogger.error(e);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +143,9 @@ export async function checkAttachment(hash) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const size = await getUploadedFileSize(hash);
|
const size = await getUploadedFileSize(hash);
|
||||||
if (size <= 0) return { failed: "File length is 0." };
|
if (size === -1) return { success: true };
|
||||||
|
|
||||||
|
if (size === 0) return { failed: "File length is 0." };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { failed: e?.message };
|
return { failed: e?.message };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import Sodium from "@ammarahmed/react-native-sodium";
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import RNFetchBlob from "react-native-blob-util";
|
import RNFetchBlob from "react-native-blob-util";
|
||||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||||
import { db } from "../database";
|
import { DatabaseLogger, db } from "../database";
|
||||||
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
|
||||||
|
|
||||||
export async function readEncrypted(filename, key, cipherData) {
|
export async function readEncrypted(filename, key, cipherData) {
|
||||||
await migrateFilesFromCache();
|
await migrateFilesFromCache();
|
||||||
console.log("Read encrypted file...");
|
DatabaseLogger.log("Read encrypted file...");
|
||||||
let path = `${cacheDir}/${filename}`;
|
let path = `${cacheDir}/${filename}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -34,8 +34,6 @@ export async function readEncrypted(filename, key, cipherData) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = await db.attachments.attachment(filename);
|
|
||||||
console.log("decrypting....");
|
|
||||||
let output = await Sodium.decryptFile(
|
let output = await Sodium.decryptFile(
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
@@ -45,12 +43,13 @@ export async function readEncrypted(filename, key, cipherData) {
|
|||||||
},
|
},
|
||||||
cipherData.outputType === "base64" ? "base64" : "text"
|
cipherData.outputType === "base64" ? "base64" : "text"
|
||||||
);
|
);
|
||||||
console.log("file decrypted...", attachment?.mimeType);
|
|
||||||
|
DatabaseLogger.log("File decrypted...");
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
RNFetchBlob.fs.unlink(path).catch(console.log);
|
RNFetchBlob.fs.unlink(path).catch(console.log);
|
||||||
console.log("readEncrypted", e);
|
DatabaseLogger.error(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +66,7 @@ export async function hashBase64(data) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeEncryptedBase64({ data, key }) {
|
export async function writeEncryptedBase64(data, key) {
|
||||||
await createCacheDir();
|
await createCacheDir();
|
||||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||||
@@ -77,6 +76,8 @@ export async function writeEncryptedBase64({ data, key }) {
|
|||||||
});
|
});
|
||||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||||
console.log("encrypted file output: ", output);
|
console.log("encrypted file output: ", output);
|
||||||
|
output.size = output.length;
|
||||||
|
delete output.length;
|
||||||
return {
|
return {
|
||||||
...output,
|
...output,
|
||||||
alg: "xcha-stream"
|
alg: "xcha-stream"
|
||||||
@@ -126,7 +127,7 @@ export async function clearFileStorage() {
|
|||||||
export async function createCacheDir() {
|
export async function createCacheDir() {
|
||||||
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
|
||||||
await RNFetchBlob.fs.mkdir(cacheDir);
|
await RNFetchBlob.fs.mkdir(cacheDir);
|
||||||
console.log("Cache directory created");
|
DatabaseLogger.log("Cache directory created");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +187,9 @@ export async function exists(filename) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (stat.size !== expectedFileSize) {
|
if (stat.size !== expectedFileSize) {
|
||||||
|
DatabaseLogger.log(
|
||||||
|
`File size mismatch: ${filename}, expected: ${expectedFileSize}, actual: ${stat.size}`
|
||||||
|
);
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(existsInAppGroup ? appGroupPath : path)
|
.unlink(existsInAppGroup ? appGroupPath : path)
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
|
|||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { IOS_APPGROUPID } from "../../utils/constants";
|
import { IOS_APPGROUPID } from "../../utils/constants";
|
||||||
import { createCacheDir } from "./io";
|
import { createCacheDir } from "./io";
|
||||||
|
import { getUploadedFileSize } from "./download";
|
||||||
|
import { ToastManager } from "../../services/event-manager";
|
||||||
|
|
||||||
export async function uploadFile(filename, data, cancelToken) {
|
export async function uploadFile(filename, data, cancelToken) {
|
||||||
if (!data) return false;
|
if (!data) return false;
|
||||||
@@ -33,13 +35,31 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const uploadedFileSize = await getUploadedFileSize(filename);
|
||||||
|
if (uploadedFileSize === -1) {
|
||||||
|
DatabaseLogger.log("Upload verification failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUploaded = uploadedFileSize !== 0;
|
||||||
|
if (isUploaded) {
|
||||||
|
DatabaseLogger.log(`File ${filename} is already uploaded.`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let res = await fetch(url, {
|
let res = await fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers
|
headers
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
|
|
||||||
const uploadUrl = await res.text();
|
const uploadUrl = res.ok ? await res.text() : await res.json();
|
||||||
if (!uploadUrl) throw new Error("Unable to resolve attachment upload url");
|
|
||||||
|
if (typeof uploadUrl !== "string") {
|
||||||
|
throw new Error(
|
||||||
|
uploadUrl.error || "Unable to resolve attachment upload url."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let uploadFilePath = `${cacheDir}/${filename}`;
|
let uploadFilePath = `${cacheDir}/${filename}`;
|
||||||
|
|
||||||
const iosAppGroup =
|
const iosAppGroup =
|
||||||
@@ -50,7 +70,15 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||||
if (!exists && Platform.OS === "ios") {
|
if (!exists && Platform.OS === "ios") {
|
||||||
uploadFilePath = appGroupPath;
|
uploadFilePath = appGroupPath;
|
||||||
|
exists = await RNFetchBlob.fs.exists(uploadFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(
|
||||||
|
`Trying to upload file at path ${uploadFilePath} that doest not exist.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
DatabaseLogger.info(`Starting upload: ${filename}`);
|
DatabaseLogger.info(`Starting upload: ${filename}`);
|
||||||
|
|
||||||
let request = RNFetchBlob.config({
|
let request = RNFetchBlob.config({
|
||||||
@@ -104,8 +132,10 @@ export async function uploadFile(filename, data, cancelToken) {
|
|||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
useAttachmentStore.getState().remove(filename);
|
useAttachmentStore.getState().remove(filename);
|
||||||
DatabaseLogger.info(`File upload error: ${filename}, ${e}`);
|
ToastManager.error(e, "File upload failed");
|
||||||
DatabaseLogger.error(e);
|
DatabaseLogger.error(e, "File upload failed", {
|
||||||
|
filename
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,15 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Linking, View } from "react-native";
|
import { Linking, View } from "react-native";
|
||||||
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
|
||||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
|
||||||
import Sync from "../../services/sync";
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { eCloseAnnouncementDialog, eCloseSheet } from "../../utils/events";
|
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||||
|
import { eCloseAnnouncementDialog } from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import { PricingPlans } from "../premium/pricing-plans";
|
import { PricingPlans } from "../premium/pricing-plans";
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
import { Progress } from "../sheets/progress";
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { allowedOnPlatform, getStyle } from "./functions";
|
import { allowedOnPlatform, getStyle } from "./functions";
|
||||||
|
|
||||||
@@ -56,13 +54,6 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} else if (item.type === "force-sync") {
|
|
||||||
eSendEvent(eCloseSheet);
|
|
||||||
await sleep(300);
|
|
||||||
Progress.present();
|
|
||||||
Sync.run("global", true, true, () => {
|
|
||||||
eSendEvent(eCloseSheet);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,10 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
import { Platform, TextInput, View } from "react-native";
|
import {
|
||||||
|
AppStateStatus,
|
||||||
|
Platform,
|
||||||
|
TextInput,
|
||||||
|
useWindowDimensions,
|
||||||
|
View
|
||||||
|
} from "react-native";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import { enabled } from "react-native-privacy-snapshot";
|
|
||||||
import { DatabaseLogger } from "../../common/database";
|
import { DatabaseLogger } from "../../common/database";
|
||||||
import {
|
import {
|
||||||
decrypt,
|
decrypt,
|
||||||
@@ -34,17 +39,20 @@ import {
|
|||||||
import { MMKV } from "../../common/database/mmkv";
|
import { MMKV } from "../../common/database/mmkv";
|
||||||
import { useAppState } from "../../hooks/use-app-state";
|
import { useAppState } from "../../hooks/use-app-state";
|
||||||
import BiometricService from "../../services/biometrics";
|
import BiometricService from "../../services/biometrics";
|
||||||
|
import { ToastManager } from "../../services/event-manager";
|
||||||
import SettingsService from "../../services/settings";
|
import SettingsService from "../../services/settings";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { NotesnookModule } from "../../utils/notesnook-module";
|
import { NotesnookModule } from "../../utils/notesnook-module";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
|
import { Toast } from "../toast";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Input from "../ui/input";
|
import Input from "../ui/input";
|
||||||
import Seperator from "../ui/seperator";
|
import Seperator from "../ui/seperator";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||||
|
|
||||||
const getUser = () => {
|
const getUser = () => {
|
||||||
const user = MMKV.getString("user");
|
const user = MMKV.getString("user");
|
||||||
@@ -74,6 +82,7 @@ const verifyUserPassword = async (password: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AppLockedOverlay = () => {
|
const AppLockedOverlay = () => {
|
||||||
|
const initialLaunchBiometricRequest = useRef(true);
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const user = getUser();
|
const user = getUser();
|
||||||
const appLocked = useUserStore((state) => state.appLocked);
|
const appLocked = useUserStore((state) => state.appLocked);
|
||||||
@@ -82,7 +91,9 @@ const AppLockedOverlay = () => {
|
|||||||
const passwordInputRef = useRef<TextInput>(null);
|
const passwordInputRef = useRef<TextInput>(null);
|
||||||
const password = useRef<string>();
|
const password = useRef<string>();
|
||||||
const appState = useAppState();
|
const appState = useAppState();
|
||||||
|
const lastAppState = useRef<AppStateStatus>(appState);
|
||||||
const biometricUnlockAwaitingUserInput = useRef(false);
|
const biometricUnlockAwaitingUserInput = useRef(false);
|
||||||
|
const { height } = useWindowDimensions();
|
||||||
const keyboardType = useSettingStore(
|
const keyboardType = useSettingStore(
|
||||||
(state) => state.settings.applockKeyboardType
|
(state) => state.settings.applockKeyboardType
|
||||||
);
|
);
|
||||||
@@ -117,7 +128,6 @@ const AppLockedOverlay = () => {
|
|||||||
);
|
);
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
lockApp(false);
|
lockApp(false);
|
||||||
enabled(false);
|
|
||||||
password.current = undefined;
|
password.current = undefined;
|
||||||
}
|
}
|
||||||
biometricUnlockAwaitingUserInput.current = false;
|
biometricUnlockAwaitingUserInput.current = false;
|
||||||
@@ -131,9 +141,8 @@ const AppLockedOverlay = () => {
|
|||||||
if (!password.current) return;
|
if (!password.current) return;
|
||||||
try {
|
try {
|
||||||
const unlocked = appLockHasPasswordSecurity
|
const unlocked = appLockHasPasswordSecurity
|
||||||
? validateAppLockPassword(password.current)
|
? await validateAppLockPassword(password.current)
|
||||||
: await verifyUserPassword(password.current);
|
: await verifyUserPassword(password.current);
|
||||||
|
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
if (!appLockHasPasswordSecurity) {
|
if (!appLockHasPasswordSecurity) {
|
||||||
await setAppLockVerificationCipher(password.current);
|
await setAppLockVerificationCipher(password.current);
|
||||||
@@ -145,8 +154,13 @@ const AppLockedOverlay = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lockApp(false);
|
lockApp(false);
|
||||||
enabled(false);
|
|
||||||
password.current = undefined;
|
password.current = undefined;
|
||||||
|
} else {
|
||||||
|
ToastManager.show({
|
||||||
|
heading: `Invalid ${keyboardType === "numeric" ? "pin" : "password"}`,
|
||||||
|
type: "error",
|
||||||
|
context: "local"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -154,32 +168,53 @@ const AppLockedOverlay = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const prevState = lastAppState.current;
|
||||||
|
lastAppState.current = appState;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
biometricUnlockAwaitingUserInput.current ||
|
|
||||||
useUserStore.getState().disableAppLockRequests
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
if (
|
|
||||||
appLocked &&
|
|
||||||
appState === "active" &&
|
appState === "active" &&
|
||||||
!useSettingStore.getState().requestBiometrics
|
(prevState === "background" || initialLaunchBiometricRequest.current)
|
||||||
) {
|
) {
|
||||||
biometricUnlockAwaitingUserInput.current = true;
|
if (SettingsService.shouldLockAppOnEnterForeground()) {
|
||||||
onUnlockAppRequested();
|
DatabaseLogger.info("Locking app on entering foreground");
|
||||||
|
useUserStore.getState().lockApp(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
biometricUnlockAwaitingUserInput.current ||
|
||||||
|
useUserStore.getState().disableAppLockRequests ||
|
||||||
|
!appLocked ||
|
||||||
|
useSettingStore.getState().requestBiometrics ||
|
||||||
|
useSettingStore.getState().appDidEnterBackgroundForAction
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
initialLaunchBiometricRequest.current = false;
|
||||||
|
DatabaseLogger.info("Biometric unlock request");
|
||||||
|
onUnlockAppRequested();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SettingsService.appEnteredBackground();
|
||||||
}
|
}
|
||||||
}, [appState, onUnlockAppRequested, appLocked]);
|
}, [appState, onUnlockAppRequested, appLocked]);
|
||||||
|
|
||||||
return appLocked ? (
|
return appLocked ? (
|
||||||
<View
|
<KeyboardAwareScrollView
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.primary.background,
|
backgroundColor: colors.primary.background,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
zIndex: 999,
|
zIndex: 999
|
||||||
justifyContent: "center"
|
|
||||||
}}
|
}}
|
||||||
|
contentContainerStyle={{
|
||||||
|
justifyContent: "center",
|
||||||
|
minHeight: height
|
||||||
|
}}
|
||||||
|
keyboardDismissMode="interactive"
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
>
|
>
|
||||||
|
<Toast context="local" />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -292,7 +327,7 @@ const AppLockedOverlay = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</KeyboardAwareScrollView>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
|
|||||||
import {
|
import {
|
||||||
eCloseAttachmentDialog,
|
eCloseAttachmentDialog,
|
||||||
eCloseSheet,
|
eCloseSheet,
|
||||||
eDBItemUpdate
|
eDBItemUpdate,
|
||||||
|
eOnLoadNote
|
||||||
} from "../../utils/events";
|
} from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
@@ -53,6 +54,9 @@ import { Notice } from "../ui/notice";
|
|||||||
import { Pressable } from "../ui/pressable";
|
import { Pressable } from "../ui/pressable";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||||
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
|
import SheetProvider from "../sheet-provider";
|
||||||
|
|
||||||
const Actions = ({
|
const Actions = ({
|
||||||
attachment,
|
attachment,
|
||||||
@@ -62,7 +66,7 @@ const Actions = ({
|
|||||||
}: {
|
}: {
|
||||||
attachment: Attachment;
|
attachment: Attachment;
|
||||||
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
|
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
|
||||||
close: () => void;
|
close?: () => void;
|
||||||
fwdRef: RefObject<ActionSheetRef>;
|
fwdRef: RefObject<ActionSheetRef>;
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -103,7 +107,7 @@ const Actions = ({
|
|||||||
reupload: true,
|
reupload: true,
|
||||||
hash: attachment.hash,
|
hash: attachment.hash,
|
||||||
context: contextId,
|
context: contextId,
|
||||||
type: attachment.type
|
type: attachment.mimeType.startsWith("image") ? "image" : "file"
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: "upload"
|
icon: "upload"
|
||||||
@@ -169,10 +173,27 @@ const Actions = ({
|
|||||||
{
|
{
|
||||||
name: "Delete",
|
name: "Delete",
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
|
const relations = await db.relations.to(attachment, "note").get();
|
||||||
await db.attachments.remove(attachment.hash, false);
|
await db.attachments.remove(attachment.hash, false);
|
||||||
setAttachments();
|
setAttachments();
|
||||||
eSendEvent(eDBItemUpdate, attachment.id);
|
eSendEvent(eDBItemUpdate, attachment.id);
|
||||||
close();
|
relations
|
||||||
|
.map((relation) => relation.fromId)
|
||||||
|
.forEach(async (id) => {
|
||||||
|
const tab = useTabStore.getState().getTabForNote(id);
|
||||||
|
if (tab !== undefined) {
|
||||||
|
const isFocused = useTabStore.getState().currentTab === tab;
|
||||||
|
if (isFocused) {
|
||||||
|
eSendEvent(eOnLoadNote, {
|
||||||
|
item: await db.notes.note(id),
|
||||||
|
forced: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
editorController.current.commands.setLoading(true, tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
close?.();
|
||||||
},
|
},
|
||||||
icon: "delete-outline"
|
icon: "delete-outline"
|
||||||
}
|
}
|
||||||
@@ -198,10 +219,11 @@ const Actions = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dialog context={contextId} />
|
<Dialog context={contextId} />
|
||||||
|
<SheetProvider context={contextId} />
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.secondary.background,
|
borderBottomColor: colors.primary.border,
|
||||||
marginBottom: notes && notes.length > 0 ? 0 : 12
|
marginBottom: notes && notes.length > 0 ? 0 : 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -218,36 +240,20 @@ const Actions = ({
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
paddingHorizontal: 12
|
paddingHorizontal: 12,
|
||||||
|
marginTop: 6,
|
||||||
|
gap: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
size={SIZE.xs}
|
{attachment.mimeType}
|
||||||
style={{
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{attachment.type}
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
style={{
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
size={SIZE.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{formatBytes(attachment.size)}
|
{formatBytes(attachment.size)}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
{notes.length ? (
|
{notes.length ? (
|
||||||
<Paragraph
|
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
|
||||||
style={{
|
|
||||||
marginRight: 10
|
|
||||||
}}
|
|
||||||
size={SIZE.xs}
|
|
||||||
color={colors.secondary.paragraph}
|
|
||||||
>
|
|
||||||
{notes.length} note
|
{notes.length} note
|
||||||
{notes.length > 1 ? "s" : ""}
|
{notes.length > 1 ? "s" : ""}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
@@ -275,7 +281,7 @@ const Actions = ({
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.secondary.background,
|
borderBottomColor: colors.primary.border,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
paddingVertical: 12
|
paddingVertical: 12
|
||||||
}}
|
}}
|
||||||
@@ -346,7 +352,7 @@ const Actions = ({
|
|||||||
{failed ? (
|
{failed ? (
|
||||||
<Notice
|
<Notice
|
||||||
type="alert"
|
type="alert"
|
||||||
text={`File check failed with error: ${attachment.failed} Try reuploading the file to fix the issue.`}
|
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -84,8 +84,9 @@ const DownloadAttachments = ({
|
|||||||
canceled.current = true;
|
canceled.current = true;
|
||||||
if (!groupId.current) return;
|
if (!groupId.current) return;
|
||||||
console.log(groupId.current, "canceling groupId downloads");
|
console.log(groupId.current, "canceling groupId downloads");
|
||||||
await db.fs().cancel(groupId.current, "download");
|
await db.fs().cancel(groupId.current);
|
||||||
setDownloading(false);
|
setDownloading(false);
|
||||||
|
setResult(new Map());
|
||||||
groupId.current = undefined;
|
groupId.current = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -201,7 +202,7 @@ const DownloadAttachments = ({
|
|||||||
</Paragraph>
|
</Paragraph>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
keyExtractor={(index) => "attachment" + index}
|
keyExtractor={(index) => "attachment_download" + index}
|
||||||
renderItem={({ index }) => {
|
renderItem={({ index }) => {
|
||||||
return (
|
return (
|
||||||
<AttachmentItem
|
<AttachmentItem
|
||||||
|
|||||||
@@ -61,10 +61,15 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
|||||||
|
|
||||||
const refresh = React.useCallback(() => {
|
const refresh = React.useCallback(() => {
|
||||||
if (note) {
|
if (note) {
|
||||||
db.attachments.ofNote(note.id, "all").sorted({
|
db.attachments
|
||||||
...DEFAULT_SORTING,
|
.ofNote(note.id, "all")
|
||||||
sortBy: "dateModified"
|
.sorted({
|
||||||
});
|
...DEFAULT_SORTING,
|
||||||
|
sortBy: "dateModified"
|
||||||
|
})
|
||||||
|
.then((attachments) => {
|
||||||
|
setAttachments(attachments);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
db.attachments.all
|
db.attachments.all
|
||||||
.sorted({
|
.sorted({
|
||||||
@@ -155,7 +160,9 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "all":
|
case "all":
|
||||||
items = db.attachments.all;
|
items = note
|
||||||
|
? db.attachments.ofNote(note.id, "all")
|
||||||
|
: db.attachments.all;
|
||||||
break;
|
break;
|
||||||
case "images":
|
case "images":
|
||||||
items = note
|
items = note
|
||||||
@@ -164,15 +171,17 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
|||||||
break;
|
break;
|
||||||
case "video":
|
case "video":
|
||||||
items = items = note
|
items = items = note
|
||||||
? db.attachments.ofNote(note.id, "all")
|
? db.attachments.ofNote(note.id, "videos")
|
||||||
: db.attachments.videos;
|
: db.attachments.videos;
|
||||||
break;
|
break;
|
||||||
case "audio":
|
case "audio":
|
||||||
items = db.attachments.all;
|
items = note
|
||||||
|
? db.attachments.ofNote(note.id, "audio")
|
||||||
|
: db.attachments.audios;
|
||||||
break;
|
break;
|
||||||
case "documents":
|
case "documents":
|
||||||
items = note
|
items = note
|
||||||
? db.attachments.ofNote(note.id, "all")
|
? db.attachments.ofNote(note.id, "documents")
|
||||||
: db.attachments.documents;
|
: db.attachments.documents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +355,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
|
|||||||
|
|
||||||
AttachmentDialog.present = (note?: Note) => {
|
AttachmentDialog.present = (note?: Note) => {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: () => <AttachmentDialog note={note} />
|
component: () => <AttachmentDialog note={note} />,
|
||||||
|
keyboardHandlerDisabled: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,12 +26,15 @@ import {
|
|||||||
ToastManager
|
ToastManager
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
import { eCloseSheet } from "../../utils/events";
|
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
|
||||||
import DialogHeader from "../dialog/dialog-header";
|
import DialogHeader from "../dialog/dialog-header";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Input from "../ui/input";
|
import Input from "../ui/input";
|
||||||
import { Notice } from "../ui/notice";
|
import { Notice } from "../ui/notice";
|
||||||
import Seperator from "../ui/seperator";
|
import Seperator from "../ui/seperator";
|
||||||
|
import { Dialog } from "../dialog";
|
||||||
|
import BackupService from "../../services/backup";
|
||||||
|
import { sleep } from "../../utils/time";
|
||||||
|
|
||||||
export const ChangePassword = () => {
|
export const ChangePassword = () => {
|
||||||
const passwordInputRef = useRef();
|
const passwordInputRef = useRef();
|
||||||
@@ -64,6 +67,9 @@ export const ChangePassword = () => {
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
|
const result = await BackupService.run(false, "change-password-dialog");
|
||||||
|
if (!result) throw new Error("Failed to create backup");
|
||||||
|
|
||||||
await db.user.clearSessions();
|
await db.user.clearSessions();
|
||||||
await db.user.changePassword(oldPassword.current, password.current);
|
await db.user.changePassword(oldPassword.current, password.current);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -73,6 +79,8 @@ export const ChangePassword = () => {
|
|||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
eSendEvent(eCloseSheet);
|
eSendEvent(eCloseSheet);
|
||||||
|
await sleep(300);
|
||||||
|
eSendEvent(eOpenRecoveryKeyDialog);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
@@ -92,6 +100,7 @@ export const ChangePassword = () => {
|
|||||||
padding: 12
|
padding: 12
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Dialog context="change-password-dialog" />
|
||||||
<DialogHeader
|
<DialogHeader
|
||||||
title="Change password"
|
title="Change password"
|
||||||
paragraph="Enter your old and new passwords"
|
paragraph="Enter your old and new passwords"
|
||||||
@@ -129,7 +138,14 @@ export const ChangePassword = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Notice
|
<Notice
|
||||||
text="Changing password is a non-undoable process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection."
|
text={`Changing password is an irreversible process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection.`}
|
||||||
|
type="alert"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={{ height: 10 }} />
|
||||||
|
|
||||||
|
<Notice
|
||||||
|
text={`Once your password is changed, please make sure to save the new account recovery key.`}
|
||||||
type="alert"
|
type="alert"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||||
import { SheetManager } from "react-native-actions-sheet";
|
import { SheetManager } from "react-native-actions-sheet";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import { eSendEvent } from "../../services/event-manager";
|
import { eSendEvent } from "../../services/event-manager";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import Sync from "../../services/sync";
|
||||||
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
|
import { useUserStore } from "../../stores/use-user-store";
|
||||||
|
import { eUserLoggedIn } from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
import { sleep } from "../../utils/time";
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
@@ -34,10 +38,6 @@ import Paragraph from "../ui/typography/paragraph";
|
|||||||
import { hideAuth } from "./common";
|
import { hideAuth } from "./common";
|
||||||
import { ForgotPassword } from "./forgot-password";
|
import { ForgotPassword } from "./forgot-password";
|
||||||
import { useLogin } from "./use-login";
|
import { useLogin } from "./use-login";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
|
||||||
import { eUserLoggedIn } from "../../utils/events";
|
|
||||||
import { useUserStore } from "../../stores/use-user-store";
|
|
||||||
import Sync from "../../services/sync";
|
|
||||||
|
|
||||||
const LoginSteps = {
|
const LoginSteps = {
|
||||||
emailAuth: 1,
|
emailAuth: 1,
|
||||||
@@ -66,7 +66,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
Progress.present();
|
Progress.present();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!useUserStore.getState().syncing) {
|
if (!useUserStore.getState().syncing) {
|
||||||
Sync.run("global", false, true);
|
Sync.run("global", false, "full");
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
@@ -183,7 +183,11 @@ export const Login = ({ changeMode }) => {
|
|||||||
defaultValue={email.current}
|
defaultValue={email.current}
|
||||||
editable={step === LoginSteps.emailAuth && !loading}
|
editable={step === LoginSteps.emailAuth && !loading}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
passwordInputRef.current?.focus();
|
if (step === LoginSteps.emailAuth) {
|
||||||
|
login();
|
||||||
|
} else {
|
||||||
|
passwordInputRef.current?.focus();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -242,6 +246,7 @@ export const Login = ({ changeMode }) => {
|
|||||||
width: 250,
|
width: 250,
|
||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}}
|
}}
|
||||||
|
height={50}
|
||||||
fontSize={SIZE.md}
|
fontSize={SIZE.md}
|
||||||
type="accent"
|
type="accent"
|
||||||
title={!loading ? "Continue" : null}
|
title={!loading ? "Continue" : null}
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ export const SessionExpired = () => {
|
|||||||
eSendEvent(eUserLoggedIn, true);
|
eSendEvent(eUserLoggedIn, true);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: false
|
||||||
|
});
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@@ -84,6 +87,9 @@ export const SessionExpired = () => {
|
|||||||
MMKV.clearStore();
|
MMKV.clearStore();
|
||||||
clearAllStores();
|
clearAllStores();
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: false
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: e.message,
|
heading: e.message,
|
||||||
@@ -106,7 +112,11 @@ export const SessionExpired = () => {
|
|||||||
if (!res) throw new Error("no token found");
|
if (!res) throw new Error("no token found");
|
||||||
if (db.tokenManager._isTokenExpired(res))
|
if (db.tokenManager._isTokenExpired(res))
|
||||||
throw new Error("token expired");
|
throw new Error("token expired");
|
||||||
Sync.run("global", false, true, async (complete) => {
|
|
||||||
|
const key = await db.user.getEncryptionKey();
|
||||||
|
if (!key) throw new Error("No encryption key found.");
|
||||||
|
|
||||||
|
Sync.run("global", false, "full", async (complete) => {
|
||||||
if (!complete) {
|
if (!complete) {
|
||||||
let user = await db.user.getUser();
|
let user = await db.user.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@@ -121,12 +131,14 @@ export const SessionExpired = () => {
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
let user = await db.user.getUser();
|
let user = await db.user.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
email.current = user.email;
|
email.current = user.email;
|
||||||
setFocused(false);
|
setFocused(false);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [email]);
|
}, [email]);
|
||||||
|
|
||||||
@@ -139,9 +151,15 @@ export const SessionExpired = () => {
|
|||||||
animated={false}
|
animated={false}
|
||||||
centered={false}
|
centered={false}
|
||||||
onShow={async () => {
|
onShow={async () => {
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: true
|
||||||
|
});
|
||||||
await sleep(300);
|
await sleep(300);
|
||||||
passwordInputRef.current?.focus();
|
passwordInputRef.current?.focus();
|
||||||
setFocused(true);
|
setFocused(true);
|
||||||
|
useUserStore.setState({
|
||||||
|
disableAppLockRequests: true
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
enableSheetKeyboardHandler={true}
|
enableSheetKeyboardHandler={true}
|
||||||
visible={true}
|
visible={true}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import Seperator from "../ui/seperator";
|
|||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { ScrollView } from "react-native-actions-sheet";
|
||||||
|
|
||||||
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -143,7 +144,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
|
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<ScrollView
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
keyboardDismissMode="interactive"
|
||||||
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -214,6 +218,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
code.current = value;
|
code.current = value;
|
||||||
//onNext();
|
//onNext();
|
||||||
}}
|
}}
|
||||||
|
onSubmitEditing={onNext}
|
||||||
caretHidden
|
caretHidden
|
||||||
inputStyle={{
|
inputStyle={{
|
||||||
fontSize: SIZE.lg,
|
fontSize: SIZE.lg,
|
||||||
@@ -225,6 +230,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
keyboardType={
|
keyboardType={
|
||||||
currentMethod.method === "recoveryCode" ? "default" : "numeric"
|
currentMethod.method === "recoveryCode" ? "default" : "numeric"
|
||||||
}
|
}
|
||||||
|
enablesReturnKeyAutomatically
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
height: 60,
|
height: 60,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
@@ -297,7 +303,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
import { useRoute } from "@react-navigation/native";
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
import { Keyboard, View } from "react-native";
|
import { Keyboard, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
@@ -54,6 +55,7 @@ const FloatingButton = ({
|
|||||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||||
const translate = useSharedValue(0);
|
const translate = useSharedValue(0);
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
@@ -140,7 +142,13 @@ const FloatingButton = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name={title === "Clear all trash" ? "delete" : "plus"}
|
name={
|
||||||
|
route.name === "Notebooks"
|
||||||
|
? "notebook-plus"
|
||||||
|
: route.name === "Trash"
|
||||||
|
? "delete"
|
||||||
|
: "plus"
|
||||||
|
}
|
||||||
color={colors.primary.accentForeground}
|
color={colors.primary.accentForeground}
|
||||||
size={SIZE.xxl}
|
size={SIZE.xxl}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,14 +21,16 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
|
|||||||
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
|
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import useKeyboard from "../../hooks/use-keyboard";
|
import useKeyboard from "../../hooks/use-keyboard";
|
||||||
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
|
|
||||||
export const Container = ({ children }: PropsWithChildren) => {
|
export const Container = ({ children }: PropsWithChildren) => {
|
||||||
|
const { colors } = useThemeColors();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const keyboard = useKeyboard();
|
const keyboard = useKeyboard();
|
||||||
const [height, setHeight] = useState(0);
|
const [height, setHeight] = useState(0);
|
||||||
const windowHeightRef = useRef(Dimensions.get("window").height);
|
const windowHeightRef = useRef(Dimensions.get("window").height);
|
||||||
const { height: windowHeight } = useWindowDimensions();
|
const { height: windowHeight } = useWindowDimensions();
|
||||||
|
const timerRef = useRef<NodeJS.Timeout>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (windowHeight !== windowHeightRef.current) {
|
if (windowHeight !== windowHeightRef.current) {
|
||||||
setHeight(0);
|
setHeight(0);
|
||||||
@@ -43,12 +45,17 @@ export const Container = ({ children }: PropsWithChildren) => {
|
|||||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||||
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
|
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
|
||||||
height: height || "100%",
|
height: height || "100%",
|
||||||
width: "100%"
|
width: "100%",
|
||||||
|
backgroundColor: colors.primary.background
|
||||||
}}
|
}}
|
||||||
onLayout={(event) => {
|
onLayout={(event) => {
|
||||||
if (!keyboard.keyboardShown) {
|
const height = event.nativeEvent.layout.height;
|
||||||
setHeight(event.nativeEvent.layout.height);
|
clearTimeout(timerRef.current);
|
||||||
}
|
timerRef.current = setTimeout(() => {
|
||||||
|
if (!keyboard.keyboardShown) {
|
||||||
|
setHeight(height);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ const DialogButtons = ({
|
|||||||
{
|
{
|
||||||
backgroundColor: colors.secondary.background,
|
backgroundColor: colors.secondary.background,
|
||||||
height: 60,
|
height: 60,
|
||||||
borderBottomRightRadius: 10,
|
borderBottomRightRadius: 5,
|
||||||
borderBottomLeftRadius: 10,
|
borderBottomLeftRadius: 5,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
borderTopWidth: 0.7,
|
borderTopWidth: 0.7,
|
||||||
borderTopColor: getColorLinearShade(
|
borderTopColor: getColorLinearShade(
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const DialogHeader = ({
|
|||||||
title={button.title}
|
title={button.title}
|
||||||
icon={button.icon}
|
icon={button.icon}
|
||||||
type={button.type || "secondary"}
|
type={button.type || "secondary"}
|
||||||
height={25}
|
height={30}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type DialogInfo = {
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
context: "global" | "local" | (string & {});
|
context: "global" | "local" | (string & {});
|
||||||
secureTextEntry?: boolean;
|
secureTextEntry?: boolean;
|
||||||
|
keyboardType?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function presentDialog(data: Partial<DialogInfo>): void {
|
export function presentDialog(data: Partial<DialogInfo>): void {
|
||||||
|
|||||||
@@ -36,11 +36,15 @@ import DialogButtons from "./dialog-buttons";
|
|||||||
import DialogHeader from "./dialog-header";
|
import DialogHeader from "./dialog-header";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
|
import { getContainerBorder } from "../../utils/colors";
|
||||||
|
|
||||||
export const Dialog = ({ context = "global" }) => {
|
export const Dialog = ({ context = "global" }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState(null);
|
const [checked, setChecked] = useState(false);
|
||||||
|
const values = useRef({
|
||||||
|
inputValue: undefined
|
||||||
|
});
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const [dialogInfo, setDialogInfo] = useState({
|
const [dialogInfo, setDialogInfo] = useState({
|
||||||
title: "",
|
title: "",
|
||||||
@@ -66,7 +70,8 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
if (dialogInfo.positivePress) {
|
if (dialogInfo.positivePress) {
|
||||||
inputRef.current?.blur();
|
inputRef.current?.blur();
|
||||||
let result = await dialogInfo.positivePress(
|
let result = await dialogInfo.positivePress(
|
||||||
inputValue || dialogInfo.defaultValue
|
values.current.inputValue || dialogInfo.defaultValue,
|
||||||
|
checked
|
||||||
);
|
);
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
return;
|
return;
|
||||||
@@ -81,8 +86,9 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
if (!data.context) data.context = "global";
|
if (!data.context) data.context = "global";
|
||||||
if (data.context !== context) return;
|
if (data.context !== context) return;
|
||||||
setDialogInfo(data);
|
setDialogInfo(data);
|
||||||
|
setChecked(false);
|
||||||
|
values.current.inputValue = data.defaultValue;
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setInputValue(data.defaultValue);
|
|
||||||
},
|
},
|
||||||
[context]
|
[context]
|
||||||
);
|
);
|
||||||
@@ -98,7 +104,8 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
}, [show]);
|
}, [show]);
|
||||||
|
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
setInputValue(null);
|
setChecked(false);
|
||||||
|
values.current.inputValue = undefined;
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,7 +113,6 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
if (dialogInfo.onClose) {
|
if (dialogInfo.onClose) {
|
||||||
await dialogInfo.onClose();
|
await dialogInfo.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
hide();
|
hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,7 +122,8 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
maxHeight: 450,
|
maxHeight: 450,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
backgroundColor: colors.primary.background,
|
backgroundColor: colors.primary.background,
|
||||||
paddingTop: 12
|
paddingTop: 12,
|
||||||
|
...getContainerBorder(colors.primary.border, 0.5)
|
||||||
};
|
};
|
||||||
|
|
||||||
return visible ? (
|
return visible ? (
|
||||||
@@ -159,7 +166,7 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
fwdRef={inputRef}
|
fwdRef={inputRef}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
onChangeText={(value) => {
|
onChangeText={(value) => {
|
||||||
setInputValue(value);
|
values.current.inputValue = value;
|
||||||
}}
|
}}
|
||||||
testID="input-value"
|
testID="input-value"
|
||||||
secureTextEntry={dialogInfo.secureTextEntry}
|
secureTextEntry={dialogInfo.secureTextEntry}
|
||||||
@@ -167,6 +174,7 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
onSubmit={onPressPositive}
|
onSubmit={onPressPositive}
|
||||||
returnKeyLabel="Done"
|
returnKeyLabel="Done"
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
|
keyboardType={dialogInfo.keyboardType || "default"}
|
||||||
placeholder={dialogInfo.inputPlaceholder}
|
placeholder={dialogInfo.inputPlaceholder}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -176,10 +184,10 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setInputValue(!inputValue);
|
setChecked(!checked);
|
||||||
}}
|
}}
|
||||||
icon={
|
icon={
|
||||||
inputValue
|
checked
|
||||||
? "check-circle-outline"
|
? "check-circle-outline"
|
||||||
: "checkbox-blank-circle-outline"
|
: "checkbox-blank-circle-outline"
|
||||||
}
|
}
|
||||||
@@ -187,9 +195,10 @@ export const Dialog = ({ context = "global" }) => {
|
|||||||
justifyContent: "flex-start"
|
justifyContent: "flex-start"
|
||||||
}}
|
}}
|
||||||
height={35}
|
height={35}
|
||||||
|
iconSize={20}
|
||||||
width="100%"
|
width="100%"
|
||||||
title={dialogInfo.check.info}
|
title={dialogInfo.check.info}
|
||||||
type={inputValue ? dialogInfo.check.type : "plain"}
|
type={checked ? dialogInfo.check.type || "selected" : "plain"}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { TextInput, View } from "react-native";
|
import { TextInput, View } from "react-native";
|
||||||
|
import { db } from "../../../common/database";
|
||||||
import {
|
import {
|
||||||
clearAppLockVerificationCipher,
|
clearAppLockVerificationCipher,
|
||||||
setAppLockVerificationCipher,
|
setAppLockVerificationCipher,
|
||||||
validateAppLockPassword
|
validateAppLockPassword
|
||||||
} from "../../../common/database/encryption";
|
} from "../../../common/database/encryption";
|
||||||
import BiometicService from "../../../services/biometrics";
|
import BiometricService from "../../../services/biometrics";
|
||||||
import { DDS } from "../../../services/device-detection";
|
import { DDS } from "../../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
ToastManager,
|
ToastManager,
|
||||||
@@ -44,6 +45,7 @@ import BaseDialog from "../../dialog/base-dialog";
|
|||||||
import DialogButtons from "../../dialog/dialog-buttons";
|
import DialogButtons from "../../dialog/dialog-buttons";
|
||||||
import DialogHeader from "../../dialog/dialog-header";
|
import DialogHeader from "../../dialog/dialog-header";
|
||||||
import { Toast } from "../../toast";
|
import { Toast } from "../../toast";
|
||||||
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import Input from "../../ui/input";
|
import Input from "../../ui/input";
|
||||||
import Seperator from "../../ui/seperator";
|
import Seperator from "../../ui/seperator";
|
||||||
@@ -66,6 +68,7 @@ export const AppLockPassword = () => {
|
|||||||
confirmPassword?: string;
|
confirmPassword?: string;
|
||||||
}>({});
|
}>({});
|
||||||
const [secureTextEntry, setSecureTextEntry] = useState(true);
|
const [secureTextEntry, setSecureTextEntry] = useState(true);
|
||||||
|
const [accountPass, setAccountPass] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subs = [
|
const subs = [
|
||||||
@@ -73,6 +76,7 @@ export const AppLockPassword = () => {
|
|||||||
eOpenAppLockPasswordDialog,
|
eOpenAppLockPasswordDialog,
|
||||||
(mode: "create" | "change" | "remove") => {
|
(mode: "create" | "change" | "remove") => {
|
||||||
setMode(mode);
|
setMode(mode);
|
||||||
|
setAccountPass(false);
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -126,7 +130,9 @@ export const AppLockPassword = () => {
|
|||||||
mode === "change"
|
mode === "change"
|
||||||
? `Change app lock ${keyboardType}`
|
? `Change app lock ${keyboardType}`
|
||||||
: mode === "remove"
|
: mode === "remove"
|
||||||
? `Remove app lock ${keyboardType}`
|
? `Enter ${
|
||||||
|
accountPass ? "account password" : `app lock ${keyboardType}`
|
||||||
|
} to remove ${keyboardType}`
|
||||||
: `Set up a custom app lock ${keyboardType} to unlock the app`
|
: `Set up a custom app lock ${keyboardType} to unlock the app`
|
||||||
}
|
}
|
||||||
icon="shield"
|
icon="shield"
|
||||||
@@ -169,33 +175,39 @@ export const AppLockPassword = () => {
|
|||||||
confirmPasswordInputRef.current?.focus();
|
confirmPasswordInputRef.current?.focus();
|
||||||
}}
|
}}
|
||||||
defaultValue={values.current.password}
|
defaultValue={values.current.password}
|
||||||
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
|
keyboardType={
|
||||||
|
keyboardType === "pin" && !accountPass ? "number-pad" : "default"
|
||||||
|
}
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
|
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
|
||||||
returnKeyType={mode !== "remove" ? "next" : "done"}
|
returnKeyType={mode !== "remove" ? "next" : "done"}
|
||||||
secureTextEntry={secureTextEntry}
|
secureTextEntry={secureTextEntry}
|
||||||
buttonLeft={
|
buttonLeft={
|
||||||
<IconButton
|
accountPass ? null : (
|
||||||
name={keyboardType === "password" ? "numeric" : "keyboard"}
|
<IconButton
|
||||||
onPress={() => {
|
name={keyboardType === "password" ? "numeric" : "keyboard"}
|
||||||
setKeyboardType(
|
onPress={() => {
|
||||||
keyboardType === "password" ? "pin" : "password"
|
setKeyboardType(
|
||||||
);
|
keyboardType === "password" ? "pin" : "password"
|
||||||
setSecureTextEntry(false);
|
);
|
||||||
setImmediate(() => {
|
setSecureTextEntry(false);
|
||||||
setSecureTextEntry(true);
|
setImmediate(() => {
|
||||||
});
|
setSecureTextEntry(true);
|
||||||
}}
|
});
|
||||||
style={{
|
}}
|
||||||
width: 25,
|
style={{
|
||||||
height: 25,
|
width: 25,
|
||||||
marginRight: 5
|
height: 25,
|
||||||
}}
|
marginRight: 5
|
||||||
size={SIZE.lg}
|
}}
|
||||||
/>
|
size={SIZE.lg}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
placeholder={
|
placeholder={
|
||||||
mode === "change"
|
accountPass
|
||||||
|
? "Account password"
|
||||||
|
: mode === "change"
|
||||||
? `New ${keyboardType}`
|
? `New ${keyboardType}`
|
||||||
: `${keyboardType === "pin" ? "Pin" : "Password"}`
|
: `${keyboardType === "pin" ? "Pin" : "Password"}`
|
||||||
}
|
}
|
||||||
@@ -222,6 +234,36 @@ export const AppLockPassword = () => {
|
|||||||
placeholder={`Confirm ${keyboardType}`}
|
placeholder={`Confirm ${keyboardType}`}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{mode === "remove" ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
icon={
|
||||||
|
accountPass ? "checkbox-marked" : "checkbox-blank-outline"
|
||||||
|
}
|
||||||
|
onPress={() => {
|
||||||
|
setSecureTextEntry(false);
|
||||||
|
setAccountPass(!accountPass);
|
||||||
|
setTimeout(() => {
|
||||||
|
setSecureTextEntry(true);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
iconSize={SIZE.lg}
|
||||||
|
type="plain"
|
||||||
|
iconColor={
|
||||||
|
accountPass ? colors.primary.accent : colors.primary.icon
|
||||||
|
}
|
||||||
|
title="Use account password instead"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
paddingHorizontal: 0,
|
||||||
|
height: 30
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
@@ -249,7 +291,8 @@ export const AppLockPassword = () => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await setAppLockVerificationCipher(values.current.password);
|
const password = values.current.password;
|
||||||
|
setAppLockVerificationCipher(password);
|
||||||
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
||||||
} else if (mode === "change") {
|
} else if (mode === "change") {
|
||||||
if (
|
if (
|
||||||
@@ -277,7 +320,6 @@ export const AppLockPassword = () => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCurrentPasswordCorrect = await validateAppLockPassword(
|
const isCurrentPasswordCorrect = await validateAppLockPassword(
|
||||||
values.current.currentPassword
|
values.current.currentPassword
|
||||||
);
|
);
|
||||||
@@ -293,8 +335,10 @@ export const AppLockPassword = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const password = values.current.password;
|
||||||
|
await clearAppLockVerificationCipher();
|
||||||
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
SettingsService.setProperty("appLockHasPasswordSecurity", true);
|
||||||
await setAppLockVerificationCipher(values.current.password);
|
await setAppLockVerificationCipher(password);
|
||||||
} else if (mode === "remove") {
|
} else if (mode === "remove") {
|
||||||
if (!values.current.password) {
|
if (!values.current.password) {
|
||||||
ToastManager.error(
|
ToastManager.error(
|
||||||
@@ -305,15 +349,20 @@ export const AppLockPassword = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCurrentPasswordCorrect = await validateAppLockPassword(
|
const isCurrentPasswordCorrect = accountPass
|
||||||
values.current.password
|
? await db.user.verifyPassword(values.current.password)
|
||||||
);
|
: await validateAppLockPassword(values.current.password);
|
||||||
|
|
||||||
if (!isCurrentPasswordCorrect) {
|
if (!isCurrentPasswordCorrect) {
|
||||||
ToastManager.error(
|
ToastManager.error(
|
||||||
new Error(
|
new Error(
|
||||||
`${keyboardType === "pin" ? "Pin" : "Password"} incorrect`
|
accountPass
|
||||||
|
? "Account password incorrect"
|
||||||
|
: `${
|
||||||
|
keyboardType === "pin" ? "Pin" : "Password"
|
||||||
|
} incorrect`
|
||||||
),
|
),
|
||||||
|
undefined,
|
||||||
"local"
|
"local"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -322,10 +371,11 @@ export const AppLockPassword = () => {
|
|||||||
SettingsService.setProperty("appLockHasPasswordSecurity", false);
|
SettingsService.setProperty("appLockHasPasswordSecurity", false);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await BiometicService.isBiometryAvailable()) ||
|
!(await BiometricService.isBiometryAvailable()) ||
|
||||||
SettingsService.getProperty("biometricsAuthEnabled") === false
|
SettingsService.getProperty("biometricsAuthEnabled") === false
|
||||||
) {
|
) {
|
||||||
SettingsService.setProperty("appLockEnabled", false);
|
SettingsService.setProperty("appLockEnabled", false);
|
||||||
|
SettingsService.setPrivacyScreen(SettingsService.get());
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
message: "App lock disabled",
|
message: "App lock disabled",
|
||||||
type: "success"
|
type: "success"
|
||||||
@@ -340,7 +390,9 @@ export const AppLockPassword = () => {
|
|||||||
|
|
||||||
close();
|
close();
|
||||||
}}
|
}}
|
||||||
positiveTitle="Save"
|
positiveTitle={
|
||||||
|
mode === "remove" ? "Remove" : mode === "change" ? "Change" : "Save"
|
||||||
|
}
|
||||||
negativeTitle="Cancel"
|
negativeTitle="Cancel"
|
||||||
positiveType="transparent"
|
positiveType="transparent"
|
||||||
loading={false}
|
loading={false}
|
||||||
|
|||||||
@@ -17,8 +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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
|
import {
|
||||||
import { ImagePickerResponse } from "react-native-image-picker";
|
Image,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
View
|
||||||
|
} from "react-native";
|
||||||
|
import { Image as ImageType } from "react-native-image-crop-picker";
|
||||||
import { useThemeColors } from "../../../../../../packages/theme/dist";
|
import { useThemeColors } from "../../../../../../packages/theme/dist";
|
||||||
import { presentSheet } from "../../../services/event-manager";
|
import { presentSheet } from "../../../services/event-manager";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
@@ -32,7 +38,7 @@ export default function AttachImage({
|
|||||||
onAttach,
|
onAttach,
|
||||||
close
|
close
|
||||||
}: {
|
}: {
|
||||||
response: ImagePickerResponse;
|
response: ImageType[];
|
||||||
onAttach: ({ compress }: { compress: boolean }) => void;
|
onAttach: ({ compress }: { compress: boolean }) => void;
|
||||||
close: ((ctx?: string | undefined) => void) | undefined;
|
close: ((ctx?: string | undefined) => void) | undefined;
|
||||||
}) {
|
}) {
|
||||||
@@ -58,14 +64,17 @@ export default function AttachImage({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
|
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
|
||||||
Attaching {response.assets?.length} image(s):
|
Attaching {response?.length} image(s):
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<ScrollView horizontal>
|
<ScrollView horizontal>
|
||||||
{response.assets?.map((item) => (
|
{response?.map?.((item) => (
|
||||||
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
|
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
|
||||||
<Image
|
<Image
|
||||||
source={{
|
source={{
|
||||||
uri: item.uri
|
uri:
|
||||||
|
Platform.OS === "ios"
|
||||||
|
? item.sourceURL || item.path
|
||||||
|
: item.path
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -142,7 +151,7 @@ export default function AttachImage({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
title={`${
|
title={`${
|
||||||
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
|
||||||
}`}
|
}`}
|
||||||
type="accent"
|
type="accent"
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -156,10 +165,16 @@ export default function AttachImage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachImage.present = (response: ImagePickerResponse) => {
|
AttachImage.present = (response: ImageType[], context?: string) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise<
|
||||||
|
| {
|
||||||
|
compress: boolean;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>((resolve) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
presentSheet({
|
presentSheet({
|
||||||
|
context: context,
|
||||||
component: (ref, close, update) => (
|
component: (ref, close, update) => (
|
||||||
<AttachImage
|
<AttachImage
|
||||||
response={response}
|
response={response}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const ColorPicker = ({
|
|||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: 250,
|
height: 250,
|
||||||
backgroundColor: selectedColor,
|
backgroundColor: colors.primary.background,
|
||||||
borderRadius: 10
|
borderRadius: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -146,12 +146,16 @@ const ColorPicker = ({
|
|||||||
onChangeText={(value) => {
|
onChangeText={(value) => {
|
||||||
title.current = value;
|
title.current = value;
|
||||||
}}
|
}}
|
||||||
|
testID="color-title-input"
|
||||||
defaultValue={title.current}
|
defaultValue={title.current}
|
||||||
placeholder={title.current || "Color title"}
|
placeholder={title.current || "Color title"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
title="Add color"
|
title="Add color"
|
||||||
|
style={{
|
||||||
|
marginBottom: 10
|
||||||
|
}}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
if (!selectedColor)
|
if (!selectedColor)
|
||||||
return ToastManager.error(
|
return ToastManager.error(
|
||||||
@@ -170,7 +174,6 @@ const ColorPicker = ({
|
|||||||
return ToastManager.error(
|
return ToastManager.error(
|
||||||
new Error(`Color #${selectedColor} already exists`)
|
new Error(`Color #${selectedColor} already exists`)
|
||||||
);
|
);
|
||||||
|
|
||||||
const id = await db.colors.add({
|
const id = await db.colors.add({
|
||||||
title: title.current,
|
title: title.current,
|
||||||
colorCode: selectedColor
|
colorCode: selectedColor
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { InteractionManager, View } from "react-native";
|
|||||||
import Share from "react-native-share";
|
import Share from "react-native-share";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
|
||||||
import BiometricService from "../../../services/biometrics";
|
import BiometricService from "../../../services/biometrics";
|
||||||
import { DDS } from "../../../services/device-detection";
|
import { DDS } from "../../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
@@ -35,11 +34,11 @@ import {
|
|||||||
import Navigation from "../../../services/navigation";
|
import Navigation from "../../../services/navigation";
|
||||||
import { getElevationStyle } from "../../../utils/elevation";
|
import { getElevationStyle } from "../../../utils/elevation";
|
||||||
import {
|
import {
|
||||||
eClearEditor,
|
|
||||||
eCloseActionSheet,
|
eCloseActionSheet,
|
||||||
eCloseVaultDialog,
|
eCloseVaultDialog,
|
||||||
eOnLoadNote,
|
eOnLoadNote,
|
||||||
eOpenVaultDialog
|
eOpenVaultDialog,
|
||||||
|
eUpdateNoteInEditor
|
||||||
} from "../../../utils/events";
|
} from "../../../utils/events";
|
||||||
import { deleteItems } from "../../../utils/functions";
|
import { deleteItems } from "../../../utils/functions";
|
||||||
import { tabBarRef } from "../../../utils/global-refs";
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
@@ -129,7 +128,7 @@ export class VaultDialog extends Component {
|
|||||||
: this.state.copyNote
|
: this.state.copyNote
|
||||||
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: this.state.goToEditor
|
: this.state.goToEditor
|
||||||
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
? "Unlock note to open it in editor."
|
||||||
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
|
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
|
||||||
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
|
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
|
||||||
}
|
}
|
||||||
@@ -338,7 +337,26 @@ export class VaultDialog extends Component {
|
|||||||
let verified = await db.user.verifyPassword(this.password);
|
let verified = await db.user.verifyPassword(this.password);
|
||||||
if (!(await db.user.getUser())) verified = true;
|
if (!(await db.user.getUser())) verified = true;
|
||||||
if (verified) {
|
if (verified) {
|
||||||
|
let noteIds = [];
|
||||||
|
if (this.state.deleteAll) {
|
||||||
|
const vault = await db.vaults.default();
|
||||||
|
const relations = await db.relations.from(vault, "note").get();
|
||||||
|
noteIds = relations.map((item) => item.toId);
|
||||||
|
}
|
||||||
|
|
||||||
await db.vault.delete(this.state.deleteAll);
|
await db.vault.delete(this.state.deleteAll);
|
||||||
|
|
||||||
|
noteIds.forEach((id) => {
|
||||||
|
eSendEvent(
|
||||||
|
eUpdateNoteInEditor,
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
deleted: true
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
eSendEvent("vaultUpdated");
|
eSendEvent("vaultUpdated");
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
@@ -365,7 +383,22 @@ export class VaultDialog extends Component {
|
|||||||
loading: true
|
loading: true
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
|
const vault = await db.vaults.default();
|
||||||
|
const relations = await db.relations.from(vault, "note").get();
|
||||||
|
const noteIds = relations.map((item) => item.toId);
|
||||||
|
|
||||||
await db.vault.clear(this.password);
|
await db.vault.clear(this.password);
|
||||||
|
|
||||||
|
noteIds.forEach((id) => {
|
||||||
|
eSendEvent(
|
||||||
|
eUpdateNoteInEditor,
|
||||||
|
{
|
||||||
|
id: id,
|
||||||
|
deleted: true
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
@@ -394,9 +427,10 @@ export class VaultDialog extends Component {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await db.vault.add(this.state.note.id);
|
await db.vault.add(this.state.note.id);
|
||||||
if (this.state.note.id === editorController.current?.note?.id) {
|
|
||||||
eSendEvent(eClearEditor);
|
console.log("update note event...");
|
||||||
}
|
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
message: "Note locked successfully",
|
message: "Note locked successfully",
|
||||||
@@ -501,9 +535,7 @@ export class VaultDialog extends Component {
|
|||||||
}
|
}
|
||||||
if (this.state.note?.id) {
|
if (this.state.note?.id) {
|
||||||
await db.vault.add(this.state.note.id);
|
await db.vault.add(this.state.note.id);
|
||||||
if (this.state.note.id === editorController.current?.note?.id) {
|
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||||
eSendEvent(eClearEditor);
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
@@ -533,6 +565,7 @@ export class VaultDialog extends Component {
|
|||||||
type: "success",
|
type: "success",
|
||||||
context: "global"
|
context: "global"
|
||||||
});
|
});
|
||||||
|
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||||
this.close();
|
this.close();
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
@@ -553,7 +586,7 @@ export class VaultDialog extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _copyNote(note) {
|
async _copyNote(note) {
|
||||||
Clipboard.setString(await convertNoteToText(note));
|
Clipboard.setString((await convertNoteToText(note, true)) || "");
|
||||||
ToastManager.show({
|
ToastManager.show({
|
||||||
heading: "Note copied",
|
heading: "Note copied",
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -569,7 +602,7 @@ export class VaultDialog extends Component {
|
|||||||
await Share.open({
|
await Share.open({
|
||||||
heading: "Share note",
|
heading: "Share note",
|
||||||
failOnCancel: false,
|
failOnCancel: false,
|
||||||
message: await convertNoteToText(note)
|
message: (await convertNoteToText(note)) || ""
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -23,10 +23,7 @@ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
|
|||||||
import { Dialog } from "../dialog";
|
import { Dialog } from "../dialog";
|
||||||
import { Issue } from "../sheets/github/issue";
|
import { Issue } from "../sheets/github/issue";
|
||||||
|
|
||||||
const error = (
|
const error = (stack: string, component: string) => `
|
||||||
stack: string,
|
|
||||||
component: string
|
|
||||||
) => `Please let us know what happened. What steps we can take to reproduce the issue here.
|
|
||||||
|
|
||||||
_______________________________
|
_______________________________
|
||||||
Stacktrace: In ${component}::${stack}`;
|
Stacktrace: In ${component}::${stack}`;
|
||||||
@@ -39,9 +36,9 @@ class ExceptionHandler extends React.Component<{
|
|||||||
error: Error | null;
|
error: Error | null;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
} = {
|
} = {
|
||||||
hasError: false,
|
hasError: false,
|
||||||
error: null
|
error: null
|
||||||
};
|
};
|
||||||
static getDerivedStateFromError(error: Error) {
|
static getDerivedStateFromError(error: Error) {
|
||||||
return { hasError: true, error: error };
|
return { hasError: true, error: error };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { eScrollEvent } from "../../utils/events";
|
|||||||
import { LeftMenus } from "./left-menus";
|
import { LeftMenus } from "./left-menus";
|
||||||
import { RightMenus } from "./right-menus";
|
import { RightMenus } from "./right-menus";
|
||||||
import { Title } from "./title";
|
import { Title } from "./title";
|
||||||
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
|
||||||
type HeaderRightButton = {
|
type HeaderRightButton = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -66,6 +67,8 @@ export const Header = ({
|
|||||||
hasSearch?: boolean;
|
hasSearch?: boolean;
|
||||||
onSearch?: () => void;
|
onSearch?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const insets = useGlobalSafeAreaInsets();
|
const insets = useGlobalSafeAreaInsets();
|
||||||
const [borderHidden, setBorderHidden] = useState(true);
|
const [borderHidden, setBorderHidden] = useState(true);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const RightMenus = ({
|
|||||||
testID={notesnook.ids.default.addBtn}
|
testID={notesnook.ids.default.addBtn}
|
||||||
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
|
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
|
||||||
iconSize={SIZE.xl}
|
iconSize={SIZE.xl}
|
||||||
type="shade"
|
type="accent"
|
||||||
hitSlop={{
|
hitSlop={{
|
||||||
top: 10,
|
top: 10,
|
||||||
right: 10,
|
right: 10,
|
||||||
@@ -82,7 +82,7 @@ export const RightMenus = ({
|
|||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
width: 32,
|
width: 32,
|
||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 5,
|
borderRadius: 100,
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.primary.accent
|
borderColor: colors.primary.accent
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import Sodium from "@ammarahmed/react-native-sodium";
|
import Sodium from "@ammarahmed/react-native-sodium";
|
||||||
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
import dataurl from "@notesnook/core/dist/utils/dataurl";
|
||||||
|
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import ImageViewer from "react-native-image-zoom-viewer";
|
import ImageViewer from "react-native-image-zoom-viewer";
|
||||||
|
import { db } from "../../common/database";
|
||||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||||
import { cacheDir } from "../../common/filesystem/utils";
|
import { cacheDir } from "../../common/filesystem/utils";
|
||||||
import {
|
import {
|
||||||
@@ -32,13 +34,13 @@ import {
|
|||||||
import BaseDialog from "../dialog/base-dialog";
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
|
||||||
const ImagePreview = () => {
|
const ImagePreview = () => {
|
||||||
const { colors } = useThemeColors("dialog");
|
const { colors } = useThemeColors("dialog");
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [image, setImage] = useState<string>();
|
const [image, setImage] = useState<string>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const imageRef = useRef<ImageAttributes>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent("ImagePreview", open);
|
eSubscribeEvent("ImagePreview", open);
|
||||||
@@ -48,6 +50,7 @@ const ImagePreview = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const open = async (image: ImageAttributes) => {
|
const open = async (image: ImageAttributes) => {
|
||||||
|
imageRef.current = image;
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
@@ -60,6 +63,9 @@ const ImagePreview = () => {
|
|||||||
type: "base64",
|
type: "base64",
|
||||||
uri: ""
|
uri: ""
|
||||||
});
|
});
|
||||||
|
if (imageRef.current) {
|
||||||
|
imageRef.current.hash = hash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!hash) return;
|
if (!hash) return;
|
||||||
//@ts-ignore // FIX ME
|
//@ts-ignore // FIX ME
|
||||||
@@ -80,7 +86,13 @@ const ImagePreview = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
visible && (
|
visible && (
|
||||||
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
|
<BaseDialog
|
||||||
|
background="black"
|
||||||
|
animation="slide"
|
||||||
|
visible={true}
|
||||||
|
onRequestClose={close}
|
||||||
|
transparent
|
||||||
|
>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -101,6 +113,21 @@ const ImagePreview = () => {
|
|||||||
color={colors.primary.accent}
|
color={colors.primary.accent}
|
||||||
borderColor="transparent"
|
borderColor="transparent"
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
onPress={() => {
|
||||||
|
if (imageRef.current?.hash) {
|
||||||
|
db.fs().cancel(imageRef.current?.hash);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: Platform.OS === "android" ? 35 : 0,
|
||||||
|
right: 12
|
||||||
|
}}
|
||||||
|
color={colors.static.white}
|
||||||
|
name="close"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
@@ -117,13 +144,12 @@ const ImagePreview = () => {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
height: 80,
|
height: 50,
|
||||||
marginTop: 0,
|
paddingHorizontal: 24,
|
||||||
paddingHorizontal: 12,
|
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
backgroundColor: "rgba(0,0,0,0.3)",
|
backgroundColor: "rgba(0,0,0,0.3)",
|
||||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
marginTop: Platform.OS === "android" ? 30 : 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -137,7 +163,7 @@ const ImagePreview = () => {
|
|||||||
)}
|
)}
|
||||||
imageUrls={[
|
imageUrls={[
|
||||||
{
|
{
|
||||||
url: image
|
url: image as string
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
export const Footer = () => {
|
export const Footer = ({ height = 150 }) => {
|
||||||
return <View style={{ height: 150 }} />;
|
return <View style={{ height: height }} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const NotebookHeader = ({
|
|||||||
style={{
|
style={{
|
||||||
flexShrink: 1
|
flexShrink: 1
|
||||||
}}
|
}}
|
||||||
size={SIZE.xxl}
|
size={SIZE.lg}
|
||||||
>
|
>
|
||||||
{notebook.title}
|
{notebook.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40
|
height: 40
|
||||||
}}
|
}}
|
||||||
type={isPinnedToMenu ? "secondary" : "secondary"}
|
type="transparent"
|
||||||
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
|
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
|
||||||
size={SIZE.lg}
|
size={SIZE.lg}
|
||||||
/>
|
/>
|
||||||
@@ -123,7 +123,7 @@ export const NotebookHeader = ({
|
|||||||
onPress={onEditNotebook}
|
onPress={onEditNotebook}
|
||||||
tooltipText="Edit this notebook"
|
tooltipText="Edit this notebook"
|
||||||
name="pencil"
|
name="pencil"
|
||||||
type="secondary"
|
type="transparent"
|
||||||
color={colors.primary.icon}
|
color={colors.primary.icon}
|
||||||
style={{
|
style={{
|
||||||
width: 40,
|
width: 40,
|
||||||
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{notebook.description ? (
|
{notebook.description ? (
|
||||||
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
|
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
|
||||||
{notebook.description}
|
{notebook.description}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -148,10 +148,6 @@ export const NotebookHeader = ({
|
|||||||
size={SIZE.xs}
|
size={SIZE.xs}
|
||||||
color={colors.secondary.paragraph}
|
color={colors.secondary.paragraph}
|
||||||
>
|
>
|
||||||
{/* {notebook.topics.length === 1
|
|
||||||
? "1 topic"
|
|
||||||
: `${notebook.topics.length} topics`}
|
|
||||||
,{" "} */}
|
|
||||||
{notebook && totalNotes > 1
|
{notebook && totalNotes > 1
|
||||||
? totalNotes + " notes"
|
? totalNotes + " notes"
|
||||||
: totalNotes === 1
|
: totalNotes === 1
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enab
|
|||||||
import { presentSheet } from "../../../services/event-manager";
|
import { presentSheet } from "../../../services/event-manager";
|
||||||
import SettingsService from "../../../services/settings";
|
import SettingsService from "../../../services/settings";
|
||||||
import { RouteName } from "../../../stores/use-navigation-store";
|
import { RouteName } from "../../../stores/use-navigation-store";
|
||||||
import { ColorValues, getContainerBorder } from "../../../utils/colors";
|
import { getContainerBorder } from "../../../utils/colors";
|
||||||
import { GROUP } from "../../../utils/constants";
|
import { GROUP } from "../../../utils/constants";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import Sort from "../../sheets/sort";
|
import Sort from "../../sheets/sort";
|
||||||
@@ -96,13 +96,9 @@ export const SectionHeader = React.memo<
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading
|
<Heading
|
||||||
color={
|
color={color || colors.primary.accent}
|
||||||
ColorValues[color?.toLowerCase() as keyof typeof ColorValues] ||
|
|
||||||
colors.primary.accent
|
|
||||||
}
|
|
||||||
size={SIZE.sm}
|
size={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
minWidth: 60,
|
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
textAlignVertical: "center"
|
textAlignVertical: "center"
|
||||||
}}
|
}}
|
||||||
@@ -117,7 +113,7 @@ export const SectionHeader = React.memo<
|
|||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{index === 0 && (
|
{index === 0 ? (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -126,7 +122,6 @@ export const SectionHeader = React.memo<
|
|||||||
component: <Sort screen={screen} type={dataType} />
|
component: <Sort screen={screen} type={dataType} />
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
hidden={screen === "Reminders"}
|
|
||||||
tooltipText="Change sorting of items in list"
|
tooltipText="Change sorting of items in list"
|
||||||
title={groupBy}
|
title={groupBy}
|
||||||
icon={
|
icon={
|
||||||
@@ -180,7 +175,7 @@ export const SectionHeader = React.memo<
|
|||||||
size={SIZE.lg - 2}
|
size={SIZE.lg - 2}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ import { notesnook } from "../../../../e2e/test.ids";
|
|||||||
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
|
||||||
import NotebookScreen from "../../../screens/notebook";
|
import NotebookScreen from "../../../screens/notebook";
|
||||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
|
||||||
import useNavigationStore from "../../../stores/use-navigation-store";
|
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
|
|
||||||
|
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
|
||||||
|
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
@@ -45,7 +46,6 @@ import { ReminderTime } from "../../ui/reminder-time";
|
|||||||
import { TimeSince } from "../../ui/time-since";
|
import { TimeSince } from "../../ui/time-since";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
|
||||||
|
|
||||||
type NoteItemProps = {
|
type NoteItemProps = {
|
||||||
item: Note | BaseTrashItem<Note>;
|
item: Note | BaseTrashItem<Note>;
|
||||||
@@ -73,8 +73,9 @@ const NoteItem = ({
|
|||||||
locked,
|
locked,
|
||||||
noOpen = false
|
noOpen = false
|
||||||
}: NoteItemProps) => {
|
}: NoteItemProps) => {
|
||||||
const isEditingNote = useEditorStore(
|
const isEditingNote = useTabStore(
|
||||||
(state) => state.currentEditingNote === item.id
|
(state) =>
|
||||||
|
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
||||||
);
|
);
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const compactMode = useIsCompactModeEnabled(
|
const compactMode = useIsCompactModeEnabled(
|
||||||
@@ -210,6 +211,31 @@ const NoteItem = ({
|
|||||||
color={colors.error.accent}
|
color={colors.error.accent}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{item.localOnly ? (
|
||||||
|
<Icon
|
||||||
|
testID="sync-off"
|
||||||
|
name="sync-off"
|
||||||
|
size={SIZE.sm}
|
||||||
|
style={{
|
||||||
|
marginRight: 6
|
||||||
|
}}
|
||||||
|
color={primaryColors.icon}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{item.readonly ? (
|
||||||
|
<Icon
|
||||||
|
testID="pencil-lock"
|
||||||
|
name="pencil-lock"
|
||||||
|
size={SIZE.sm}
|
||||||
|
style={{
|
||||||
|
marginRight: 6
|
||||||
|
}}
|
||||||
|
color={primaryColors.icon}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<TimeSince
|
<TimeSince
|
||||||
style={{
|
style={{
|
||||||
fontSize: SIZE.xs,
|
fontSize: SIZE.xs,
|
||||||
@@ -225,12 +251,13 @@ const NoteItem = ({
|
|||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginRight: 6
|
marginRight: 6,
|
||||||
|
gap: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name="attachment"
|
name="attachment"
|
||||||
size={SIZE.md}
|
size={SIZE.sm}
|
||||||
color={primaryColors.icon}
|
color={primaryColors.icon}
|
||||||
/>
|
/>
|
||||||
<Paragraph
|
<Paragraph
|
||||||
@@ -270,7 +297,7 @@ const NoteItem = ({
|
|||||||
<Icon
|
<Icon
|
||||||
testID="icon-star"
|
testID="icon-star"
|
||||||
name="star-outline"
|
name="star-outline"
|
||||||
size={SIZE.md}
|
size={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
@@ -287,7 +314,8 @@ const NoteItem = ({
|
|||||||
height={23}
|
height={23}
|
||||||
type="plain"
|
type="plain"
|
||||||
textStyle={{
|
textStyle={{
|
||||||
textDecorationLine: "underline"
|
textDecorationLine: "underline",
|
||||||
|
color: colors.secondary.paragraph
|
||||||
}}
|
}}
|
||||||
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
|
||||||
fontSize={SIZE.xs}
|
fontSize={SIZE.xs}
|
||||||
@@ -369,7 +397,7 @@ const NoteItem = ({
|
|||||||
<Icon
|
<Icon
|
||||||
testID="icon-star"
|
testID="icon-star"
|
||||||
name="star-outline"
|
name="star-outline"
|
||||||
size={SIZE.md}
|
size={SIZE.sm}
|
||||||
style={{
|
style={{
|
||||||
marginRight: 6
|
marginRight: 6
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -26,17 +26,14 @@ import { DDS } from "../../../services/device-detection";
|
|||||||
import {
|
import {
|
||||||
eSendEvent,
|
eSendEvent,
|
||||||
hideSheet,
|
hideSheet,
|
||||||
openVault,
|
|
||||||
presentSheet
|
presentSheet
|
||||||
} from "../../../services/event-manager";
|
} from "../../../services/event-manager";
|
||||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
|
||||||
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||||
import { tabBarRef } from "../../../utils/global-refs";
|
import { tabBarRef } from "../../../utils/global-refs";
|
||||||
|
|
||||||
import NotePreview from "../../note-history/preview";
|
|
||||||
import SelectionWrapper from "../selection-wrapper";
|
|
||||||
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
||||||
|
import NotePreview from "../../note-history/preview";
|
||||||
|
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||||
|
|
||||||
export const openNote = async (
|
export const openNote = async (
|
||||||
item: Note,
|
item: Note,
|
||||||
@@ -49,51 +46,22 @@ export const openNote = async (
|
|||||||
if (!isTrash) {
|
if (!isTrash) {
|
||||||
note = (await db.notes.note(item.id)) as Note;
|
note = (await db.notes.note(item.id)) as Note;
|
||||||
}
|
}
|
||||||
if (useSelectionStore.getState().selectionMode === item.type) {
|
|
||||||
const {
|
|
||||||
selectedItemsList,
|
|
||||||
selectionMode,
|
|
||||||
clearSelection,
|
|
||||||
setSelectedItem
|
|
||||||
} = useSelectionStore.getState();
|
|
||||||
|
|
||||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
if (selectItem(item)) return;
|
||||||
setSelectedItem(note.id);
|
|
||||||
} else {
|
|
||||||
clearSelection();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (note.conflicted) {
|
if (note.conflicted) {
|
||||||
eSendEvent(eShowMergeDialog, note);
|
eSendEvent(eShowMergeDialog, note);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await db.vaults.itemExists(note)) {
|
|
||||||
openVault({
|
|
||||||
item: note,
|
|
||||||
novault: true,
|
|
||||||
locked: true,
|
|
||||||
goToEditor: true,
|
|
||||||
title: "Open note",
|
|
||||||
description: "Unlock note to open it in editor."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isTrash) {
|
if (isTrash) {
|
||||||
if (!note.contentId) return;
|
if (!note.contentId) return;
|
||||||
|
|
||||||
const content = await db.content.get(note.contentId as string);
|
const content = await db.content.get(note.contentId as string);
|
||||||
presentSheet({
|
presentSheet({
|
||||||
component: (
|
component: <NotePreview note={item} content={content} />
|
||||||
<NotePreview note={item} content={{ type: "tiptap", data: content }} />
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (note?.readonly) {
|
|
||||||
useEditorStore.getState().setReadonly(note?.readonly);
|
|
||||||
}
|
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note
|
item: note
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,18 +27,12 @@ import Navigation from "../../../services/navigation";
|
|||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||||
import { presentDialog } from "../../dialog/functions";
|
import { presentDialog } from "../../dialog/functions";
|
||||||
import SelectionWrapper from "../selection-wrapper";
|
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||||
|
|
||||||
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
|
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
|
||||||
const isTrash = item.type === "trash";
|
const isTrash = item.type === "trash";
|
||||||
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
|
|
||||||
useSelectionStore.getState();
|
if (selectItem(item)) return;
|
||||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
|
||||||
setSelectedItem(item.id);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
clearSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTrash) {
|
if (isTrash) {
|
||||||
presentDialog({
|
presentDialog({
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import React from "react";
|
|||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
import ReminderSheet from "../../sheets/reminder";
|
import ReminderSheet from "../../sheets/reminder";
|
||||||
@@ -30,7 +29,7 @@ import { IconButton } from "../../ui/icon-button";
|
|||||||
import { ReminderTime } from "../../ui/reminder-time";
|
import { ReminderTime } from "../../ui/reminder-time";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import SelectionWrapper from "../selection-wrapper";
|
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||||
|
|
||||||
const ReminderItem = React.memo(
|
const ReminderItem = React.memo(
|
||||||
({
|
({
|
||||||
@@ -44,18 +43,7 @@ const ReminderItem = React.memo(
|
|||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const openReminder = () => {
|
const openReminder = () => {
|
||||||
const {
|
if (selectItem(item)) return;
|
||||||
selectedItemsList,
|
|
||||||
setSelectedItem,
|
|
||||||
selectionMode,
|
|
||||||
clearSelection
|
|
||||||
} = useSelectionStore.getState();
|
|
||||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
|
||||||
setSelectedItem(item.id);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
clearSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
ReminderSheet.present(item, undefined, isSheet);
|
ReminderSheet.present(item, undefined, isSheet);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,17 +17,18 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Item } from "@notesnook/core";
|
||||||
import { useThemeColors } from "@notesnook/theme";
|
import { useThemeColors } from "@notesnook/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import useIsSelected from "../../../hooks/use-selected";
|
import useIsSelected from "../../../hooks/use-selected";
|
||||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
|
||||||
import { Item } from "@notesnook/core";
|
|
||||||
|
|
||||||
export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const isEditingNote = useEditorStore(
|
const isEditingNote = useTabStore(
|
||||||
(state) => state.currentEditingNote === item.id
|
(state) =>
|
||||||
|
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selected] = useIsSelected(item);
|
const [selected] = useIsSelected(item);
|
||||||
@@ -41,8 +42,8 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
|||||||
backgroundColor: colors.selected.background,
|
backgroundColor: colors.selected.background,
|
||||||
borderLeftWidth: 5,
|
borderLeftWidth: 5,
|
||||||
borderLeftColor: isEditingNote
|
borderLeftColor: isEditingNote
|
||||||
? item.color
|
? color
|
||||||
? colors.static[item.color]
|
? color
|
||||||
: colors.selected.accent
|
: colors.selected.accent
|
||||||
: "transparent"
|
: "transparent"
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -26,6 +26,23 @@ import { Filler } from "./back-fill";
|
|||||||
import { SelectionIcon } from "./selection";
|
import { SelectionIcon } from "./selection";
|
||||||
import { Item, TrashItem } from "@notesnook/core";
|
import { Item, TrashItem } from "@notesnook/core";
|
||||||
|
|
||||||
|
export function selectItem(item: Item) {
|
||||||
|
if (useSelectionStore.getState().selectionMode === item.type) {
|
||||||
|
const { selectionMode, clearSelection, setSelectedItem } =
|
||||||
|
useSelectionStore.getState();
|
||||||
|
|
||||||
|
if (selectionMode === item.type) {
|
||||||
|
setSelectedItem(item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSelectionStore.getState().selectedItemsList.length === 0) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
type SelectionWrapperProps = PropsWithChildren<{
|
type SelectionWrapperProps = PropsWithChildren<{
|
||||||
item: Item;
|
item: Item;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ import React from "react";
|
|||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { notesnook } from "../../../../e2e/test.ids";
|
import { notesnook } from "../../../../e2e/test.ids";
|
||||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
|
||||||
import { SIZE } from "../../../utils/size";
|
import { SIZE } from "../../../utils/size";
|
||||||
import { Properties } from "../../properties";
|
import { Properties } from "../../properties";
|
||||||
import { IconButton } from "../../ui/icon-button";
|
import { IconButton } from "../../ui/icon-button";
|
||||||
import Heading from "../../ui/typography/heading";
|
import Heading from "../../ui/typography/heading";
|
||||||
import Paragraph from "../../ui/typography/paragraph";
|
import Paragraph from "../../ui/typography/paragraph";
|
||||||
import SelectionWrapper from "../selection-wrapper";
|
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||||
|
|
||||||
const TagItem = React.memo(
|
const TagItem = React.memo(
|
||||||
({
|
({
|
||||||
@@ -43,18 +42,7 @@ const TagItem = React.memo(
|
|||||||
}) => {
|
}) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
const {
|
if (selectItem(item)) return;
|
||||||
selectedItemsList,
|
|
||||||
setSelectedItem,
|
|
||||||
selectionMode,
|
|
||||||
clearSelection
|
|
||||||
} = useSelectionStore.getState();
|
|
||||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
|
||||||
setSelectedItem(item.id);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
clearSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
TaggedNotes.navigate(item, true);
|
TaggedNotes.navigate(item, true);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export const Card = ({ color }: { color?: string }) => {
|
|||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
paddingHorizontal: 0
|
paddingHorizontal: 0,
|
||||||
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
@@ -91,7 +92,6 @@ export const Card = ({ color }: { color?: string }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
flexShrink: 1,
|
|
||||||
marginRight: 10
|
marginRight: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default function List(props: ListProps) {
|
|||||||
const groupOptions = useGroupOptions(groupType);
|
const groupOptions = useGroupOptions(groupType);
|
||||||
|
|
||||||
const _onRefresh = async () => {
|
const _onRefresh = async () => {
|
||||||
Sync.run("global", false, true, () => {
|
Sync.run("global", false, "full", () => {
|
||||||
props.onRefresh?.();
|
props.onRefresh?.();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -192,7 +192,9 @@ export default function List(props: ListProps) {
|
|||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
ListFooterComponent={<Footer />}
|
ListFooterComponent={
|
||||||
|
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
|
||||||
|
}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<>
|
<>
|
||||||
{props.CustomLisHeader ? (
|
{props.CustomLisHeader ? (
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
|
|||||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||||
import ReminderItem from "../list-items/reminder";
|
import ReminderItem from "../list-items/reminder";
|
||||||
import TagItem from "../list-items/tag";
|
import TagItem from "../list-items/tag";
|
||||||
|
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
|
||||||
|
|
||||||
type ListItemWrapperProps<TItem = Item> = {
|
type ListItemWrapperProps<TItem = Item> = {
|
||||||
group?: GroupingKey;
|
group?: GroupingKey;
|
||||||
@@ -75,6 +76,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
|||||||
const refreshTimeout = useRef<NodeJS.Timeout>();
|
const refreshTimeout = useRef<NodeJS.Timeout>();
|
||||||
const currentItemId = useRef<string>();
|
const currentItemId = useRef<string>();
|
||||||
const locked = useRef(false);
|
const locked = useRef(false);
|
||||||
|
const compactMode = useIsCompactModeEnabled(props.dataType as ItemType);
|
||||||
|
|
||||||
const refreshItem = useCallback((resolvedItem: any) => {
|
const refreshItem = useCallback((resolvedItem: any) => {
|
||||||
if (!resolvedItem || !resolvedItem.data) {
|
if (!resolvedItem || !resolvedItem.data) {
|
||||||
@@ -117,14 +119,24 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
|||||||
}
|
}
|
||||||
currentItemId.current = resolvedItem.item.id;
|
currentItemId.current = resolvedItem.item.id;
|
||||||
setItem(resolvedItem.item);
|
setItem(resolvedItem.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedItem?.group) {
|
||||||
setGroupHeader(resolvedItem.group);
|
setGroupHeader(resolvedItem.group);
|
||||||
|
} else {
|
||||||
|
setGroupHeader(undefined);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (previousIndex.current !== index) {
|
if (previousIndex.current !== index) {
|
||||||
previousIndex.current = index;
|
previousIndex.current = index;
|
||||||
const resolvedItem = items?.cacheItem(index);
|
const resolvedItem = items?.cacheItem(index);
|
||||||
refreshItem(resolvedItem);
|
if (resolvedItem) {
|
||||||
|
refreshItem(resolvedItem);
|
||||||
|
} else {
|
||||||
|
setItem(undefined);
|
||||||
|
setGroupHeader(undefined);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -132,12 +144,15 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
|||||||
try {
|
try {
|
||||||
clearTimeout(refreshTimeout.current);
|
clearTimeout(refreshTimeout.current);
|
||||||
const idx = index;
|
const idx = index;
|
||||||
refreshTimeout.current = setTimeout(async () => {
|
refreshTimeout.current = setTimeout(
|
||||||
if (idx !== previousIndex.current) return;
|
async () => {
|
||||||
const resolvedItem = await items?.item(idx, resolveItems);
|
if (idx !== previousIndex.current) return;
|
||||||
if (idx !== previousIndex.current) return;
|
const resolvedItem = await items?.item(idx, resolveItems);
|
||||||
refreshItem(resolvedItem);
|
if (idx !== previousIndex.current) return;
|
||||||
}, 100);
|
refreshItem(resolvedItem);
|
||||||
|
},
|
||||||
|
items?.cacheItem(index) ? 100 : 0
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error", e);
|
console.log("Error", e);
|
||||||
}
|
}
|
||||||
@@ -148,7 +163,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
|||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: 120,
|
height: compactMode ? 50 : 120,
|
||||||
width: "100%"
|
width: "100%"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import { FlatList, StyleSheet, View } from "react-native";
|
|||||||
import {
|
import {
|
||||||
DraxList,
|
DraxList,
|
||||||
DraxListProps,
|
DraxListProps,
|
||||||
DraxListRenderItemContent,
|
DraxListRenderItemContent
|
||||||
DraxProvider
|
|
||||||
} from "react-native-drax";
|
} from "react-native-drax";
|
||||||
|
|
||||||
import { tabBarRef } from "../../utils/global-refs";
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
|
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
|
||||||
@@ -74,6 +74,7 @@ function ReorderableList<T extends { id: string }>({
|
|||||||
const renderItemContent: DraxListRenderItemContent<T> = React.useCallback(
|
const renderItemContent: DraxListRenderItemContent<T> = React.useCallback(
|
||||||
(info, props) => {
|
(info, props) => {
|
||||||
const isHidden = hiddenItemsState.indexOf(info?.item?.id) > -1;
|
const isHidden = hiddenItemsState.indexOf(info?.item?.id) > -1;
|
||||||
|
|
||||||
return isHidden && !dragging ? null : (
|
return isHidden && !dragging ? null : (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -136,53 +137,49 @@ function ReorderableList<T extends { id: string }>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraxProvider>
|
<View style={styles.container}>
|
||||||
<View style={styles.container}>
|
<DraxList
|
||||||
<DraxList
|
{...restProps}
|
||||||
{...restProps}
|
ref={listRef}
|
||||||
ref={listRef}
|
data={getOrderedItems()}
|
||||||
data={getOrderedItems()}
|
renderItemContent={renderItemContent}
|
||||||
renderItemContent={renderItemContent}
|
itemStyles={{
|
||||||
itemStyles={{
|
hoverDragReleasedStyle: {
|
||||||
hoverDragReleasedStyle: {
|
display: "none"
|
||||||
display: "none"
|
},
|
||||||
},
|
hoverDraggingWithoutReceiverStyle: {
|
||||||
hoverDraggingWithoutReceiverStyle: {
|
opacity: 0.5
|
||||||
opacity: 0.5
|
},
|
||||||
},
|
dragReleasedStyle: {
|
||||||
dragReleasedStyle: {
|
opacity: 1
|
||||||
opacity: 1
|
},
|
||||||
},
|
hoverDraggingStyle: {
|
||||||
hoverDraggingStyle: {
|
backgroundColor: colors.secondary.background
|
||||||
backgroundColor: colors.secondary.background
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
longPressDelay={500}
|
|
||||||
onItemDragStart={() =>
|
|
||||||
useSideBarDraggingStore.setState({
|
|
||||||
dragging: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
itemsDraggable={disableDefaultDrag ? dragging : true}
|
}}
|
||||||
lockItemDragsToMainAxis
|
longPressDelay={500}
|
||||||
onItemReorder={({ fromIndex, fromItem, toIndex, toItem }) => {
|
onItemDragStart={() =>
|
||||||
const newOrder = getOrderedItems().map((item) => item.id);
|
useSideBarDraggingStore.setState({
|
||||||
const element = newOrder.splice(fromIndex, 1)[0];
|
dragging: true
|
||||||
if (toIndex === 0) {
|
})
|
||||||
newOrder.unshift(element);
|
}
|
||||||
} else {
|
disableVirtualization
|
||||||
newOrder.splice(toIndex, 0, element);
|
itemsDraggable={disableDefaultDrag ? dragging : true}
|
||||||
}
|
lockItemDragsToMainAxis
|
||||||
setItemsOrder(newOrder);
|
onItemReorder={({ fromIndex, fromItem, toIndex, toItem }) => {
|
||||||
onListOrderChanged?.(newOrder);
|
const newOrder = getOrderedItems().map((item) => item.id);
|
||||||
}}
|
const element = newOrder.splice(fromIndex, 1)[0];
|
||||||
onItemDragEnd={(e) => {
|
if (toIndex === 0) {
|
||||||
console.log(e.receiver?.receiveOffset);
|
newOrder.unshift(element);
|
||||||
}}
|
} else {
|
||||||
keyExtractor={(item) => (item as any).id}
|
newOrder.splice(toIndex, 0, element);
|
||||||
/>
|
}
|
||||||
</View>
|
setItemsOrder(newOrder);
|
||||||
</DraxProvider>
|
onListOrderChanged?.(newOrder);
|
||||||
|
}}
|
||||||
|
keyExtractor={(item) => (item as any).id}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ import { SafeAreaView, Text, View } from "react-native";
|
|||||||
import Animated from "react-native-reanimated";
|
import Animated from "react-native-reanimated";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||||
import Editor from "../../screens/editor";
|
import { ReadonlyEditor } from "../../screens/editor/readonly-editor";
|
||||||
|
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import {
|
import {
|
||||||
@@ -38,7 +39,6 @@ import Sync from "../../services/sync";
|
|||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import { sleep } from "../../utils/time";
|
|
||||||
import BaseDialog from "../dialog/base-dialog";
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import DialogButtons from "../dialog/dialog-buttons";
|
import DialogButtons from "../dialog/dialog-buttons";
|
||||||
import DialogContainer from "../dialog/dialog-container";
|
import DialogContainer from "../dialog/dialog-container";
|
||||||
@@ -47,6 +47,7 @@ import { Button } from "../ui/button";
|
|||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
import Seperator from "../ui/seperator";
|
import Seperator from "../ui/seperator";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { diff } from "diffblazer";
|
||||||
|
|
||||||
const MergeConflicts = () => {
|
const MergeConflicts = () => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -88,10 +89,11 @@ const MergeConflicts = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Navigation.queueRoutesForUpdate();
|
Navigation.queueRoutesForUpdate();
|
||||||
if (editorController.current?.note?.id === note.id) {
|
|
||||||
|
if (useTabStore.getState().getCurrentNoteId() === note.id) {
|
||||||
// reload the note in editor
|
// reload the note in editor
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: editorController.current?.note,
|
item: editorController.current.note.current[note.id],
|
||||||
forced: true
|
forced: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -311,26 +313,20 @@ const MergeConflicts = () => {
|
|||||||
height: height / 2 - (50 + insets.top / 2),
|
height: height / 2 - (50 + insets.top / 2),
|
||||||
backgroundColor: colors.primary.background,
|
backgroundColor: colors.primary.background,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: colors.secondary.background
|
borderBottomColor: colors.primary.border
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Editor
|
<ReadonlyEditor
|
||||||
noHeader
|
editorId="conflictPrimary"
|
||||||
noToolbar
|
onLoad={async (loadContent) => {
|
||||||
readonly
|
|
||||||
editorId=":conflictPrimary"
|
|
||||||
onLoad={async () => {
|
|
||||||
const note = await db.notes.note(content.current?.noteId);
|
const note = await db.notes.note(content.current?.noteId);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
await sleep(300);
|
loadContent({
|
||||||
eSendEvent(eOnLoadNote + ":conflictPrimary", {
|
id: note.id,
|
||||||
item: {
|
data: diff(
|
||||||
...note,
|
content.current.conflicted.data,
|
||||||
content: {
|
content.current.data
|
||||||
...content.current,
|
)
|
||||||
isPreview: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -351,20 +347,14 @@ const MergeConflicts = () => {
|
|||||||
borderRadius: 10
|
borderRadius: 10
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Editor
|
<ReadonlyEditor
|
||||||
noHeader
|
editorId="conflictSecondary"
|
||||||
noToolbar
|
onLoad={async (loadContent) => {
|
||||||
readonly
|
|
||||||
editorId=":conflictSecondary"
|
|
||||||
onLoad={async () => {
|
|
||||||
const note = await db.notes.note(content.current?.noteId);
|
const note = await db.notes.note(content.current?.noteId);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
await sleep(300);
|
loadContent({
|
||||||
eSendEvent(eOnLoadNote + ":conflictSecondary", {
|
id: note.id,
|
||||||
item: {
|
data: content.current.conflicted.data
|
||||||
...note,
|
|
||||||
content: { ...content.current.conflicted, isPreview: true }
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -199,7 +199,9 @@ export default function NoteHistory({
|
|||||||
Note version history is local only.{" "}
|
Note version history is local only.{" "}
|
||||||
<Text
|
<Text
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
openLinkInBrowser("https://docs.notesnook.com/versionhistory");
|
openLinkInBrowser(
|
||||||
|
"https://help.notesnook.com/note-version-history"
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
color: colors.primary.accent,
|
color: colors.primary.accent,
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import { useThemeColors } from "@notesnook/theme";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import Editor from "../../screens/editor";
|
import { ReadonlyEditor } from "../../screens/editor/readonly-editor";
|
||||||
|
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
|
||||||
import { editorController } from "../../screens/editor/tiptap/utils";
|
import { editorController } from "../../screens/editor/tiptap/utils";
|
||||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
import { ToastManager, eSendEvent } from "../../services/event-manager";
|
||||||
import Navigation from "../../services/navigation";
|
import Navigation from "../../services/navigation";
|
||||||
import { useEditorStore } from "../../stores/use-editor-store";
|
|
||||||
import { useSelectionStore } from "../../stores/use-selection-store";
|
import { useSelectionStore } from "../../stores/use-selection-store";
|
||||||
import { useTrashStore } from "../../stores/use-trash-store";
|
import { useTrashStore } from "../../stores/use-trash-store";
|
||||||
import { eCloseSheet, eOnLoadNote } from "../../utils/events";
|
import { eCloseSheet, eOnLoadNote } from "../../utils/events";
|
||||||
@@ -34,6 +34,7 @@ import DialogHeader from "../dialog/dialog-header";
|
|||||||
import { presentDialog } from "../dialog/functions";
|
import { presentDialog } from "../dialog/functions";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import Paragraph from "../ui/typography/paragraph";
|
import Paragraph from "../ui/typography/paragraph";
|
||||||
|
import { diff } from "diffblazer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -42,7 +43,6 @@ import Paragraph from "../ui/typography/paragraph";
|
|||||||
*/
|
*/
|
||||||
export default function NotePreview({ session, content, note }) {
|
export default function NotePreview({ session, content, note }) {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
const editorId = ":noteHistory";
|
|
||||||
const [locked, setLocked] = useState(false);
|
const [locked, setLocked] = useState(false);
|
||||||
|
|
||||||
async function restore() {
|
async function restore() {
|
||||||
@@ -58,10 +58,11 @@ export default function NotePreview({ session, content, note }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await db.noteHistory.restore(session.id);
|
await db.noteHistory.restore(session.id);
|
||||||
if (useEditorStore.getState()?.currentEditingNote === session?.noteId) {
|
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
|
||||||
if (editorController.current?.note) {
|
const note = editorController.current.note.current[session?.noteId];
|
||||||
|
if (note) {
|
||||||
eSendEvent(eOnLoadNote, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: editorController.current?.note,
|
item: note,
|
||||||
forced: true
|
forced: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,22 +118,25 @@ export default function NotePreview({ session, content, note }) {
|
|||||||
flex: 1
|
flex: 1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Editor
|
<ReadonlyEditor
|
||||||
noHeader
|
editorId="historyPreview"
|
||||||
noToolbar
|
onLoad={async (loadContent) => {
|
||||||
readonly
|
try {
|
||||||
editorId={editorId}
|
if (content.data) {
|
||||||
onLoad={async () => {
|
const _note = note || (await db.notes.note(session?.noteId));
|
||||||
const _note = note || (await db.notes.note(session?.noteId));
|
const currentContent = await db.content.get(_note.contentId);
|
||||||
eSendEvent(eOnLoadNote + editorId, {
|
loadContent({
|
||||||
item: {
|
data: diff(currentContent.data, content.data),
|
||||||
..._note,
|
id: _note.id
|
||||||
content: {
|
});
|
||||||
...content,
|
|
||||||
isPreview: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
|
ToastManager.error(
|
||||||
|
e,
|
||||||
|
"Failed to load history preview",
|
||||||
|
"local"
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export const features = [
|
|||||||
platform: "android"
|
platform: "android"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
info: "* Free users are limited to keeping 3 notebooks (no limit on topics) and 5 tags."
|
info: "* Free users are limited to keeping 3 notebooks and 5 tags."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ export const ColorTags = ({ item }: { item: Note }) => {
|
|||||||
visible={visible}
|
visible={visible}
|
||||||
setVisible={setVisible}
|
setVisible={setVisible}
|
||||||
onColorAdded={async (color) => {
|
onColorAdded={async (color) => {
|
||||||
|
await db.relations.to(note, "color").unlink();
|
||||||
await db.relations.add(color, note);
|
await db.relations.add(color, note);
|
||||||
useRelationStore.getState().update();
|
useRelationStore.getState().update();
|
||||||
useMenuStore.getState().setColorNotes();
|
useMenuStore.getState().setColorNotes();
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ import { Platform, View } from "react-native";
|
|||||||
import { FlatList } from "react-native-actions-sheet";
|
import { FlatList } from "react-native-actions-sheet";
|
||||||
import { db } from "../../common/database";
|
import { db } from "../../common/database";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import { presentSheet } from "../../services/event-manager";
|
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||||
import { ColorValues } from "../../utils/colors";
|
import { ColorValues } from "../../utils/colors";
|
||||||
|
import { eOnLoadNote } from "../../utils/events";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
import SheetProvider from "../sheet-provider";
|
import SheetProvider from "../sheet-provider";
|
||||||
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
|
import { useSideBarDraggingStore } from "../side-menu/dragging-store";
|
||||||
|
import { IconButton } from "../ui/icon-button";
|
||||||
import { Pressable } from "../ui/pressable";
|
import { Pressable } from "../ui/pressable";
|
||||||
import { ReminderTime } from "../ui/reminder-time";
|
import { ReminderTime } from "../ui/reminder-time";
|
||||||
import Heading from "../ui/typography/heading";
|
import Heading from "../ui/typography/heading";
|
||||||
@@ -36,6 +38,7 @@ import { Items } from "./items";
|
|||||||
import Notebooks from "./notebooks";
|
import Notebooks from "./notebooks";
|
||||||
import { Synced } from "./synced";
|
import { Synced } from "./synced";
|
||||||
import { TagStrip, Tags } from "./tags";
|
import { TagStrip, Tags } from "./tags";
|
||||||
|
import { tabBarRef } from "../../utils/global-refs";
|
||||||
|
|
||||||
const Line = ({ top = 6, bottom = 6 }) => {
|
const Line = ({ top = 6, bottom = 6 }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
@@ -74,6 +77,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
|||||||
maxHeight: "100%"
|
maxHeight: "100%"
|
||||||
}}
|
}}
|
||||||
nestedScrollEnabled
|
nestedScrollEnabled
|
||||||
|
bounces={false}
|
||||||
data={[0]}
|
data={[0]}
|
||||||
keyExtractor={() => "properties-scroll-item"}
|
keyExtractor={() => "properties-scroll-item"}
|
||||||
renderItem={() => (
|
renderItem={() => (
|
||||||
@@ -89,31 +93,61 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
|||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center"
|
justifyContent: "space-between"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.type === "color" ? (
|
<View
|
||||||
<Pressable
|
style={{
|
||||||
type="accent"
|
flexDirection: "row",
|
||||||
accentColor={item.colorCode}
|
alignItems: "center",
|
||||||
accentText={colors.static.white}
|
flexShrink: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.type === "color" ? (
|
||||||
|
<Pressable
|
||||||
|
type="accent"
|
||||||
|
accentColor={item.colorCode}
|
||||||
|
accentText={colors.static.white}
|
||||||
|
style={{
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
borderRadius: 100,
|
||||||
|
marginRight: 10
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Heading size={SIZE.lg}>
|
||||||
|
{item.type === "tag" && !isColor ? (
|
||||||
|
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
||||||
|
#
|
||||||
|
</Heading>
|
||||||
|
) : null}
|
||||||
|
{item.title}
|
||||||
|
</Heading>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{item.type === "note" ? (
|
||||||
|
<IconButton
|
||||||
|
name="open-in-new"
|
||||||
|
type="plain"
|
||||||
|
color={colors.primary.icon}
|
||||||
|
size={SIZE.lg}
|
||||||
style={{
|
style={{
|
||||||
width: 30,
|
alignSelf: "flex-start"
|
||||||
height: 30,
|
}}
|
||||||
borderRadius: 100,
|
onPress={() => {
|
||||||
marginRight: 10
|
close();
|
||||||
|
eSendEvent(eOnLoadNote, {
|
||||||
|
item: item,
|
||||||
|
presistTab: true
|
||||||
|
});
|
||||||
|
if (!DDS.isTab) {
|
||||||
|
tabBarRef.current?.goToPage(1);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Heading size={SIZE.lg}>
|
|
||||||
{item.type === "tag" && !isColor ? (
|
|
||||||
<Heading size={SIZE.xl} color={colors.primary.accent}>
|
|
||||||
#
|
|
||||||
</Heading>
|
|
||||||
) : null}
|
|
||||||
{item.title}
|
|
||||||
</Heading>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{item.type === "notebook" && item.description ? (
|
{item.type === "notebook" && item.description ? (
|
||||||
@@ -208,13 +242,14 @@ Properties.present = async (item, buttons = [], isSheet) => {
|
|||||||
"attachments",
|
"attachments",
|
||||||
"lock-unlock",
|
"lock-unlock",
|
||||||
"trash",
|
"trash",
|
||||||
"remove-from-topic",
|
|
||||||
"remove-from-notebook",
|
"remove-from-notebook",
|
||||||
"history",
|
"history",
|
||||||
"read-only",
|
"read-only",
|
||||||
"reminders",
|
"reminders",
|
||||||
"local-only",
|
"local-only",
|
||||||
"duplicate",
|
"duplicate",
|
||||||
|
"copy-link",
|
||||||
|
"references",
|
||||||
...android,
|
...android,
|
||||||
...buttons
|
...buttons
|
||||||
]);
|
]);
|
||||||
@@ -228,7 +263,8 @@ Properties.present = async (item, buttons = [], isSheet) => {
|
|||||||
"trash",
|
"trash",
|
||||||
"default-notebook",
|
"default-notebook",
|
||||||
"add-notebook",
|
"add-notebook",
|
||||||
"move-notes"
|
"move-notes",
|
||||||
|
"move-notebook"
|
||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case "tag":
|
case "tag":
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import React from "react";
|
|||||||
import { Dimensions, ScrollView, View } from "react-native";
|
import { Dimensions, ScrollView, View } from "react-native";
|
||||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||||
import { useActions } from "../../hooks/use-actions";
|
import { useActions } from "../../hooks/use-actions";
|
||||||
|
import { useStoredRef } from "../../hooks/use-stored-ref";
|
||||||
import { DDS } from "../../services/device-detection";
|
import { DDS } from "../../services/device-detection";
|
||||||
import { useSettingStore } from "../../stores/use-setting-store";
|
import { useSettingStore } from "../../stores/use-setting-store";
|
||||||
import { SIZE } from "../../utils/size";
|
import { SIZE } from "../../utils/size";
|
||||||
@@ -31,6 +32,8 @@ import Paragraph from "../ui/typography/paragraph";
|
|||||||
|
|
||||||
export const Items = ({ item, buttons, close }) => {
|
export const Items = ({ item, buttons, close }) => {
|
||||||
const { colors } = useThemeColors();
|
const { colors } = useThemeColors();
|
||||||
|
const topBarSorting = useStoredRef("topbar-sorting-ref", {});
|
||||||
|
|
||||||
const dimensions = useSettingStore((state) => state.dimensions);
|
const dimensions = useSettingStore((state) => state.dimensions);
|
||||||
const actions = useActions({ item, close });
|
const actions = useActions({ item, close });
|
||||||
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
|
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
|
||||||
@@ -45,7 +48,6 @@ export const Items = ({ item, buttons, close }) => {
|
|||||||
|
|
||||||
const _renderRowItem = ({ item }) => (
|
const _renderRowItem = ({ item }) => (
|
||||||
<View
|
<View
|
||||||
onPress={item.func}
|
|
||||||
key={item.id}
|
key={item.id}
|
||||||
testID={"icon-" + item.id}
|
testID={"icon-" + item.id}
|
||||||
style={{
|
style={{
|
||||||
@@ -71,7 +73,7 @@ export const Items = ({ item, buttons, close }) => {
|
|||||||
<Icon
|
<Icon
|
||||||
allowFontScaling
|
allowFontScaling
|
||||||
name={item.icon}
|
name={item.icon}
|
||||||
size={DDS.isTab ? SIZE.xxl : SIZE.lg}
|
size={DDS.isTab ? SIZE.xxl : shouldShrink ? SIZE.xxl : SIZE.lg}
|
||||||
color={
|
color={
|
||||||
item.on
|
item.on
|
||||||
? colors.primary.accent
|
? colors.primary.accent
|
||||||
@@ -117,16 +119,23 @@ export const Items = ({ item, buttons, close }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderTopBarItem = (item, index) => {
|
const renderTopBarItem = (item, index) => {
|
||||||
const isLast = index === topBarItems.length;
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={item.func}
|
onPress={() => {
|
||||||
|
item.func();
|
||||||
|
setImmediate(() => {
|
||||||
|
const currentValue = topBarSorting.current[item.id] || 0;
|
||||||
|
topBarSorting.current = {
|
||||||
|
...topBarSorting.current,
|
||||||
|
[item.id]: currentValue + 1
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
testID={"icon-" + item.id}
|
testID={"icon-" + item.id}
|
||||||
activeOpacity={1}
|
activeOpacity={1}
|
||||||
style={{
|
style={{
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
marginRight: isLast ? 0 : 10,
|
|
||||||
paddingHorizontal: 0,
|
paddingHorizontal: 0,
|
||||||
width: topBarItemWidth
|
width: topBarItemWidth
|
||||||
}}
|
}}
|
||||||
@@ -174,34 +183,36 @@ export const Items = ({ item, buttons, close }) => {
|
|||||||
"favorite",
|
"favorite",
|
||||||
"copy",
|
"copy",
|
||||||
"share",
|
"share",
|
||||||
|
"lock-unlock",
|
||||||
|
"publish",
|
||||||
"export",
|
"export",
|
||||||
"lock-unlock"
|
"copy-link",
|
||||||
|
"duplicate",
|
||||||
|
"local-only",
|
||||||
|
"read-only"
|
||||||
|
];
|
||||||
|
|
||||||
|
const bottomBarItemsList = [
|
||||||
|
"notebooks",
|
||||||
|
"add-reminder",
|
||||||
|
"pin-to-notifications",
|
||||||
|
"history",
|
||||||
|
"reminders",
|
||||||
|
"attachments",
|
||||||
|
"references",
|
||||||
|
"trash"
|
||||||
];
|
];
|
||||||
|
|
||||||
const topBarItems = data
|
const topBarItems = data
|
||||||
.filter((item) => topBarItemsList.indexOf(item.id) > -1)
|
.filter((item) => topBarItemsList.indexOf(item.id) > -1)
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
topBarItemsList.indexOf(a.id) > topBarItemsList.indexOf(b.id) ? 1 : -1
|
topBarItemsList.indexOf(a.id) > topBarItemsList.indexOf(b.id) ? 1 : -1
|
||||||
);
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
const bottomBarItemsList = [
|
return (
|
||||||
"notebooks",
|
(topBarSorting.current[b.id] || 0) - (topBarSorting.current[a.id] || 0)
|
||||||
"add-reminder",
|
);
|
||||||
"pin-to-notifications",
|
});
|
||||||
"duplicate",
|
|
||||||
"read-only",
|
|
||||||
"local-only",
|
|
||||||
"history",
|
|
||||||
"reminders",
|
|
||||||
"attachments",
|
|
||||||
"trash"
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!shouldShrink) {
|
|
||||||
topBarItemsList.push("publish");
|
|
||||||
} else {
|
|
||||||
bottomBarItemsList.splice(4, 0, "publish");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bottomGridItems = data
|
const bottomGridItems = data
|
||||||
.filter((item) => bottomBarItemsList.indexOf(item.id) > -1)
|
.filter((item) => bottomBarItemsList.indexOf(item.id) > -1)
|
||||||
@@ -215,8 +226,8 @@ export const Items = ({ item, buttons, close }) => {
|
|||||||
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
|
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
|
||||||
topBarItemWidth;
|
topBarItemWidth;
|
||||||
|
|
||||||
if (topBarItemWidth < 55) {
|
if (topBarItemWidth < 60) {
|
||||||
topBarItemWidth = 55;
|
topBarItemWidth = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.type === "note" ? (
|
return item.type === "note" ? (
|
||||||
@@ -225,7 +236,13 @@ export const Items = ({ item, buttons, close }) => {
|
|||||||
horizontal
|
horizontal
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
marginTop: 6
|
marginTop: 6,
|
||||||
|
marginBottom: 6
|
||||||
|
}}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{
|
||||||
|
paddingRight: 25,
|
||||||
|
gap: 5
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{topBarItems.map(renderTopBarItem)}
|
{topBarItems.map(renderTopBarItem)}
|
||||||
|
|||||||