Compare commits

..

297 Commits

Author SHA1 Message Date
Abdullah Atta
900f82a991 ui: fix right clicking when menu is open doesn't close it 2024-02-13 11:19:03 +05:00
Abdullah Atta
13bbe2149e ui: fix hover highlighting 2 items in menu 2024-02-13 11:18:47 +05:00
Abdullah Atta
603593a312 web: fix color renaming and add color remove support 2024-02-13 11:18:29 +05:00
Abdullah Atta
0bd385395e web: add readonly indicator on notes 2024-02-13 11:18:05 +05:00
Abdullah Atta
af1312977a web: fix compact view not affecting notes 2024-02-13 11:17:53 +05:00
Abdullah Atta
5002947c27 editor: fix Position -1 is outside of fragment error 2024-02-13 10:46:17 +05:00
Ammar Ahmed
3ed5a031a4 mobile: fix migration bugs 2024-02-12 19:22:12 +05:00
Abdullah Atta
5ba87df099 core: minor refactoring 2024-02-12 15:19:51 +05:00
Abdullah Atta
a1dd4e7a98 core: migrate deleted items as well 2024-02-12 13:50:21 +05:00
Abdullah Atta
21d429f764 web: refresh app on sync completed 2024-02-12 13:06:20 +05:00
Abdullah Atta
f6fba20baf core: fix continuous invalid_grant error if login fails 2024-02-12 12:45:16 +05:00
Abdullah Atta
7542204c6e web: fix app hangs when downloading logs 2024-02-12 12:44:34 +05:00
Abdullah Atta
c5527e3893 web: bump to v3.0.2-beta 2024-02-12 11:49:30 +05:00
Abdullah Atta
0b221cd900 web: hide window/tab title on app lock 2024-02-12 11:48:01 +05:00
Abdullah Atta
d6830c5488 core: add support for creating backups of legacy collections 2024-02-12 11:38:35 +05:00
Abdullah Atta
a7ab07d570 web: fix P.item is undefined error 2024-02-11 13:18:10 +05:00
Abdullah Atta
fec1370239 core: remove deleteReason property 2024-02-11 09:51:11 +05:00
Ammar Ahmed
75704a08ea Update ios.publish.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-11 00:39:58 +05:00
Ammar Ahmed
081b4d4eb1 mobile: cleanup gradle.properties 2024-02-11 00:15:06 +05:00
Ammar Ahmed
e1e850f493 mobile: fix share extension 2024-02-11 00:02:00 +05:00
Ammar Ahmed
8e34679fd5 mobile: beta release 2024-02-10 22:36:29 +05:00
Ammar Ahmed
c2200ee8e1 mobile: fix notebook move to trash 2024-02-10 22:34:13 +05:00
Ammar Ahmed
4c86bac8e0 mobile: cleanup 2024-02-10 22:34:13 +05:00
Abdullah Atta
b152fa7463 desktop: disable downgrades 2024-02-10 22:16:39 +05:00
Ammar Ahmed
949f6ace54 mobile: stop app updates check in beta 2024-02-10 21:23:02 +05:00
Ammar Ahmed
5c6fd953eb mobile: minor fixes 2024-02-10 21:16:26 +05:00
Abdullah Atta
0326c3fee9 web: improve database deletion 2024-02-10 19:52:54 +05:00
Abdullah Atta
d7967d322e web: reload page on reset from error boundary 2024-02-10 19:52:54 +05:00
Abdullah Atta
62d2a4471d web: locked status not updating on note 2024-02-10 19:52:54 +05:00
Abdullah Atta
42df6536c0 web: only save note's content through vault 2024-02-10 19:52:54 +05:00
Abdullah Atta
e9d9252182 core: fix adding note to vault should return its id 2024-02-10 19:52:53 +05:00
Abdullah Atta
a400350e48 core: fix database resetting 2024-02-10 19:52:53 +05:00
Abdullah Atta
1dcbf93b3a theme: fix error input variant 2024-02-10 19:52:53 +05:00
Abdullah Atta
620c0b5006 web: allow typing custom date when adding reminder 2024-02-10 19:52:53 +05:00
Abdullah Atta
d3468401f9 common: use upcoming reminder when resolving reminders on note 2024-02-10 19:52:53 +05:00
Ammar Ahmed
a6b83f503e mobile: fix reminders 2024-02-10 19:06:16 +05:00
Ammar Ahmed
e52ad6186f mobile: fix notebooks & colors 2024-02-10 18:56:45 +05:00
Abdullah Atta
59486a7894 core: set mode to "sync" for migrations during sync 2024-02-10 13:42:43 +05:00
Abdullah Atta
76fbfd3251 desktop: patch better-sqlite3-multiple-ciphers to get prebuilt binaries 2024-02-10 13:30:45 +05:00
Abdullah Atta
804dff4a53 web: bump to 3.0.1-beta 2024-02-10 11:11:45 +05:00
Abdullah Atta
a1df8fbf21 core: enable sync during tests 2024-02-10 11:08:48 +05:00
Abdullah Atta
24c69a8c73 core: improve backup importing 2024-02-10 11:00:51 +05:00
Abdullah Atta
16e483954e core: add support for nested transactions 2024-02-10 11:00:51 +05:00
Abdullah Atta
7315192e1a web: use single transaction for importing a backup 2024-02-10 11:00:51 +05:00
Abdullah Atta
f0d3cca188 web: make sqlite slightly faster 2024-02-10 11:00:51 +05:00
Ammar Ahmed
557baf43a3 mobile: fix release build 2024-02-09 12:24:46 +05:00
Abdullah Atta
b9c8ac091f core: fix type errors 2024-02-09 11:06:11 +05:00
Ammar Ahmed
5add1b1d48 mobile: minor fixes 2024-02-09 10:41:21 +05:00
Ammar Ahmed
2734e50a97 core: fix migration bugs 2024-02-09 10:41:00 +05:00
Ammar Ahmed
ab7814c0cb mobile: fix saving user key 2024-02-07 23:04:27 +05:00
Abdullah Atta
f07269f4ea web: fix monograph publishing popup 2024-02-07 19:52:15 +05:00
Abdullah Atta
131ed88906 web: ui fixes 2024-02-07 19:35:50 +05:00
Abdullah Atta
d03b46d2c1 web: fix app lock not getting disabled 2024-02-07 19:35:49 +05:00
Ammar Ahmed
848c25adeb mobile: minor fixes 2024-02-07 19:18:10 +05:00
Ammar Ahmed
61f5153c9f mobile: locked notes 2024-02-07 18:52:35 +05:00
Abdullah Atta
86b6c71501 web: fix indexedDB not getting detected 2024-02-07 15:59:48 +05:00
Abdullah Atta
fa11312c46 core: destroy kysely on error 2024-02-07 13:40:17 +05:00
Abdullah Atta
e032852632 crypto: move libsodium & sodium-native to peer deps 2024-02-07 13:40:17 +05:00
Abdullah Atta
9a4ebb5662 global: pin node version to 20 2024-02-07 13:40:17 +05:00
Abdullah Atta
7631612935 web: various fixes 2024-02-07 13:40:17 +05:00
Abdullah Atta
d5ce877d11 common: fix note resolution 2024-02-07 13:40:17 +05:00
Abdullah Atta
446005b8e2 core: fix database migration 2024-02-07 13:40:17 +05:00
Abdullah Atta
dd7765df97 core: disable sync push 2024-02-07 13:40:17 +05:00
Abdullah Atta
b32b3a5af3 web: trigger error boundary on unhandled errors 2024-02-07 13:40:16 +05:00
Abdullah Atta
e3cc211355 web: use safeStorage to encrypt secrets on desktop 2024-02-07 13:40:16 +05:00
Abdullah Atta
41fed6e347 desktop: use better-sqlite3 on desktop for SQLite 2024-02-07 13:40:16 +05:00
Abdullah Atta
8766966c27 desktop: fix autoupdater 2024-02-07 13:40:16 +05:00
Abdullah Atta
a2972ea9e6 desktop: add api to delete files 2024-02-07 13:40:16 +05:00
Abdullah Atta
99a3f9e779 desktop: expose electron's safeStorage api via trpc 2024-02-07 13:40:16 +05:00
Abdullah Atta
33aad3ec3f desktop: enable native modules rebuild during build 2024-02-07 13:40:16 +05:00
Ammar Ahmed
92c6e9c0bb core: return decrypted content on unlock note 2024-02-07 13:31:53 +05:00
Ammar Ahmed
7a0ec7ca11 mobile: applock with pin and password 2024-02-07 13:03:25 +05:00
Ammar Ahmed
fd1e39cee4 mobile: minor fixes 2024-02-07 11:00:24 +05:00
Ammar Ahmed
bc0f92ea5d mobile: sqlite-multiple-ciphers patch 2024-02-06 23:15:40 +05:00
Ammar Ahmed
2b2a9deb71 mobile: refactor 2024-02-06 12:08:41 +05:00
Ammar Ahmed
b2ac5965d4 mobile: fix reorderable side menu 2024-02-06 00:00:14 +05:00
Ammar Ahmed
7397d5bd01 mobile: yearly reminders 2024-02-05 23:00:46 +05:00
Abdullah Atta
7bca839f6d web: add support for yearly reminders 2024-02-05 22:58:50 +05:00
Abdullah Atta
017b466dc0 core: add support for yearly reminders 2024-02-05 22:57:43 +05:00
Ammar Ahmed
b4f7a7112b mobile: stop bg fetch events when toggled off 2024-02-05 22:55:50 +05:00
Ammar Ahmed
47c6b80594 mobile: enable experimental background sync 2024-02-05 22:55:19 +05:00
Abdullah Atta
4e28793c0e web: add support for hiding items from side bar 2024-02-05 22:50:50 +05:00
Abdullah Atta
24f1307955 web: add support for reordering navigation menu items 2024-02-05 22:50:45 +05:00
Ammar Ahmed
56a9b5a5d9 mobile: cleanup 2024-02-05 22:47:54 +05:00
Ammar Ahmed
884fbf65e9 core: add sidebar order settings 2024-02-05 22:47:54 +05:00
Ammar Ahmed
bc03ad8339 mobile: reorder sidebar items 2024-02-05 22:47:43 +05:00
Abdullah Atta
2fca6b2431 web: add support for custom colors 2024-02-05 22:39:23 +05:00
Ammar Ahmed
4067a7f7c7 mobile: allow custom colors 2024-02-05 22:38:55 +05:00
Ammar Ahmed
a9179734d0 mobile: migrate app lock 2024-02-05 22:34:46 +05:00
Abdullah Atta
0f2f2f085b web: remove cbor-x package 2024-02-05 22:31:37 +05:00
Abdullah Atta
b9e04b763f web: add support for at rest encryption and app lock 2024-02-05 22:30:46 +05:00
Ammar Ahmed
eb23224687 mobile: at rest encryption 2024-02-05 22:28:51 +05:00
Ammar Ahmed
70f39fdc2d mobile: simplify tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
3eea4f73e4 core: remove dummy test files 2024-02-05 22:21:14 +05:00
Abdullah Atta
226b9f5892 core: migrate key value storage to sqlite 2024-02-05 22:21:14 +05:00
Ammar Ahmed
47ef642648 mobile: update sheet component 2024-02-05 22:21:14 +05:00
Abdullah Atta
35b7ef9204 core: fix moving nested notebooks to trash 2024-02-05 22:21:14 +05:00
Abdullah Atta
cf024eefd8 global: move item resolution logic to @notesnook/common 2024-02-05 22:21:14 +05:00
Ammar Ahmed
6bba171df0 mobile: add theme switch animation 2024-02-05 22:21:14 +05:00
Abdullah Atta
e52492488f web: fix all type errors 2024-02-05 22:21:14 +05:00
Abdullah Atta
bdaa2a5977 web: fix duplicate imports 2024-02-05 22:21:14 +05:00
Abdullah Atta
cd85cc32e2 core: fix type error 2024-02-05 22:21:14 +05:00
Abdullah Atta
1d7d5fd565 core: add tests for sync merger 2024-02-05 22:21:14 +05:00
Abdullah Atta
4baeee9429 core: migrate vault to a database collection 2024-02-05 22:21:14 +05:00
Ammar Ahmed
667a760c89 mobile: fix exporter and attachment download 2024-02-05 22:21:14 +05:00
Ammar Ahmed
da3ce62f08 global: fix type errors during build 2024-02-05 22:21:14 +05:00
Abdullah Atta
b5adf70853 web: use simple transform stream for export progress 2024-02-05 22:21:14 +05:00
Abdullah Atta
973c6e0d6f web: fix exporting 2024-02-05 22:21:14 +05:00
Ammar Ahmed
ca15a016fd mobile: add patches 2024-02-05 22:21:14 +05:00
Abdullah Atta
5cce557a14 web: update export note snapshots for linux 2024-02-05 22:21:14 +05:00
Abdullah Atta
e81ee6e4ce web: fix reminder tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
339e3444e7 web: refresh notebooks after adding a new notebook 2024-02-05 22:21:14 +05:00
Abdullah Atta
a32df1a009 web: fix force redirect to home on navigating to a color 2024-02-05 22:21:14 +05:00
Abdullah Atta
cb7e438cd2 web: fix crash when sorting tags 2024-02-05 22:21:14 +05:00
Abdullah Atta
5cf5c1a7f7 web: fix crash when removing a notebook 2024-02-05 22:21:14 +05:00
Ammar Ahmed
35ae19acce mobile: sync with latest core changes 2024-02-05 22:21:14 +05:00
Abdullah Atta
e4130556e9 setup: optimize bootstrap script 2024-02-05 22:21:14 +05:00
Abdullah Atta
8dc25e9cac setup: only run npm rebuild if required 2024-02-05 22:21:14 +05:00
Abdullah Atta
90cc89cd6e core: remove unnecessary console.log 2024-02-05 22:21:14 +05:00
Abdullah Atta
4fc7c49ed5 setup: run npm rebuild after bootstrap 2024-02-05 22:21:14 +05:00
Abdullah Atta
387a05e24e setup: ignore scripts during bootstrap 2024-02-05 22:21:14 +05:00
Abdullah Atta
0129513ec6 setup: use 1 thread on CI 2024-02-05 22:21:14 +05:00
Ammar Ahmed
20e5f7babb mobile: fix search to use filtered selector for notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
12dc4caff3 core: prevent recursive sync after pushing items 2024-02-05 22:21:14 +05:00
Abdullah Atta
58bab91bc8 web: simplify sync status 2024-02-05 22:21:14 +05:00
Abdullah Atta
78a25dccdd web: fix previewing trashed notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
1ce15d8e50 core: fix trash 2024-02-05 22:21:14 +05:00
Abdullah Atta
4fb10e3507 web: improve sync test reliability 2024-02-05 22:21:14 +05:00
Abdullah Atta
f5e43f753f core: auto sync not triggered on note update 2024-02-05 22:21:14 +05:00
Abdullah Atta
f088ddf50b web: fix note history tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
60974182e5 web: fix selection logic in list 2024-02-05 22:21:14 +05:00
Abdullah Atta
d1c613c2ec web: set testId on auth input fields 2024-02-05 22:21:14 +05:00
Abdullah Atta
676ffd1116 core: fix note content is not saving 2024-02-05 22:21:14 +05:00
Abdullah Atta
c759350b4c core: fix data is not iterable error on backup restore 2024-02-05 22:21:14 +05:00
Abdullah Atta
930fadeae4 global: update lockfiles 2024-02-05 22:21:14 +05:00
Abdullah Atta
3728179a5b core: add retry if a test fails 2024-02-05 22:21:14 +05:00
Abdullah Atta
0fa9649997 core: fix invalid object-id generation for some timestamps 2024-02-05 22:21:14 +05:00
Abdullah Atta
c8b2b14412 core: fix sync & monographs tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
fe6007bc51 core: fix auto sync starting even when auto sync is disabled 2024-02-05 22:21:14 +05:00
Abdullah Atta
4df150d8a0 core: fix sync tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
d62ee05e4b core: remove unnecessary migration success log 2024-02-05 22:21:14 +05:00
Abdullah Atta
a88d9ee134 core: fix Crypto is not supported on this platform. error during tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
f77a95ca2b core: make all the core tests pass again 2024-02-05 22:21:14 +05:00
Abdullah Atta
5d2c6e0509 web: fix crash when note has no title 2024-02-05 22:21:14 +05:00
Abdullah Atta
8064cecc89 core: set default title if note has no title 2024-02-05 22:21:14 +05:00
Abdullah Atta
bf238ea560 web: update kysely patch 2024-02-05 22:21:14 +05:00
Abdullah Atta
b0c69b5d5a core: run patch-package after npm i 2024-02-05 22:21:14 +05:00
Abdullah Atta
82a0273cac web: run patch-package after npm i 2024-02-05 22:21:14 +05:00
Abdullah Atta
28edcadc22 web: more fixes 2024-02-05 22:21:14 +05:00
Abdullah Atta
6ab5264587 web: fix many type errors 2024-02-05 22:21:14 +05:00
Abdullah Atta
b732337ca2 global: update lockfiles 2024-02-05 22:21:14 +05:00
Abdullah Atta
c164ec4de7 theme: fix tinycolor2 not found error 2024-02-05 22:21:14 +05:00
Abdullah Atta
30f4fb3d9a web: fix notes search in tags, favorites etc 2024-02-05 22:21:14 +05:00
Abdullah Atta
aa12f5975f web: update wa-sqlite 2024-02-05 22:21:14 +05:00
Abdullah Atta
f3c2757b2a web: use memory tempStore for sqlite 2024-02-05 22:21:14 +05:00
Abdullah Atta
f1f67f871c web: add support for exporting sql database 2024-02-05 22:21:14 +05:00
Abdullah Atta
f352b8edec core: fix FTS5 SQLite search 2024-02-05 22:21:14 +05:00
Ammar Ahmed
a37dababe3 mobile: cleanup 2024-02-05 22:21:14 +05:00
Ammar Ahmed
fee13715f4 mobile: fix types 2024-02-05 22:21:14 +05:00
Ammar Ahmed
b046f17644 mobile: refactor zustand stores 2024-02-05 22:21:14 +05:00
Ammar Ahmed
890bb6e057 mobile: bug fixes 2024-02-05 22:21:14 +05:00
Ammar Ahmed
96ad097434 core: add fn to get cache item 2024-02-05 22:21:14 +05:00
Abdullah Atta
74e921930b web: only auto align title & headline 2024-02-05 22:21:14 +05:00
Abdullah Atta
e8eb0ee598 web: getKey -> key 2024-02-05 22:21:14 +05:00
Abdullah Atta
9fc7d644fc web: fix resolved item data misaligned with items 2024-02-05 22:21:14 +05:00
Abdullah Atta
2c6c7f7774 core: fix many issues with virtualized grouping 2024-02-05 22:21:14 +05:00
Ammar Ahmed
e9f5f22601 core: early return when items are empty 2024-02-05 22:21:14 +05:00
Abdullah Atta
2db4bd99b6 web: fix random crashes when scrolling in list 2024-02-05 22:21:14 +05:00
Abdullah Atta
65edcf8e6c web: replace react-virtual with react-virtuouso 2024-02-05 22:21:14 +05:00
Ammar Ahmed
8b927ca94f mobile: fix build ang bugs 2024-02-05 22:21:14 +05:00
Abdullah Atta
81fe67253a web: hide attachments access if user is logged out 2024-02-05 22:21:14 +05:00
Abdullah Atta
4cb418ff75 web: allow core to specify custom sql db name 2024-02-05 22:21:14 +05:00
Abdullah Atta
4cf281da08 web: close idb connection on sqlite connection close 2024-02-05 22:21:14 +05:00
Abdullah Atta
0682345db8 core: clear database on logout 2024-02-05 22:21:14 +05:00
Ammar Ahmed
f5613bd7ae core: add tempStore option to DB 2024-02-05 22:21:14 +05:00
Abdullah Atta
b121f75548 core: migrate items sequentially during sync 2024-02-05 22:21:14 +05:00
Abdullah Atta
1c837f5fc2 core: fix build error 2024-02-05 22:21:14 +05:00
Abdullah Atta
255bda8e61 web: execute only 1 query at a time 2024-02-05 22:21:14 +05:00
Abdullah Atta
c8c1871906 web: fix diff viewer crashing the app 2024-02-05 22:21:14 +05:00
Abdullah Atta
d56916fc33 core: do not stop sync if a conflict occurs 2024-02-05 22:21:14 +05:00
Abdullah Atta
a3fab3b5ca core: allow only 1 transaction at a time 2024-02-05 22:21:14 +05:00
Abdullah Atta
321bd2aa93 core: simplify item key derivation in virtualized grouping 2024-02-05 22:21:14 +05:00
Abdullah Atta
05ccfcd9fb web: improve list item rendering performance 2024-02-05 22:21:14 +05:00
Abdullah Atta
159c4454a3 core: always send realtime sync event 2024-02-05 22:21:14 +05:00
Abdullah Atta
371d7e527f core: fix content date edited not updating 2024-02-05 22:21:14 +05:00
Abdullah Atta
5de4e16b12 core: put items in chunks of 200 2024-02-05 22:21:14 +05:00
Abdullah Atta
bba809cef9 web: set name prop on fields 2024-02-05 22:21:14 +05:00
Abdullah Atta
384db9d27e core: implement sync v2 2024-02-05 22:21:14 +05:00
Ammar Ahmed
b236e7c9b8 mobile: use index based grouping 2024-02-05 22:21:14 +05:00
Abdullah Atta
fceb8a29c7 web: changes to use new grouping api 2024-02-05 22:21:14 +05:00
Abdullah Atta
c2791f2bbf core: implement new high perf indexed based grouping 2024-02-05 22:21:14 +05:00
Ammar Ahmed
4a7d3f5caf mobile: fix share extension in release 2024-02-05 22:21:14 +05:00
Abdullah Atta
e11fea58f6 web: fix router types 2024-02-05 22:21:14 +05:00
Abdullah Atta
ff33ced879 web: enable vite hot reloading 2024-02-05 22:21:14 +05:00
Abdullah Atta
5f7068c376 web: fix minor build issues 2024-02-05 22:21:14 +05:00
Abdullah Atta
c988c510ba web: migrate everything to react@18 2024-02-05 22:21:14 +05:00
Abdullah Atta
4917a888f1 web: fix notebook tests 2024-02-05 22:21:14 +05:00
Ammar Ahmed
3ba5250d59 mobile: share extension cleanup 2024-02-05 22:21:14 +05:00
Ammar Ahmed
d595913722 mobile: cleanup 2024-02-05 22:21:14 +05:00
Ammar Ahmed
1f94c47880 mobile: migrate share extension 2024-02-05 22:21:14 +05:00
Ammar Ahmed
c1b7c66e81 mobile: fix navigation 2024-02-05 22:21:14 +05:00
Abdullah Atta
8712dd286f web: send noteId when decrypting real-time locked content 2024-02-05 22:21:14 +05:00
Abdullah Atta
d836170b6f web: fix tag tests 2024-02-05 22:21:14 +05:00
Ammar Ahmed
0f2122bc41 mobile: pass tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
77a5f83a84 web: fix html export snapshot 2024-02-05 22:21:14 +05:00
Abdullah Atta
f618f97845 core: fix md with frontmatter export 2024-02-05 22:21:14 +05:00
Ammar Ahmed
26bdab05dd core: fix ts error 2024-02-05 22:21:14 +05:00
Ammar Ahmed
ccd078f660 mobile: fix webpack build 2024-02-05 22:21:14 +05:00
Ammar Ahmed
74c4fdac21 mobile: push changes 2024-02-05 22:21:14 +05:00
Abdullah Atta
fc270459b0 web: fix notes tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
a51f84c1f4 core: prevent duplicate colors based on color code 2024-02-05 22:21:14 +05:00
Abdullah Atta
c452e1524d core: fix error on locking notes 2024-02-05 22:21:14 +05:00
Ammar Ahmed
bc428dd11f mobile: push changes 2024-02-05 22:21:14 +05:00
Abdullah Atta
8311db0572 web: reset search results on navigation 2024-02-05 22:21:14 +05:00
Abdullah Atta
26d08615a7 web: reset prepared sql stmt on error 2024-02-05 22:21:14 +05:00
Abdullah Atta
130f39ef8a web: reuse search query 2024-02-05 22:21:14 +05:00
Abdullah Atta
4b83e7319b core: use porter trigram tokenizer for fts 2024-02-05 22:21:14 +05:00
Abdullah Atta
96f45bf61e core: properly escape fts query 2024-02-05 22:21:14 +05:00
Abdullah Atta
ebf20c31c8 web: add support for assigning tags to notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
9c858c0037 core: fix "type" does not exist 2024-02-05 22:21:14 +05:00
Abdullah Atta
16b68aae4f core: fix build error 2024-02-05 22:21:14 +05:00
Ammar Ahmed
3179c0ca2a mobile: migrate to sqlite 2024-02-05 22:21:14 +05:00
Abdullah Atta
905c7cdf31 web: fix note editor properties 2024-02-05 22:21:14 +05:00
Abdullah Atta
dbeb6f7835 common: type is now optional in getFormattedDate 2024-02-05 22:21:14 +05:00
Abdullah Atta
72015fcbeb core: return FilteredSelector from attachments.ofNote 2024-02-05 22:21:14 +05:00
Abdullah Atta
d32f3994e8 web: show notebook & tag shortcuts when assigning notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
eb9f99dc4b web: implement virtualized search for all items 2024-02-05 22:21:14 +05:00
Abdullah Atta
803ef1ca36 web: use lazy loading to load attachments 2024-02-05 22:21:14 +05:00
Abdullah Atta
9b420c200a web: load tag & color title lazily 2024-02-05 22:21:14 +05:00
Abdullah Atta
204d8f37fd web: show all nested notebooks as tree when moving notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
3d202c834a web: add support for nested notebooks 2024-02-05 22:21:14 +05:00
Abdullah Atta
d5adb0c195 web: use new virtualized grouping for reminders 2024-02-05 22:21:14 +05:00
Abdullah Atta
fd4174d8d5 web: use new virtualized grouping for trash 2024-02-05 22:21:14 +05:00
Abdullah Atta
cb376a6144 web: use new virtualized grouping for monographs 2024-02-05 22:21:14 +05:00
Abdullah Atta
254db0fb49 core: add support for fetching trash as grouped 2024-02-05 22:21:14 +05:00
Abdullah Atta
34ec5c0eaa web: install missing @hazae41/foras 2024-02-05 22:21:14 +05:00
Abdullah Atta
9b31bb8598 core: fix attachment extraction for locked notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
9f33de94cb global: update lockfiles 2024-02-05 22:21:14 +05:00
Abdullah Atta
f3cbcf47a5 web: start initial migration to sqlite 2024-02-05 22:21:14 +05:00
Abdullah Atta
ec7e81d5e8 ui: add support for lazily loading menu items 2024-02-05 22:21:14 +05:00
Abdullah Atta
200547415c common: remove total-notes utility 2024-02-05 22:21:14 +05:00
Abdullah Atta
4322559680 core: changes according to client needs 2024-02-05 22:21:14 +05:00
Abdullah Atta
de187ec433 core: fix database migration 2024-02-05 22:21:14 +05:00
Abdullah Atta
9e9a25861d core: add support for incremental grouping 2024-02-05 22:21:14 +05:00
Abdullah Atta
3ecf9eab3b core: fix size of nanoid in benchmarks 2024-02-05 22:21:14 +05:00
Abdullah Atta
a0f8666b7a core: optimize bson objectid generation & add benchmarks 2024-02-05 22:21:14 +05:00
Abdullah Atta
7cf8895b4d core: fix monographs 2024-02-05 22:21:14 +05:00
Abdullah Atta
84b786b5a3 core: fix sync tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
b28ab28f57 core: remove temporary sqlite db 2024-02-05 22:21:14 +05:00
Abdullah Atta
70ef954de5 core: migrate everything to sqlite 2024-02-05 22:21:14 +05:00
Abdullah Atta
608761ee0d core: migrate all database writes to sqlite 2024-02-05 22:21:14 +05:00
Abdullah Atta
a70b309df8 global: update lockfiles 2024-02-05 22:21:14 +05:00
Abdullah Atta
58ef5050c4 core: remove outbox 2024-02-05 22:21:14 +05:00
Abdullah Atta
c1594c1687 core: convert everything else to typescript 2024-02-05 22:21:14 +05:00
Abdullah Atta
bcb8c14abf core: remove unnecessary sync tests 2024-02-05 22:21:14 +05:00
Abdullah Atta
adb325af4d core: migrate monographs to typescript 2024-02-05 22:21:14 +05:00
Abdullah Atta
315961eaae core: make migration tests less flaky 2024-02-05 22:21:14 +05:00
Abdullah Atta
65d3ef87d2 core: add tests for migrations 2024-02-05 22:21:14 +05:00
Abdullah Atta
d4d9ebf378 core: migrate settings to its own collection 2024-02-05 22:21:14 +05:00
Abdullah Atta
d90fa07ed8 core: get rid of topics; move to sub notebooks 2024-02-05 22:21:14 +05:00
Abdullah Atta
adae1b6187 core: migrate attachments to use relations for linking to notes 2024-02-05 22:21:14 +05:00
Abdullah Atta
b40f52b12b core: minor updates and fixes 2024-02-05 22:21:14 +05:00
Abdullah Atta
0b8e0f9848 core: fix types & syncing issues 2024-02-05 22:21:14 +05:00
ammarahm-ed
8cbb7f88d6 mobile: pass all tests 2024-02-05 22:21:14 +05:00
ammarahm-ed
17201979ba mobile: fix filesystem not loaded 2024-02-05 22:21:14 +05:00
ammarahm-ed
0bcd162559 mobile: fix navigating to tags 2024-02-05 22:21:14 +05:00
ammarahm-ed
fe2fc26bc0 mobile: migrate to typescript 2024-02-05 22:21:14 +05:00
ammarahm-ed
7b13d064e7 common: fix ts errors 2024-02-05 22:21:14 +05:00
Abdullah Atta
cad18cb9e8 mobile: use new types from core 2024-02-05 22:21:14 +05:00
Abdullah Atta
fa45ead4cb web: use new types from core 2024-02-05 22:21:14 +05:00
Abdullah Atta
e781273345 core: fix more typings 2024-02-05 22:21:14 +05:00
Abdullah Atta
d28179e0cd core: fix typing errors 2024-02-05 22:21:14 +05:00
Abdullah Atta
ee6a515683 core: convert almost everything to typescript 2024-02-05 22:21:14 +05:00
Abdullah Atta
37263304fa core: fix attachment mime type on read 2024-02-05 22:21:14 +05:00
Ammar Ahmed
e96ba80583 mobile: fix lint errors 2024-02-02 18:46:30 +05:00
Abdullah Atta
cfd80d2008 ci: update node version to 20 2024-02-02 18:35:42 +05:00
Abdullah Atta
288226a3af config: update nx 2024-02-02 18:32:53 +05:00
Abdullah Atta
4d2c9cfd4e desktop: set release channel to beta 2024-02-02 18:03:38 +05:00
Ammar Ahmed
fe52ce38e1 mobile: update ios version number 2024-02-02 15:51:05 +05:00
Ammar Ahmed
46acdf4279 ci: update workflow name
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-02 15:39:46 +05:00
Ammar Ahmed
1f698280df mobile: v3 beta release 2024-02-02 15:32:11 +05:00
Ammar Ahmed
94babf2a97 ci: update iOS workflow
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-02 15:12:47 +05:00
Ammar Ahmed
b19be80738 Update android.publish.internal.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-02 15:11:54 +05:00
Ammar Ahmed
f77746cf0e ci: android.publish.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-02-02 15:10:38 +05:00
Abdullah Atta
d3c033faf2 desktop: do not set teamId for notarization 2024-01-31 16:34:08 +05:00
Abdullah Atta
b9f22e1dd0 ci: APPLE_API_KEY_ISSUER > APPLE_API_ISSUER 2024-01-31 16:26:10 +05:00
Abdullah Atta
b0dd1c2095 desktop: fix unknown property 'notarize' error 2024-01-31 16:14:43 +05:00
Abdullah Atta
0beffd5165 desktop: use official electron builder notarization 2024-01-31 15:45:37 +05:00
Abdullah Atta
a1c0a834ee ci: update {upload,download}-artifact to v4 2024-01-31 15:34:10 +05:00
Abdullah Atta
27dc19cb58 ci: get rid of you may only define one of branches and branches-ignore for a single event 2024-01-31 15:20:21 +05:00
Abdullah Atta
896884ff59 ci: use m1 for building on mac 2024-01-31 15:19:10 +05:00
Abdullah Atta
97cb724bc1 ci: use wrangler directly to publish to cf 2024-01-31 14:52:18 +05:00
Abdullah Atta
f08e372bcf desktop: use app version from autoUpdater 2024-01-31 14:46:42 +05:00
Abdullah Atta
0aecb2d27a ci: update cloudflare action 2024-01-31 14:34:24 +05:00
Abdullah Atta
3735d9ee18 desktop: ready beta for deployment 2024-01-31 14:31:42 +05:00
Abdullah Atta
3d4264257e ci: deploy to correct cf pages project
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-01-31 12:53:37 +05:00
Abdullah Atta
96b97c4bec core: turn off sync push by default 2024-01-31 12:47:53 +05:00
953 changed files with 73674 additions and 116526 deletions

View File

@@ -147,10 +147,8 @@ jobs:
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
- name: Upload sourcemaps
uses: actions/upload-artifact@v4
- name: Upload signed aab to Github
uses: actions/upload-artifact@v2
with:
name: sourcemaps
path: |
apps/mobile/native/android/app/build/**/*.map
packages/editor-mobile/sourcemaps/*.map
name: Notesnook.aab
path: ${{steps.sign_app.outputs.signedReleaseFile}}

View File

@@ -133,6 +133,7 @@ jobs:
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Create release draft on Github
uses: softprops/action-gh-release@v1
with:
@@ -147,10 +148,8 @@ jobs:
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
- name: Upload sourcemaps
uses: actions/upload-artifact@v4
- name: Upload signed aab to Github
uses: actions/upload-artifact@v2
with:
name: sourcemaps
path: |
apps/mobile/native/android/app/build/**/*.map
packages/editor-mobile/sourcemaps/*.map
name: Notesnook.aab
path: ${{steps.sign_app.outputs.signedReleaseFile}}

View File

@@ -97,9 +97,6 @@ jobs:
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install setuptools
run: brew install python-setuptools
- name: Setup notarization
run: |
mkdir -p ~/private_keys/
@@ -147,7 +144,7 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
run: |
npx nx run release --project @notesnook/desktop -- --variant=mas
yarn electron-builder --mac mas --universal -p never
npx electron-builder --mac mas --universal -p never
working-directory: ./apps/desktop
- name: Build zip and dmg
@@ -161,9 +158,9 @@ jobs:
run: |
npx nx run release --project @notesnook/desktop
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
npx electron-builder --mac zip dmg --arm64 --x64 -p always
else
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
npx electron-builder --mac zip dmg --arm64 --x64 -p never
fi
working-directory: ./apps/desktop
@@ -219,7 +216,7 @@ jobs:
- name: Build snap
if: inputs.publish-snap
run: |
yarn electron-builder --linux snap:x64 -p never
npx electron-builder --linux snap:x64 -p never
working-directory: ./apps/desktop
- name: Build AppImage
@@ -227,9 +224,9 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
else
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
fi
working-directory: ./apps/desktop
@@ -254,8 +251,8 @@ jobs:
with:
dotnet-version: 6.0.x
- name: Install TrustedSigning
run: Install-Module -Name TrustedSigning -RequiredVersion 0.3.15 -Force -Repository PSGallery
- name: Install AzureCodeSigning
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
shell: pwsh
- name: Setup Node

View File

@@ -89,19 +89,16 @@ jobs:
- name: CCache Stats After Build
run: ccache -sv
# - name: "Upload app to TestFlight"
# uses: apple-actions/upload-testflight-build@v1
# with:
# app-path: Notesnook.ipa
# issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
# api-key-id: ${{ secrets.API_KEY_ID }}
# api-private-key: ${{ secrets.API_KEY }}
- name: "Upload app to TestFlight"
uses: apple-actions/upload-testflight-build@v1
with:
app-path: Notesnook.ipa
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
api-key-id: ${{ secrets.API_KEY_ID }}
api-private-key: ${{ secrets.API_KEY }}
- name: Upload Notesnook.ipa to Github
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v2
with:
name: Notesnook.zip
path: |
Notesnook.ipa
apps/mobile/native/ios/**/*.map
packages/editor-mobile/sourcemaps/*.map
name: Notesnook.ipa
path: Notesnook.ipa

69
.github/workflows/web.beta.publish.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Publish @notesnook/web Beta
on:
workflow_dispatch:
push:
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
pull_request:
types:
- closed
- ready_for_review
- reopened
- opened
- synchronize
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate beta build
run: npm run build:beta:web
- name: Publish to Cloudflare Pages
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
id: pages
with:
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
cloudflare-project-name: notesnook-beta-app
directory: ./apps/web/build
github-token: ${{ secrets.GITHUB_TOKEN }}
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}

View File

@@ -40,7 +40,7 @@ jobs:
run: npm run build:test:web
- name: Archive build artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v2
with:
name: build
path: apps/web/build/**/*
@@ -56,7 +56,7 @@ jobs:
uses: actions/checkout@v3
- name: Download build
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: build
path: ./apps/web/build
@@ -67,7 +67,7 @@ jobs:
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm install -D @playwright/test otplib dayjs @zip.js/zip.js
npm install -D @playwright/test otplib
- name: Validate build extraction
run: ls -ld ./apps/web/build/index.html
@@ -88,7 +88,7 @@ jobs:
working-directory: apps/web
- name: Upload test results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-results-${{ matrix.shard }}

View File

@@ -0,0 +1,32 @@
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
View File

@@ -10,4 +10,4 @@ nx-cloud.env
.nyc_output
site
node_modules.backup
.nx
.nx/cache

View File

@@ -40,35 +40,12 @@ Once you are inside the `./notesnook` directory, run the preparation step:
npm install
```
Now you can finally start the desktop app for development:
Now you can finally start the desktop app:
```bash
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
### The tech stack

View File

@@ -1,4 +1,3 @@
owner: streetwriters
repo: notesnook
provider: github
updaterCacheDirName: Notesnook

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.13",
"version": "3.0.2-beta",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -14,11 +14,11 @@
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"better-sqlite3-multiple-ciphers": "^9.4.0",
"electron-trpc": "0.5.2",
"electron-updater": "^6.2.1",
"electron-updater": "6.1.4",
"icojs": "^0.17.1",
"sodium-native": "^4.1.1",
"sodium-native": "^4.0.6",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
@@ -27,14 +27,11 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.23.0",
"kysely": "^0.27.3",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",
"electron": "^28.2.1",
"electron-builder": "^24.9.1",
"esbuild": "^0.20.0",
"tree-kill": "^1.2.2",
"undici": "^6.14.1"
"undici": "^6.6.1"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
@@ -58,8 +55,8 @@
"productName": "Notesnook",
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
"artifactName": "notesnook_${os}_${arch}.${ext}",
"generateUpdatesFilesForAllChannels": true,
"asar": false,
"generateUpdatesFilesForAllChannels": true,
"files": [
"!*.chunk.js.map",
"!*.chunk.js.LICENSE.txt",
@@ -222,7 +219,8 @@
{
"provider": "github",
"repo": "notesnook",
"owner": "streetwriters"
"owner": "streetwriters",
"channel": "beta"
}
]
}

View File

@@ -1,46 +0,0 @@
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
})
}

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import path from "path";
import fs, { readFile, writeFile } from "fs/promises";
import { existsSync, readFileSync } from "fs";
import { existsSync } from "fs";
import yargs from "yargs-parser";
import os from "os";
import * as childProcess from "child_process";
@@ -28,29 +28,23 @@ import { fileURLToPath } from "url";
const args = yargs(process.argv);
const __filename = fileURLToPath(import.meta.url);
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"));
await fs.rm("./build/", { force: true, recursive: true });
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
await exec(
"yarn nx build:desktop @notesnook/web",
path.join(__dirname, "..", "..", "..")
);
await exec(`cd ${webAppPath} && npm run build:desktop`);
}
// temporary until there's support for prebuilt binaries for linux ARM
if (os.platform() === "linux") await patchBetterSQLite3();
// temporary until there's support for prebuilt binaries for windows ARM & linux ARM
await patchBetterSQLite3();
// if (os.platform() === "win32")
// await exec(
// `npx prebuildify --arch=arm64 --strip -t electron@${packageJson.devDependencies.electron}`,
// path.join(__dirname, "..", "node_modules", "sodium-native")
// );
if (os.platform() === "win32")
await exec(
"npx prebuildify --arch=arm64 --strip",
path.join(__dirname, "..", "node_modules", "sodium-native")
);
await fs.cp(path.join(webAppPath, "build"), "build", {
recursive: true,
@@ -58,19 +52,19 @@ await fs.cp(path.join(webAppPath, "build"), "build", {
});
if (args.variant === "mas") {
await exec(`yarn run bundle:mas`);
await exec(`npm run bundle:mas`);
} else {
await exec(`yarn run bundle`);
await exec(`npm run bundle`);
}
await exec(`yarn tsc`);
await exec(`npx tsc`);
if (args.run) {
await exec(`yarn electron-builder --dir --x64`);
await exec(`npx electron-builder --dir --x64`);
if (process.platform === "win32") {
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
} else if (process.platform === "darwin") {
await exec(`./output/mac/Notesnook.app/Contents/MacOS/Notesnook`);
await exec(`./output/darwin-unpacked/Notesnook`);
} else {
await exec(`./output/linux-unpacked/Notesnook`);
}
@@ -94,7 +88,7 @@ async function patchBetterSQLite3() {
);
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
json.version = "9.5.1";
json.version = "9.4.1";
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
json.repository.url =
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";

View File

@@ -53,11 +53,11 @@ async function onChange(first) {
if (first) {
await fs.rm("./build/", { force: true, recursive: true });
await exec("yarn electron-builder install-app-deps");
await exec("npx electron-rebuild");
}
await exec(`yarn run bundle`);
execAsync(`yarn`, [`tsc`]);
await exec(`npm run bundle`);
execAsync(`npx`, [`tsc`]);
if (await isBundleSame()) {
console.log("Bundle is same. Doing nothing.");
@@ -66,7 +66,7 @@ async function onChange(first) {
if (first) {
await spawnAndWaitUntil(
["yarn", "nx", "start:desktop", "@notesnook/web"],
["npx", "nx", "start:desktop", "@notesnook/web"],
path.join(__dirname, "..", "..", ".."),
(data) => data.includes("Network: use --host to expose")
);
@@ -78,7 +78,7 @@ async function onChange(first) {
}
execAsync(
"yarn",
"npx",
["electron", path.join("build", "electron.js")],
true,
cleanup
@@ -169,10 +169,9 @@ async function cleanup() {
}
async function isBundleSame() {
const bundle = Buffer.concat([
await fs.readFile(path.join(__dirname, "..", "build", "electron.js")),
await fs.readFile(path.join(__dirname, "..", "build", "preload.js"))
]);
const bundle = await fs.readFile(
path.join(__dirname, "..", "build", "electron.js")
);
const hashSum = crypto.createHash("sha256");
hashSum.update(bundle);

View File

@@ -33,7 +33,7 @@ module.exports = async function (configuration) {
const DescriptionUrl = "https://notesnook.com/";
const FilesCatalog = createCatalog(configuration.path);
const command = `Invoke-TrustedSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
console.debug("Signing", configuration.path, "using command", command);

View File

@@ -24,7 +24,6 @@ import { spellCheckerRouter } from "./spell-checker";
import { updaterRouter } from "./updater";
import { bridgeRouter } from "./bridge";
import { safeStorageRouter } from "./safe-storage";
import { windowRouter } from "./window";
const t = initTRPC.create();
@@ -34,8 +33,7 @@ export const router = t.router({
spellChecker: spellCheckerRouter,
updater: updaterRouter,
bridge: bridgeRouter,
safeStorage: safeStorageRouter,
window: windowRouter
safeStorage: safeStorageRouter
});
export const api = router.createCaller({});

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { initTRPC } from "@trpc/server";
import { z } from "zod";
import { app, dialog, nativeTheme, Notification, shell } from "electron";
import { dialog, nativeTheme, Notification, shell } from "electron";
import { AutoLaunch } from "../utils/autolaunch";
import { config, DesktopIntegration } from "../utils/config";
import { bringToFront } from "../utils/bring-to-front";
@@ -32,7 +32,6 @@ import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
const t = initTRPC.create();
@@ -56,23 +55,6 @@ export const osIntegrationRouter = t.router({
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),
setPrivacyMode: t.procedure
.input(z.object({ enabled: z.boolean() }))
@@ -152,10 +134,6 @@ export const osIntegrationRouter = t.router({
await rm(input);
}),
restart: t.procedure.query(() => {
app.relaunch();
app.exit();
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {
@@ -184,45 +162,16 @@ export const osIntegrationRouter = t.router({
}),
bringToFront: t.procedure.query(() => bringToFront()),
changeTheme: t.procedure
.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);
}
),
.input(Theme)
.mutation(({ input }) => setTheme(input)),
onThemeChanged: t.procedure.subscription(() =>
observable<"dark" | "light">((emit) => {
const updated = () => {
nativeTheme.on("updated", () => {
if (getTheme() === "system") {
emit.next(nativeTheme.shouldUseDarkColors ? "dark" : "light");
}
};
nativeTheme.on("updated", updated);
return () => {
nativeTheme.off("updated", updated);
};
});
})
)
});

View File

@@ -23,22 +23,16 @@ import { z } from "zod";
const t = initTRPC.create();
export const safeStorageRouter = t.router({
isEncryptionAvailable: t.procedure.query(() => {
return (
!process.env.PORTABLE_EXECUTABLE_DIR &&
safeStorage.isEncryptionAvailable()
);
}),
/**
* Takes a string and returns an encrypted base64 string
*/
encryptString: t.procedure.input(z.string()).query(({ input }) => {
encryptString: t.procedure.input(z.string()).query(async ({ input }) => {
return safeStorage.encryptString(input).toString("base64");
}),
/**
* Takes an encrypted base64 string and returns a string
*/
decryptString: t.procedure.input(z.string()).query(({ input }) => {
decryptString: t.procedure.input(z.string()).query(async ({ input }) => {
return safeStorage.decryptString(Buffer.from(input, "base64"));
})
});

View File

@@ -118,13 +118,5 @@ export const spellCheckerRouter = t.router({
.mutation(({ input: { enabled } }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(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
);
})
})
});

View File

@@ -1,143 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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
});
}
}

View File

@@ -28,20 +28,16 @@ type UpdateInfo = { version: string };
type Progress = { percent: number };
const t = initTRPC.create();
let cancellationToken: CancellationToken | undefined = undefined;
export const updaterRouter = t.router({
autoUpdates: t.procedure.query(() => config.automaticUpdates),
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
download: t.procedure.query(async () => {
if (cancellationToken) return;
cancellationToken = new CancellationToken();
await autoUpdater
.downloadUpdate(cancellationToken)
.finally(() => (cancellationToken = undefined));
const cancellationToken = new CancellationToken();
await autoUpdater.downloadUpdate(cancellationToken);
}),
check: t.procedure.query(async () => {
await autoUpdater.checkForUpdates().catch(console.error);
await autoUpdater.checkForUpdates();
}),
toggleAutoUpdates: t.procedure
@@ -61,18 +57,7 @@ export const updaterRouter = t.router({
"update-not-available"
),
onAvailable: createSubscription<"update-available", UpdateInfo>(
"update-available",
() => {
if (!config.automaticUpdates) return false;
autoUpdater.emit("download-progress", {
bytesPerSecond: 0,
delta: 0,
percent: 0,
total: 100,
transferred: 0
});
return true;
}
"update-available"
),
onError: createSubscription("error")
});
@@ -80,14 +65,12 @@ export const updaterRouter = t.router({
function createSubscription<
TName extends keyof AppUpdaterEvents,
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
>(eventName: TName, handler?: (args: TReturnType) => boolean) {
>(eventName: TName) {
return t.procedure.subscription(() => {
return observable<TReturnType>((emit) => {
const listener: AppUpdaterEvents[TName] = (...args: any[]) => {
if (handler?.(args[0])) return;
emit.next(args[0]);
};
autoUpdater.removeAllListeners(eventName);
autoUpdater.addListener(eventName, listener);
return () => {
autoUpdater.removeListener(eventName, listener);

View File

@@ -1,83 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { 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
);
};
});
})
});

View File

@@ -21,4 +21,3 @@ export * from "./constants";
export type { AppRouter } from "./api";
export { type UpdateInfo } from "builder-util-runtime";
export { type DesktopIntegration } from "./utils/config";
export { SQLite } from "./api/sqlite-kysely.js";

View File

@@ -35,7 +35,6 @@ import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -83,28 +82,10 @@ async function createWindow() {
size: 512,
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: {
zoomFactor: config.zoomFactor,
nodeIntegration: true,
contextIsolation: false,
nodeIntegrationInWorker: true,
spellcheck: config.isSpellCheckerEnabled,
preload: __dirname + "/preload.js"
}
@@ -131,7 +112,6 @@ async function createWindow() {
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
);
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
mainWindow.once("closed", () => {
globalThis.window = null;
@@ -141,7 +121,7 @@ async function createWindow() {
setupJumplist();
if (isDevelopment())
mainWindow.webContents.openDevTools({ mode: "bottom", activate: true });
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
@@ -157,9 +137,6 @@ async function createWindow() {
app.once("ready", async () => {
console.info("App ready. Opening window.");
if (config.customDns) enableCustomDns();
else disableCustomDns();
if (!isDevelopment()) registerProtocol();
await createWindow();
configureAutoUpdater();

View File

@@ -23,11 +23,13 @@ import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
import type { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
import { platform } from "os";
import sqlite3, { Database } from "better-sqlite3-multiple-ciphers";
declare global {
var os: () => "mas" | ReturnType<typeof platform>;
var electronTRPC: RendererGlobalElectronTRPC;
var NativeNNCrypto: (new () => NNCrypto) | undefined;
var createSQLite3Database: (filename: string) => Database;
}
process.once("loaded", async () => {
@@ -41,4 +43,5 @@ process.once("loaded", async () => {
});
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
globalThis.createSQLite3Database = (filename) => sqlite3(filename);
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());

View File

@@ -25,11 +25,7 @@ Type=Application
Version=${app.getVersion()}
Name=${app.getName()}
Comment=${app.getName()} startup script
Exec=${
process.env.APPIMAGE
? `${process.env.APPIMAGE}${hidden ? " --hidden" : ""}`
: `${process.execPath}${hidden ? " --hidden" : ""}`
}
Exec=${process.execPath}${hidden ? " --hidden" : ""}
StartupNotify=false
Terminal=false`;

View File

@@ -35,7 +35,6 @@ async function configureAutoUpdater() {
autoUpdater.allowDowngrade = false;
autoUpdater.allowPrerelease = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.disableWebInstaller = true;
}
export { configureAutoUpdater };

View File

@@ -25,8 +25,7 @@ export const DesktopIntegration = z.object({
autoStart: z.boolean().optional(),
startMinimized: z.boolean().optional(),
minimizeToSystemTray: z.boolean().optional(),
closeToSystemTray: z.boolean().optional(),
nativeTitlebar: z.boolean().optional()
closeToSystemTray: z.boolean().optional()
});
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
@@ -36,20 +35,13 @@ export const config = {
autoStart: false,
startMinimized: false,
minimizeToSystemTray: false,
closeToSystemTray: false,
nativeTitlebar: false
closeToSystemTray: false
},
privacyMode: false,
isSpellCheckerEnabled: true,
zoomFactor: 1,
theme: nativeTheme.themeSource,
automaticUpdates: true,
proxyRules: "",
customDns: true,
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
windowControlsIconColor:
nativeTheme.themeSource === "dark" ? "#ffffff" : "#000000"
automaticUpdates: true
};
type ConfigKey = keyof typeof config;

View File

@@ -1,37 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { 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
});
}

View File

@@ -131,17 +131,6 @@ 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)
menu.append(
new MenuItem({

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { net, protocol } from "electron";
import { protocol } from "electron";
import { isDevelopment } from "./index";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
@@ -62,7 +62,26 @@ function registerProtocol() {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
return net.fetch(request, { bypassCustomProtocolHandlers: true });
if (request.headers.has("X-Content-Length")) {
request.headers.set(
"Content-Length",
request.headers.get("X-Content-Length") || "0"
);
request.headers.delete("X-Content-Length");
}
const headers = Object.fromEntries(request.headers.entries());
return await fetch(request.url, {
signal: request.signal,
mode: request.mode,
headers,
method: request.method,
body: request.body,
credentials: request.credentials,
referrer: (request as any).referrer,
duplex: "half",
redirect: "manual"
});
}
});
console.info(`${SCHEME} protocol inteception "successful"`);

View File

@@ -41,10 +41,7 @@ function setTheme(theme: Theme) {
}
function getBackgroundColor() {
return (
config.backgroundColor ||
(nativeTheme.shouldUseDarkColors ? "#0f0f0f" : "#ffffff")
);
return nativeTheme.shouldUseDarkColors ? "#0f0f0f" : "#ffffff";
}
function getSystemTheme() {

View File

@@ -16,6 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "@azure/core-asynciterator-polyfill";
import "react-native-gesture-handler";
import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
@@ -40,25 +42,42 @@ import { useUserStore } from "./stores/use-user-store";
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(false);
const { appLockEnabled, appLockMode } = SettingsService.get();
if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
// How app lock works
// 1. User goes to settings and setup app lock with a Pin/Password.
// 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 = () => {
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
const init = useAppEvents();
useEffect(() => {
SettingsService.onFirstLaunch();
const { appLockEnabled, appLockMode } = SettingsService.get();
if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
init();
setTimeout(async () => {
SettingsService.onFirstLaunch();
await Notifications.get();
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(true);
}
TipManager.init();
}, 100);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<View
style={{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -25,7 +25,6 @@ import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
import { generateSecureRandom } from "react-native-securerandom";
import { DatabaseLogger } from ".";
import { MMKV } from "./mmkv";
import { ToastManager } from "../../services/event-manager";
// 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
@@ -43,10 +42,8 @@ export const CipherStorage = new MMKVLoader()
const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
const NOTESNOOK_APPLOCK_KEY_SALT = "kBwr1Kre86ebOZ8ThLu2OA";
const NOTESNOOK_DB_KEY_SALT = "SNuzOcEK3amoqL0WvPeKqw";
const NOTESNOOK_APPLOCK_KEY_SALT = "notesnook_applock_key_salt";
const NOTESNOOK_DB_KEY_SALT = "notesnook_database_key";
const DB_KEY_CIPHER = "databaseKeyCipher";
const USER_KEY_CIPHER = "userKeyCipher";
const APPLOCK_CIPHER = "applockCipher";
@@ -117,7 +114,9 @@ export async function setAppLockVerificationCipher(appLockPassword) {
NOTESNOOK_APPLOCK_KEY_SALT
);
const encrypted = await encrypt(appLockCredentials, generatePassword());
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
DatabaseLogger.info("setAppLockVerificationCipher");
} catch (e) {
DatabaseLogger.error(e);
@@ -133,9 +132,12 @@ export async function validateAppLockPassword(appLockPassword) {
try {
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
if (!appLockCipher) return true;
const key = await Sodium.deriveKey(appLockPassword, appLockCipher.salt);
const decrypted = await decrypt(key, appLockCipher);
const decrypted = await decrypt(
{
password: appLockPassword
},
appLockCipher
);
DatabaseLogger.info(
`validateAppLockPassword: ${typeof decrypted === "string"}`
);
@@ -165,66 +167,54 @@ export async function getDatabaseKey(appLockPassword) {
);
DatabaseLogger.info("Getting database key from cipher");
DB_KEY = databaseKey;
return databaseKey;
}
if (!DB_KEY) {
const hasKey = await Keychain.hasInternetCredentials(
KEYCHAIN_SERVER_DBKEY
);
if (hasKey) {
let credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
KEYSTORE_CONFIG
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = credentials.password;
}
}
if (!DB_KEY) {
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
DB_KEY = derivedDatabaseKey.key;
await Keychain.setInternetCredentials(
const hasKey = await Keychain.hasInternetCredentials(KEYCHAIN_SERVER_DBKEY);
if (hasKey) {
let credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
DB_KEY,
KEYSTORE_CONFIG
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = credentials.password;
return credentials.password;
}
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
derivedDatabaseKey.key,
KEYSTORE_CONFIG
);
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
);
if (userKeyCredentials) {
const userKeyCipher = await encrypt(
{
key: derivedDatabaseKey.key
},
userKeyCredentials.password
);
if (userKeyCredentials) {
const userKeyCipher = await encrypt(
{
key: DB_KEY,
salt: NOTESNOOK_DB_KEY_SALT
},
userKeyCredentials.password
);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
}
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
DatabaseLogger.info("Migrated user credentials to cipher storage");
}
return DB_KEY;
DB_KEY = derivedDatabaseKey.key;
return derivedDatabaseKey.key;
} catch (e) {
ToastManager.error(e, "Error getting database key");
console.log(e, "error");
DatabaseLogger.error(e);
return null;
}
@@ -235,12 +225,11 @@ export async function deriveCryptoKey(data) {
let credentials = await Sodium.deriveKey(data.password, data.salt);
const userKeyCipher = await encrypt(
{
key: await getDatabaseKey(),
salt: NOTESNOOK_DB_KEY_SALT
key: await getDatabaseKey()
},
credentials.key
);
DatabaseLogger.info("User key stored: ", !!userKeyCipher);
DatabaseLogger.info("User key stored: ", !!userKeyCipher, credentials);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
@@ -261,15 +250,15 @@ export async function getCryptoKey(_name) {
const key = await decrypt(
{
key: await getDatabaseKey(),
salt: keyCipher.salt
key: await getDatabaseKey()
},
keyCipher
);
DatabaseLogger.info("User key decrypted: ", !!key);
return key;
} catch (e) {
console.log("getCryptoKey", e);
DatabaseLogger.error(e);
}
}
@@ -312,8 +301,6 @@ export async function decrypt(password, data) {
return undefined;
let _data = { ...data };
_data.output = "plain";
if (!password.salt) password.salt = data.salt;
return await Sodium.decrypt(password, _data);
}
@@ -326,11 +313,6 @@ export async function decryptMulti(password, data) {
d.output = "plain";
return d;
});
if (data.length && !password.salt) {
password.salt = data[0].salt;
}
return await Sodium.decryptMulti(password, data);
}

View File

@@ -85,10 +85,4 @@ export async function setupDatabase(password) {
}
export const db = database;
let DatabaseLogger = dbLogger.scope(Platform.OS);
const setLogger = () => {
DatabaseLogger = dbLogger.scope(Platform.OS);
};
export { DatabaseLogger, setLogger };
export const DatabaseLogger = dbLogger;

View File

@@ -16,30 +16,14 @@ 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 { MMKVLoader } from "react-native-mmkv-storage";
import { initialize } from "@notesnook/core/dist/logger";
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
import { Platform } from "react-native";
import { setLogger } from ".";
import { RNSqliteDriver } from "./sqlite.kysely";
import { KV } from "./storage";
let loggerLoaded = false;
const initializeLogger = async () => {
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;
};
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
export { initializeLogger };
initialize(new KV(LoggerStorage));
export { LoggerStorage };

View File

@@ -16,61 +16,20 @@ 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 { Dimensions, Image, Platform } from "react-native";
import { Dimensions, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "react-native-blob-util";
console.log("hello world");
/**
* Scale down & compress images to screen width
* for loading in editor.
* @returns
*/
export async function compressToBase64(path, type) {
const { width: screenWidth, scale } = Dimensions.get("window");
console.log("COMPRESSING TO BASE64...");
return new Promise((resolve) => {
console.log(path, "image path...");
Image.getSize(path, async (width) => {
console.log("image width", width);
const response = await ImageResizer.createResizedImage(
path,
screenWidth * scale,
99999,
type,
width <= 1920 ? 100 : 90,
0,
undefined,
true,
{
mode: "contain",
onlyScaleDown: true
}
);
const base64 = await RNFetchBlob.fs.readFile(
Platform.OS === "ios"
? response.uri?.replace("file://", "")
: response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs
.unlink(response.uri.replace("file://", ""))
.catch(console.log);
resolve(base64);
});
});
}
export async function compressToFile(path, type) {
const { width, scale } = Dimensions.get("window");
const response = await ImageResizer.createResizedImage(
path,
1920,
99999,
width * scale,
9999,
type,
80,
0,
@@ -81,6 +40,11 @@ export async function compressToFile(path, type) {
onlyScaleDown: true
}
);
const base64 = await RNFetchBlob.fs.readFile(
Platform.OS === "ios" ? response.uri?.replace("file://", "") : response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
return response.uri;
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(console.log);
return base64;
}

View File

@@ -31,7 +31,7 @@ import RNFetchBlob from "react-native-blob-util";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { ToastManager, presentSheet } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { DatabaseLogger, db } from "../database";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir, exists } from "./io";
@@ -84,10 +84,8 @@ export async function downloadAttachments(
Platform.OS === "ios"
? `${outputFolder}/notesnook-attachments-${Date.now()}.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.mkdir(zipSourceFolder);
for (let i = 0; i < attachments.length; i++) {
@@ -129,7 +127,7 @@ export async function downloadAttachments(
status: FileDownloadStatus.Fail,
reason: e
});
ToastManager.error(e, "Error downloading attachment");
console.log("Error downloading attachment", e);
}
}
if (canceled.current) {
@@ -166,7 +164,7 @@ export async function downloadAttachments(
} catch (e) {
releasePermissions(outputFolder);
sub?.remove();
ToastManager.error(e, "Error zipping attachments");
console.log("Error zipping attachments", e);
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
@@ -194,7 +192,7 @@ export default async function downloadAttachment(
let attachment = await db.attachments.attachment(hash);
if (!attachment) {
DatabaseLogger.log("Attachment not found");
console.log("attachment not found");
return;
}
@@ -209,16 +207,19 @@ export default async function downloadAttachment(
}
try {
console.log(
"starting download attachment",
attachment.hash,
options.groupId
);
await db
.fs()
.downloadFile(options.groupId || attachment.hash, attachment.hash);
if (!(await exists(attachment.hash))) {
DatabaseLogger.log("Attachment does not exist after download.");
return;
}
if (options.base64 || options.text) {
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
return await db.attachments.read(
attachment.hash,
options.base64 ? "base64" : "text"
@@ -231,7 +232,6 @@ export default async function downloadAttachment(
);
let key = await db.attachments.decryptKey(attachment.key);
let info = {
iv: attachment.iv,
salt: attachment.salt,
@@ -245,6 +245,7 @@ export default async function downloadAttachment(
chunkSize: attachment.chunkSize,
appGroupId: IOS_APPGROUPID
};
let fileUri = await Sodium.decryptFile(
key,
info,
@@ -287,7 +288,7 @@ export default async function downloadAttachment(
return fileUri;
} catch (e) {
DatabaseLogger.error(e);
console.log("download attachment error: ", e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)

View File

@@ -22,24 +22,20 @@ import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { DatabaseLogger, db } from "../database";
import { db } from "../database";
import { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
export async function downloadFile(filename, data, cancelToken) {
if (!data) {
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
return false;
}
if (!data) return false;
DatabaseLogger.log(`Downloading ${filename}`);
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
DatabaseLogger.log(`File Exists already: ${filename}`);
return true;
}
@@ -47,24 +43,13 @@ export async function downloadFile(filename, data, cancelToken) {
method: "GET",
headers
});
if (!res.ok) {
DatabaseLogger.log(
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
);
if (!res.ok)
throw new Error(`${res.status}: Unable to resolve download url`);
}
const downloadUrl = await res.text();
if (!downloadUrl) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: Unable to resolve download url`
);
throw new Error("Unable to resolve download url");
}
if (!downloadUrl) throw new Error("Unable to resolve download url");
let totalSize = 0;
DatabaseLogger.log(`Download starting: ${filename}`);
console.log("Download starting");
let request = RNFetchBlob.config({
path: path,
IOSBackgroundTask: true
@@ -75,15 +60,13 @@ export async function downloadFile(filename, data, cancelToken) {
.getState()
.setProgress(0, total, filename, recieved, "download");
totalSize = total;
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
console.log("downloading: ", recieved, total);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
DatabaseLogger.log(`Download cancelled: ${filename}`);
};
let response = await request;
await fileCheck(response, totalSize);
let status = response.info().status;
@@ -107,10 +90,7 @@ export async function downloadFile(filename, data, cancelToken) {
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
DatabaseLogger.error(e, {
url,
headers
});
console.log("Download file error:", e, url, headers);
return false;
}
}
@@ -119,17 +99,18 @@ export async function getUploadedFileSize(hash) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
DatabaseLogger.error(e);
return -1;
return 0;
}
}
@@ -143,9 +124,7 @@ export async function checkAttachment(hash) {
try {
const size = await getUploadedFileSize(hash);
if (size === -1) return { success: true };
if (size === 0) return { failed: "File length is 0." };
if (size <= 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e?.message };
}

View File

@@ -17,16 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "react-native-blob-util";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
DatabaseLogger.log("Read encrypted file...");
console.log("Read encrypted file...");
let path = `${cacheDir}/${filename}`;
try {
@@ -34,6 +35,10 @@ export async function readEncrypted(filename, key, cipherData) {
return false;
}
const attachment = await db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.mimeType || "");
const isJpeg = /(jpeg|jpg)/g.test(attachment?.mimeType || "");
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{
@@ -41,15 +46,25 @@ export async function readEncrypted(filename, key, cipherData) {
hash: filename,
appGroupId: IOS_APPGROUPID
},
cipherData.outputType === "base64" ? "base64" : "text"
cipherData.outputType === "base64"
? isPng || isJpeg
? "cache"
: "base64"
: "text"
);
DatabaseLogger.log("File decrypted...");
console.log("file decrypted...");
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
const dCachePath = `${cacheDir}/${output}`;
output = await compressToBase64(
`file://${dCachePath}`,
isPng ? "PNG" : "JPEG"
);
}
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
DatabaseLogger.error(e);
console.log("readEncrypted", e);
return false;
}
}
@@ -66,7 +81,7 @@ export async function hashBase64(data) {
};
}
export async function writeEncryptedBase64(data, key) {
export async function writeEncryptedBase64({ data, key }) {
await createCacheDir();
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
@@ -76,8 +91,6 @@ export async function writeEncryptedBase64(data, key) {
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log("encrypted file output: ", output);
output.size = output.length;
delete output.length;
return {
...output,
alg: "xcha-stream"
@@ -127,7 +140,7 @@ export async function clearFileStorage() {
export async function createCacheDir() {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
DatabaseLogger.log("Cache directory created");
console.log("Cache directory created");
}
}
@@ -187,9 +200,6 @@ export async function exists(filename) {
);
if (stat.size !== expectedFileSize) {
DatabaseLogger.log(
`File size mismatch: ${filename}, expected: ${expectedFileSize}, actual: ${stat.size}`
);
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);

View File

@@ -25,8 +25,6 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
import { getUploadedFileSize } from "./download";
import { ToastManager } from "../../services/event-manager";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
@@ -35,31 +33,13 @@ export async function uploadFile(filename, data, cancelToken) {
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
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, {
method: "PUT",
headers
});
const uploadUrl = res.ok ? await res.text() : await res.json();
if (typeof uploadUrl !== "string") {
throw new Error(
uploadUrl.error || "Unable to resolve attachment upload url."
);
}
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
const uploadUrl = await res.text();
if (!uploadUrl) throw new Error("Unable to resolve attachment upload url");
let uploadFilePath = `${cacheDir}/${filename}`;
const iosAppGroup =
@@ -70,15 +50,7 @@ export async function uploadFile(filename, data, cancelToken) {
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
if (!exists && Platform.OS === "ios") {
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}`);
let request = RNFetchBlob.config({
@@ -132,10 +104,8 @@ export async function uploadFile(filename, data, cancelToken) {
return result;
} catch (e) {
useAttachmentStore.getState().remove(filename);
ToastManager.error(e, "File upload failed");
DatabaseLogger.error(e, "File upload failed", {
filename
});
DatabaseLogger.info(`File upload error: ${filename}, ${e}`);
DatabaseLogger.error(e);
return false;
}
}

View File

@@ -23,14 +23,12 @@ import { View } from "react-native";
import { useMessageStore } from "../../stores/use-message-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { allowedOnPlatform, renderItem } from "./functions";
import { getContainerBorder } from "../../utils/colors";
export const Announcement = ({ color }) => {
const { colors } = useThemeColors();
const announcements = useMessageStore((state) => state.announcements);
let announcement = announcements.length > 0 ? announcements[0] : null;
const selectionMode = useSelectionStore((state) => state.selectionMode);
return !announcement || selectionMode ? null : (
<View
style={{
@@ -47,8 +45,7 @@ export const Announcement = ({ color }) => {
borderRadius: 10,
overflow: "hidden",
backgroundColor: colors.secondary.background,
paddingBottom: 12,
...getContainerBorder(colors.secondary.background)
paddingBottom: 12
}}
>
<View

View File

@@ -20,13 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Linking, View } from "react-native";
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
import { useThemeColors } from "@notesnook/theme";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { eCloseAnnouncementDialog } from "../../utils/events";
import Sync from "../../services/sync";
import { useThemeColors } from "@notesnook/theme";
import { eCloseAnnouncementDialog, eCloseSheet } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { PricingPlans } from "../premium/pricing-plans";
import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
import { allowedOnPlatform, getStyle } from "./functions";
@@ -54,6 +56,13 @@ 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 (
@@ -94,7 +103,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
key={item.title}
title={item.title}
fontSize={SIZE.sm}
type="plain"
type="gray"
onPress={() => onPress(item)}
width={null}
height={30}
@@ -140,7 +149,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
key={item.title}
title={item.title}
fontSize={SIZE.xs}
type="plain"
type="gray"
onPress={() => onPress(item)}
width={null}
height={30}

View File

@@ -54,7 +54,7 @@ export const Title = ({ text, style = {}, inline }) => {
</Heading>
<Button
type="plain"
type="gray"
icon="close"
height={null}
onPress={() => {

View File

@@ -18,15 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef } from "react";
import {
AppStateStatus,
Platform,
TextInput,
useWindowDimensions,
View
} from "react-native";
import { Platform, TextInput, View } from "react-native";
//@ts-ignore
import { useThemeColors } from "@notesnook/theme";
import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger } from "../../common/database";
import {
decrypt,
@@ -39,20 +34,17 @@ import {
import { MMKV } from "../../common/database/mmkv";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import { ToastManager } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { NotesnookModule } from "../../utils/notesnook-module";
import { SIZE } from "../../utils/size";
import { Toast } from "../toast";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
const getUser = () => {
const user = MMKV.getString("user");
@@ -82,7 +74,6 @@ const verifyUserPassword = async (password: string) => {
};
const AppLockedOverlay = () => {
const initialLaunchBiometricRequest = useRef(true);
const { colors } = useThemeColors();
const user = getUser();
const appLocked = useUserStore((state) => state.appLocked);
@@ -91,9 +82,7 @@ const AppLockedOverlay = () => {
const passwordInputRef = useRef<TextInput>(null);
const password = useRef<string>();
const appState = useAppState();
const lastAppState = useRef<AppStateStatus>(appState);
const biometricUnlockAwaitingUserInput = useRef(false);
const { height } = useWindowDimensions();
const keyboardType = useSettingStore(
(state) => state.settings.applockKeyboardType
);
@@ -114,8 +103,6 @@ const AppLockedOverlay = () => {
)
return;
biometricUnlockAwaitingUserInput.current = true;
if (Platform.OS === "android") {
const activityName = await NotesnookModule.getActivityName();
if (activityName !== "MainActivity") return;
@@ -128,21 +115,22 @@ const AppLockedOverlay = () => {
);
if (unlocked) {
lockApp(false);
enabled(false);
password.current = undefined;
}
biometricUnlockAwaitingUserInput.current = false;
setTimeout(() => {
if (biometricUnlockAwaitingUserInput.current) return;
biometricUnlockAwaitingUserInput.current = false;
useSettingStore.getState().setRequestBiometrics(false);
}, 500);
}, 1);
}, [biometricsAuthEnabled, lockApp]);
const onSubmit = async () => {
if (!password.current) return;
try {
const unlocked = appLockHasPasswordSecurity
? await validateAppLockPassword(password.current)
? validateAppLockPassword(password.current)
: await verifyUserPassword(password.current);
if (unlocked) {
if (!appLockHasPasswordSecurity) {
await setAppLockVerificationCipher(password.current);
@@ -154,13 +142,8 @@ const AppLockedOverlay = () => {
}
lockApp(false);
enabled(false);
password.current = undefined;
} else {
ToastManager.show({
heading: `Invalid ${keyboardType === "numeric" ? "pin" : "password"}`,
type: "error",
context: "local"
});
}
} catch (e) {
console.error(e);
@@ -168,53 +151,28 @@ const AppLockedOverlay = () => {
};
useEffect(() => {
const prevState = lastAppState.current;
lastAppState.current = appState;
if (
appState === "active" &&
(prevState === "background" || initialLaunchBiometricRequest.current)
) {
if (SettingsService.shouldLockAppOnEnterForeground()) {
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();
biometricUnlockAwaitingUserInput.current ||
useUserStore.getState().disableAppLockRequests
)
return;
if (appLocked && appState === "active") {
biometricUnlockAwaitingUserInput.current = true;
onUnlockAppRequested();
}
}, [appState, onUnlockAppRequested, appLocked]);
return appLocked ? (
<KeyboardAwareScrollView
<View
style={{
backgroundColor: colors.primary.background,
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999
zIndex: 999,
justifyContent: "center"
}}
contentContainerStyle={{
justifyContent: "center",
minHeight: height
}}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
>
<Toast context="local" />
<View
style={{
flex: 1,
@@ -234,7 +192,7 @@ const AppLockedOverlay = () => {
<IconButton
name="fingerprint"
size={100}
style={{
customStyle={{
width: 100,
height: 100,
marginBottom: 20,
@@ -327,7 +285,7 @@ const AppLockedOverlay = () => {
</View>
</View>
</View>
</KeyboardAwareScrollView>
</View>
) : null;
};

View File

@@ -40,8 +40,7 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import {
eCloseAttachmentDialog,
eCloseSheet,
eDBItemUpdate,
eOnLoadNote
eDBItemUpdate
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
@@ -51,12 +50,9 @@ import { openNote } from "../list-items/note/wrapper";
import { DateMeta } from "../properties/date-meta";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
import { Pressable } from "../ui/pressable";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
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 = ({
attachment,
@@ -66,7 +62,7 @@ const Actions = ({
}: {
attachment: Attachment;
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
close?: () => void;
close: () => void;
fwdRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
@@ -107,7 +103,7 @@ const Actions = ({
reupload: true,
hash: attachment.hash,
context: contextId,
type: attachment.mimeType.startsWith("image") ? "image" : "file"
type: attachment.type
});
},
icon: "upload"
@@ -118,7 +114,7 @@ const Actions = ({
setLoading({
name: "Run file check"
});
const res = await filesystem.checkAttachment(attachment.hash);
let res = await filesystem.checkAttachment(attachment.hash);
if (res.failed) {
db.attachments.markAsFailed(attachment.id, res.failed);
setFailed(res.failed);
@@ -173,27 +169,10 @@ const Actions = ({
{
name: "Delete",
onPress: async () => {
const relations = await db.relations.to(attachment, "note").get();
await db.attachments.remove(attachment.hash, false);
setAttachments();
eSendEvent(eDBItemUpdate, attachment.id);
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?.();
close();
},
icon: "delete-outline"
}
@@ -219,11 +198,10 @@ const Actions = ({
}}
>
<Dialog context={contextId} />
<SheetProvider context={contextId} />
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
borderBottomColor: colors.secondary.background,
marginBottom: notes && notes.length > 0 ? 0 : 12
}}
>
@@ -240,20 +218,36 @@ const Actions = ({
style={{
flexDirection: "row",
marginBottom: 10,
paddingHorizontal: 12,
marginTop: 6,
gap: 10
paddingHorizontal: 12
}}
>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{attachment.mimeType}
<Paragraph
size={SIZE.xs}
style={{
marginRight: 10
}}
color={colors.secondary.paragraph}
>
{attachment.type}
</Paragraph>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
<Paragraph
style={{
marginRight: 10
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{formatBytes(attachment.size)}
</Paragraph>
{notes.length ? (
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
<Paragraph
style={{
marginRight: 10
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{notes.length} note
{notes.length > 1 ? "s" : ""}
</Paragraph>
@@ -281,7 +275,7 @@ const Actions = ({
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
borderBottomColor: colors.secondary.background,
marginBottom: 12,
paddingVertical: 12
}}
@@ -297,7 +291,7 @@ const Actions = ({
</Heading>
{notes.map((item) => (
<Pressable
<PressableButton
onPress={async () => {
eSendEvent(eCloseSheet, contextId);
await sleep(150);
@@ -305,7 +299,7 @@ const Actions = ({
await sleep(300);
openNote(item, (item as any).type === "trash");
}}
style={{
customStyle={{
paddingVertical: 12,
alignItems: "flex-start",
@@ -314,7 +308,7 @@ const Actions = ({
key={item.id}
>
<Paragraph size={SIZE.xs}>{item.title}</Paragraph>
</Pressable>
</PressableButton>
))}
</>
</View>
@@ -325,7 +319,7 @@ const Actions = ({
key={item.name}
buttonType={{
text:
item.name === "Delete"
item.name === "Delete" || item.name === "PermDelete"
? colors.error.paragraph
: colors.primary.paragraph
}}
@@ -333,7 +327,7 @@ const Actions = ({
title={item.name}
icon={item.icon}
loading={loading?.name === item.name}
type="plain"
type="gray"
fontSize={SIZE.sm}
style={{
borderRadius: 0,
@@ -352,7 +346,7 @@ const Actions = ({
{failed ? (
<Notice
type="alert"
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
text={`File check failed with error: ${attachment.failed} Try reuploading the file to fix the issue.`}
size="small"
/>
) : null}

View File

@@ -67,8 +67,9 @@ export const AttachmentItem = ({
Actions.present(attachment, setAttachments, context);
};
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
return (hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value)) ||
!attachment ? null : (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
@@ -79,111 +80,104 @@ export const AttachmentItem = ({
padding: 12,
paddingVertical: 6,
borderRadius: 5,
backgroundColor: colors.secondary.background,
minHeight: 45
backgroundColor: colors.secondary.background
}}
>
{!attachment ? null : (
<>
<View
<View
style={{
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
}}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginLeft: -5
}}
>
<Icon name="file" size={SIZE.xxxl} color={colors.primary.icon} />
<Paragraph
adjustsFontSizeToFit
size={6}
color={colors.static.white}
style={{
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
position: "absolute"
}}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginLeft: -5
}}
>
<Icon name="file" size={SIZE.xxxl} color={colors.primary.icon} />
{getFileExtension(attachment.filename).toUpperCase()}
</Paragraph>
</View>
<Paragraph
adjustsFontSizeToFit
size={6}
color={colors.static.white}
style={{
position: "absolute"
}}
>
{getFileExtension(attachment.filename).toUpperCase()}
</Paragraph>
</View>
<View
style={{
flexShrink: 1,
marginLeft: 10
}}
>
<Paragraph
size={SIZE.sm - 1}
style={{
flexWrap: "wrap",
marginBottom: 2.5
}}
numberOfLines={1}
lineBreakMode="middle"
color={colors.primary.paragraph}
>
{attachment.filename}
</Paragraph>
<View
style={{
flexShrink: 1,
marginLeft: 10
}}
>
<Paragraph
size={SIZE.sm - 1}
style={{
flexWrap: "wrap",
marginBottom: 2.5
}}
numberOfLines={1}
lineBreakMode="middle"
color={colors.primary.paragraph}
>
{attachment.filename}
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{formatBytes(attachment.size)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
</View>
</View>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{formatBytes(attachment.size)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
</View>
</View>
{currentProgress ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs().cancel(attachment.hash);
setCurrentProgress(undefined);
}}
style={{
justifyContent: "center",
marginLeft: 5,
marginTop: 5,
marginRight: -5
}}
>
<ProgressCircleComponent
size={SIZE.xxl}
progress={
currentProgress?.value ? currentProgress?.value / 100 : 0
}
showsText
textStyle={{
fontSize: 10
}}
color={colors.primary.accent}
formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
</TouchableOpacity>
) : (
<>
{attachment.failed ? (
<IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.error.paragraph}
/>
) : null}
</>
)}
{currentProgress ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs().cancel(attachment.metadata.hash);
setCurrentProgress(undefined);
}}
style={{
justifyContent: "center",
marginLeft: 5,
marginTop: 5,
marginRight: -5
}}
>
<ProgressCircleComponent
size={SIZE.xxl}
progress={currentProgress?.value ? currentProgress?.value / 100 : 0}
showsText
textStyle={{
fontSize: 10
}}
color={colors.primary.accent}
formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
</TouchableOpacity>
) : (
<>
{attachment.failed ? (
<IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.error.paragraph}
/>
) : null}
</>
)}
</TouchableOpacity>

View File

@@ -84,9 +84,8 @@ const DownloadAttachments = ({
canceled.current = true;
if (!groupId.current) return;
console.log(groupId.current, "canceling groupId downloads");
await db.fs().cancel(groupId.current);
await db.fs().cancel(groupId.current, "download");
setDownloading(false);
setResult(new Map());
groupId.current = undefined;
};
@@ -202,7 +201,7 @@ const DownloadAttachments = ({
</Paragraph>
</View>
}
keyExtractor={(index) => "attachment_download" + index}
keyExtractor={(index) => "attachment" + index}
renderItem={({ index }) => {
return (
<AttachmentItem
@@ -246,7 +245,7 @@ const DownloadAttachments = ({
onPress={() => {
close?.();
}}
type="secondary"
type="grayBg"
title="No"
/>
<Button

View File

@@ -61,21 +61,10 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
const refresh = React.useCallback(() => {
if (note) {
db.attachments
.ofNote(note.id, "all")
.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.then((attachments) => {
setAttachments(attachments);
});
db.attachments.ofNote(note.id, "all").sorted(DEFAULT_SORTING);
} else {
db.attachments.all
.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.sorted(DEFAULT_SORTING)
.then((attachments) => setAttachments(attachments));
}
}, [note]);
@@ -160,9 +149,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
switch (type) {
case "all":
items = note
? db.attachments.ofNote(note.id, "all")
: db.attachments.all;
items = db.attachments.all;
break;
case "images":
items = note
@@ -171,24 +158,19 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
break;
case "video":
items = items = note
? db.attachments.ofNote(note.id, "videos")
? db.attachments.ofNote(note.id, "all")
: db.attachments.videos;
break;
case "audio":
items = note
? db.attachments.ofNote(note.id, "audio")
: db.attachments.audios;
items = db.attachments.all;
break;
case "documents":
items = note
? db.attachments.ofNote(note.id, "documents")
? db.attachments.ofNote(note.id, "all")
: db.attachments.documents;
}
return await items.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
});
return await items.sorted(DEFAULT_SORTING);
};
return (
@@ -227,7 +209,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
) : (
<IconButton
name="check-all"
style={{
customStyle={{
height: 40,
width: 40,
marginRight: 10
@@ -240,7 +222,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
<IconButton
name="download"
style={{
customStyle={{
height: 40,
width: 40
}}
@@ -283,9 +265,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
>
{attachmentTypes.map((item) => (
<Button
type={
currentFilter === item.filterBy ? "secondaryAccented" : "plain"
}
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
key={item.title}
title={item.title}
style={{
@@ -355,7 +335,6 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
AttachmentDialog.present = (note?: Note) => {
presentSheet({
component: () => <AttachmentDialog note={note} />,
keyboardHandlerDisabled: true
component: () => <AttachmentDialog note={note} />
});
};

View File

@@ -158,7 +158,7 @@ const AuthModal = () => {
hideAuth();
}}
iconSize={16}
type="plain"
type="gray"
iconPosition="right"
icon="chevron-right"
height={25}

View File

@@ -26,15 +26,12 @@ import {
ToastManager
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import { eCloseSheet } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -67,9 +64,6 @@ export const ChangePassword = () => {
}
setLoading(true);
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.changePassword(oldPassword.current, password.current);
ToastManager.show({
@@ -79,8 +73,6 @@ export const ChangePassword = () => {
});
setLoading(false);
eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
setLoading(false);
ToastManager.show({
@@ -100,7 +92,6 @@ export const ChangePassword = () => {
padding: 12
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader
title="Change password"
paragraph="Enter your old and new passwords"
@@ -138,14 +129,7 @@ export const ChangePassword = () => {
/>
<Notice
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.`}
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."
type="alert"
/>

View File

@@ -113,7 +113,7 @@ export const ForgotPassword = () => {
}}
>
<IconButton
style={{
customStyle={{
width: null,
height: null
}}

View File

@@ -59,6 +59,22 @@ const Auth = ({ navigation, route }) => {
/>
)}
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (
<IconButton
name="arrow-left"
onPress={() => {
hideAuth();
}}
color={colors.primary.paragraph}
customStyle={{
position: 'absolute',
zIndex: 999,
left: 12,
top: Platform.OS === 'ios' ? 12 + insets.top : insets.top
}}
/>
)} */}
<Toast context="local" />
</View>
);

View File

@@ -17,16 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Sync from "../../services/sync";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { eUserLoggedIn } from "../../utils/events";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import SheetProvider from "../sheet-provider";
@@ -38,6 +34,10 @@ import Paragraph from "../ui/typography/paragraph";
import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
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 = {
emailAuth: 1,
@@ -66,7 +66,7 @@ export const Login = ({ changeMode }) => {
Progress.present();
setTimeout(() => {
if (!useUserStore.getState().syncing) {
Sync.run("global", false, "full");
Sync.run("global", false, true);
}
}, 5000);
});
@@ -103,7 +103,7 @@ export const Login = ({ changeMode }) => {
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.secondary.background,
borderBottomWidth: 0.8,
borderBottomWidth: 1,
marginBottom: 12,
borderBottomColor: colors.primary.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
@@ -183,11 +183,7 @@ export const Login = ({ changeMode }) => {
defaultValue={email.current}
editable={step === LoginSteps.emailAuth && !loading}
onSubmit={() => {
if (step === LoginSteps.emailAuth) {
login();
} else {
passwordInputRef.current?.focus();
}
passwordInputRef.current?.focus();
}}
/>
@@ -226,7 +222,7 @@ export const Login = ({ changeMode }) => {
textDecorationLine: "underline"
}}
fontSize={SIZE.xs}
type="plain"
type="gray"
/>
</>
)}
@@ -246,7 +242,6 @@ export const Login = ({ changeMode }) => {
width: 250,
borderRadius: 100
}}
height={50}
fontSize={SIZE.md}
type="accent"
title={!loading ? "Continue" : null}

View File

@@ -69,9 +69,6 @@ export const SessionExpired = () => {
eSendEvent(eUserLoggedIn, true);
setVisible(false);
setFocused(false);
useUserStore.setState({
disableAppLockRequests: false
});
},
true
);
@@ -87,9 +84,6 @@ export const SessionExpired = () => {
MMKV.clearStore();
clearAllStores();
setVisible(false);
useUserStore.setState({
disableAppLockRequests: false
});
} catch (e) {
ToastManager.show({
heading: e.message,
@@ -112,11 +106,7 @@ export const SessionExpired = () => {
if (!res) throw new Error("no token found");
if (db.tokenManager._isTokenExpired(res))
throw new Error("token expired");
const key = await db.user.getEncryptionKey();
if (!key) throw new Error("No encryption key found.");
Sync.run("global", false, "full", async (complete) => {
Sync.run("global", false, true, async (complete) => {
if (!complete) {
let user = await db.user.getUser();
if (!user) return;
@@ -131,14 +121,12 @@ export const SessionExpired = () => {
setVisible(false);
});
} catch (e) {
console.log(e);
let user = await db.user.getUser();
if (!user) return;
email.current = user.email;
setFocused(false);
setVisible(true);
useUserStore.setState({
disableAppLockRequests: true
});
}
}, [email]);
@@ -151,15 +139,9 @@ export const SessionExpired = () => {
animated={false}
centered={false}
onShow={async () => {
useUserStore.setState({
disableAppLockRequests: true
});
await sleep(300);
passwordInputRef.current?.focus();
setFocused(true);
useUserStore.setState({
disableAppLockRequests: true
});
}}
enableSheetKeyboardHandler={true}
visible={true}
@@ -184,7 +166,7 @@ export const SessionExpired = () => {
}}
>
<IconButton
style={{
customStyle={{
width: 60,
height: 60
}}

View File

@@ -111,7 +111,7 @@ export const Signup = ({ changeMode, trial }) => {
paddingHorizontal: 20,
backgroundColor: colors.secondary.background,
marginBottom: 20,
borderBottomWidth: 0.8,
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,

View File

@@ -32,12 +32,11 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import { Pressable } from "../ui/pressable";
import { PressableButton } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import { ScrollView } from "react-native-actions-sheet";
const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
const { colors } = useThemeColors();
@@ -144,10 +143,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
return (
<ScrollView
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
>
<View>
<View
style={{
alignItems: "center",
@@ -155,7 +151,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
}}
>
<IconButton
style={{
customStyle={{
width: 70,
height: 70
}}
@@ -187,7 +183,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
<Button
onPress={onSendCode}
type={seconds ? "plain" : "transparent"}
type={seconds ? "gray" : "transparent"}
title={
sending
? ""
@@ -218,7 +214,6 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
code.current = value;
//onNext();
}}
onSubmitEditing={onNext}
caretHidden
inputStyle={{
fontSize: SIZE.lg,
@@ -230,7 +225,6 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
keyboardType={
currentMethod.method === "recoveryCode" ? "default" : "numeric"
}
enablesReturnKeyAutomatically
containerStyle={{
height: 60,
borderWidth: 0,
@@ -253,7 +247,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
<Button
title={secondaryMethodsText[currentMethod.method]}
type="plain"
type="gray"
onPress={onRequestSecondaryMethod}
height={30}
/>
@@ -261,7 +255,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
) : (
<>
{getMethods().map((item) => (
<Pressable
<PressableButton
key={item.title}
onPress={() => {
setCurrentMethod({
@@ -269,7 +263,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
isPrimary: false
});
}}
style={{
customStyle={{
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
@@ -281,8 +275,8 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
}}
>
<IconButton
type="secondaryAccented"
style={{
type="grayAccent"
customStyle={{
width: 40,
height: 40,
marginRight: 10
@@ -298,12 +292,12 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
>
<Paragraph size={SIZE.md}>{item.title}</Paragraph>
</View>
</Pressable>
</PressableButton>
))}
</>
)}
</View>
</ScrollView>
</View>
);
};

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import { useRoute } from "@react-navigation/native";
import React, { useCallback, useEffect } from "react";
import { Keyboard, View } from "react-native";
import Animated, {
@@ -35,7 +34,7 @@ import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import NativeTooltip from "../../utils/tooltip";
import { Pressable } from "../ui/pressable";
import { PressableButton } from "../ui/pressable";
interface FloatingButtonProps {
title: string;
@@ -55,7 +54,6 @@ const FloatingButton = ({
const deviceMode = useSettingStore((state) => state.deviceMode);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const translate = useSharedValue(0);
const route = useRoute();
const animatedStyle = useAnimatedStyle(() => {
return {
@@ -118,11 +116,11 @@ const FloatingButton = ({
animatedStyle
]}
>
<Pressable
<PressableButton
testID={notesnook.buttons.add}
type="accent"
accentColor={color}
style={{
customStyle={{
...getElevationStyle(5),
borderRadius: 100
}}
@@ -142,18 +140,12 @@ const FloatingButton = ({
}}
>
<Icon
name={
route.name === "Notebooks"
? "notebook-plus"
: route.name === "Trash"
? "delete"
: "plus"
}
name={title === "Clear all trash" ? "delete" : "plus"}
color={colors.primary.accentForeground}
size={SIZE.xxl}
/>
</View>
</Pressable>
</PressableButton>
</Animated.View>
);
};

View File

@@ -21,16 +21,14 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useKeyboard from "../../hooks/use-keyboard";
import { useThemeColors } from "@notesnook/theme";
export const Container = ({ children }: PropsWithChildren) => {
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const keyboard = useKeyboard();
const [height, setHeight] = useState(0);
const windowHeightRef = useRef(Dimensions.get("window").height);
const { height: windowHeight } = useWindowDimensions();
const timerRef = useRef<NodeJS.Timeout>();
useEffect(() => {
if (windowHeight !== windowHeightRef.current) {
setHeight(0);
@@ -45,17 +43,12 @@ export const Container = ({ children }: PropsWithChildren) => {
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
height: height || "100%",
width: "100%",
backgroundColor: colors.primary.background
width: "100%"
}}
onLayout={(event) => {
const height = event.nativeEvent.layout.height;
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
if (!keyboard.keyboardShown) {
setHeight(height);
}
}, 500);
if (!keyboard.keyboardShown) {
setHeight(event.nativeEvent.layout.height);
}
}}
>
{children}

View File

@@ -43,7 +43,6 @@ const DialogProvider = () => {
return (
<>
<AppLockPassword />
<LoadingDialog />
<Dialog context="global" />
<PremiumDialog colors={colors} />
@@ -62,6 +61,7 @@ const DialogProvider = () => {
<SessionExpired />
<PDFPreview />
<JumpToSectionDialog />
<AppLockPassword />
</>
);
};

View File

@@ -25,7 +25,6 @@ import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
import { getColorLinearShade } from "../../utils/colors";
const DialogButtons = ({
onPressPositive,
@@ -36,7 +35,7 @@ const DialogButtons = ({
doneText,
positiveType
}) => {
const { colors, isDark } = useThemeColors();
const { colors } = useThemeColors();
return (
<View
@@ -45,15 +44,9 @@ const DialogButtons = ({
{
backgroundColor: colors.secondary.background,
height: 60,
borderBottomRightRadius: 5,
borderBottomLeftRadius: 5,
paddingHorizontal: 12,
borderTopWidth: 0.7,
borderTopColor: getColorLinearShade(
colors.secondary.background,
0.05,
isDark
)
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
paddingHorizontal: 12
}
]}
>
@@ -87,7 +80,7 @@ const DialogButtons = ({
onPress={onPressNegative}
fontSize={SIZE.md}
testID={notesnook.ids.default.dialog.no}
type="plain"
type="gray"
bold
title={negativeTitle}
/>

View File

@@ -22,7 +22,6 @@ import { View, ViewProps } from "react-native";
import { DDS } from "../../services/device-detection";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../utils/elevation";
import { getContainerBorder } from "../../utils/colors";
const DialogContainer = ({
width,
@@ -38,15 +37,17 @@ const DialogContainer = ({
return (
<View
{...restProps}
style={{
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12,
...getContainerBorder(colors.secondary.background, 0.8, 0.05)
}}
style={[
{
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
},
style
]}
/>
);
};

View File

@@ -22,7 +22,7 @@ import { Text, View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableProps } from "../ui/pressable";
import { PressableButtonProps } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
@@ -34,7 +34,7 @@ type DialogHeaderProps = {
onPress?: () => void;
loading?: boolean;
title?: string;
type?: PressableProps["type"];
type?: PressableButtonProps["type"];
icon?: string;
};
paragraphColor?: string;
@@ -86,9 +86,7 @@ const DialogHeader = ({
>
{title}{" "}
{titlePart ? (
<Text style={{ color: colors.primary.accent }}>
{titlePart}
</Text>
<Text style={{ color: colors.primary.accent }}>{titlePart}</Text>
) : null}
</Heading>
@@ -103,8 +101,8 @@ const DialogHeader = ({
fontSize={13}
title={button.title}
icon={button.icon}
type={button.type || "secondary"}
height={30}
type={button.type || "grayBg"}
height={25}
/>
) : null}
</View>

View File

@@ -29,8 +29,8 @@ type DialogInfo = {
onClose?: () => void;
positiveType?:
| "transparent"
| "plain"
| "secondary"
| "gray"
| "grayBg"
| "accent"
| "inverted"
| "shade"
@@ -44,7 +44,6 @@ type DialogInfo = {
// eslint-disable-next-line @typescript-eslint/ban-types
context: "global" | "local" | (string & {});
secureTextEntry?: boolean;
keyboardType?: string;
};
export function presentDialog(data: Partial<DialogInfo>): void {

View File

@@ -36,15 +36,11 @@ import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
import { useCallback } from "react";
import { Button } from "../ui/button";
import { getContainerBorder } from "../../utils/colors";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [checked, setChecked] = useState(false);
const values = useRef({
inputValue: undefined
});
const [inputValue, setInputValue] = useState(null);
const inputRef = useRef();
const [dialogInfo, setDialogInfo] = useState({
title: "",
@@ -70,8 +66,7 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.positivePress) {
inputRef.current?.blur();
let result = await dialogInfo.positivePress(
values.current.inputValue || dialogInfo.defaultValue,
checked
inputValue || dialogInfo.defaultValue
);
if (result === false) {
return;
@@ -86,9 +81,8 @@ export const Dialog = ({ context = "global" }) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setChecked(false);
values.current.inputValue = data.defaultValue;
setVisible(true);
setInputValue(data.defaultValue);
},
[context]
);
@@ -104,8 +98,7 @@ export const Dialog = ({ context = "global" }) => {
}, [show]);
const hide = () => {
setChecked(false);
values.current.inputValue = undefined;
setInputValue(null);
setVisible(false);
};
@@ -113,6 +106,7 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.onClose) {
await dialogInfo.onClose();
}
hide();
};
@@ -122,8 +116,7 @@ export const Dialog = ({ context = "global" }) => {
maxHeight: 450,
borderRadius: 5,
backgroundColor: colors.primary.background,
paddingTop: 12,
...getContainerBorder(colors.primary.border, 0.5)
paddingTop: 12
};
return visible ? (
@@ -166,7 +159,7 @@ export const Dialog = ({ context = "global" }) => {
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.inputValue = value;
setInputValue(value);
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
@@ -174,7 +167,6 @@ export const Dialog = ({ context = "global" }) => {
onSubmit={onPressPositive}
returnKeyLabel="Done"
returnKeyType="done"
keyboardType={dialogInfo.keyboardType || "default"}
placeholder={dialogInfo.inputPlaceholder}
/>
</View>
@@ -184,10 +176,10 @@ export const Dialog = ({ context = "global" }) => {
<>
<Button
onPress={() => {
setChecked(!checked);
setInputValue(!inputValue);
}}
icon={
checked
inputValue
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
@@ -195,10 +187,9 @@ export const Dialog = ({ context = "global" }) => {
justifyContent: "flex-start"
}}
height={35}
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type || "selected" : "plain"}
type={inputValue ? dialogInfo.check.type : "gray"}
/>
</>
) : null}

View File

@@ -19,13 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { db } from "../../../common/database";
import {
clearAppLockVerificationCipher,
setAppLockVerificationCipher,
validateAppLockPassword
} from "../../../common/database/encryption";
import BiometricService from "../../../services/biometrics";
import BiometicService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
@@ -45,7 +44,6 @@ import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
import DialogHeader from "../../dialog/dialog-header";
import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
@@ -68,7 +66,6 @@ export const AppLockPassword = () => {
confirmPassword?: string;
}>({});
const [secureTextEntry, setSecureTextEntry] = useState(true);
const [accountPass, setAccountPass] = useState(false);
useEffect(() => {
const subs = [
@@ -76,7 +73,6 @@ export const AppLockPassword = () => {
eOpenAppLockPasswordDialog,
(mode: "create" | "change" | "remove") => {
setMode(mode);
setAccountPass(false);
setVisible(true);
}
),
@@ -95,7 +91,7 @@ export const AppLockPassword = () => {
setVisible(false);
};
return !visible ? null : (
return (
<BaseDialog
onShow={async () => {
await sleep(100);
@@ -130,9 +126,7 @@ export const AppLockPassword = () => {
mode === "change"
? `Change app lock ${keyboardType}`
: mode === "remove"
? `Enter ${
accountPass ? "account password" : `app lock ${keyboardType}`
} to remove ${keyboardType}`
? `Remove app lock ${keyboardType}`
: `Set up a custom app lock ${keyboardType} to unlock the app`
}
icon="shield"
@@ -175,39 +169,33 @@ export const AppLockPassword = () => {
confirmPasswordInputRef.current?.focus();
}}
defaultValue={values.current.password}
keyboardType={
keyboardType === "pin" && !accountPass ? "number-pad" : "default"
}
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
autoComplete="password"
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
returnKeyType={mode !== "remove" ? "next" : "done"}
secureTextEntry={secureTextEntry}
buttonLeft={
accountPass ? null : (
<IconButton
name={keyboardType === "password" ? "numeric" : "keyboard"}
onPress={() => {
setKeyboardType(
keyboardType === "password" ? "pin" : "password"
);
setSecureTextEntry(false);
setImmediate(() => {
setSecureTextEntry(true);
});
}}
style={{
width: 25,
height: 25,
marginRight: 5
}}
size={SIZE.lg}
/>
)
<IconButton
name={keyboardType === "password" ? "numeric" : "keyboard"}
onPress={() => {
setKeyboardType(
keyboardType === "password" ? "pin" : "password"
);
setSecureTextEntry(false);
setImmediate(() => {
setSecureTextEntry(true);
});
}}
customStyle={{
width: 25,
height: 25,
marginRight: 5
}}
size={SIZE.lg}
/>
}
placeholder={
accountPass
? "Account password"
: mode === "change"
mode === "change"
? `New ${keyboardType}`
: `${keyboardType === "pin" ? "Pin" : "Password"}`
}
@@ -234,36 +222,6 @@ export const AppLockPassword = () => {
placeholder={`Confirm ${keyboardType}`}
/>
) : 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>
<DialogButtons
@@ -291,8 +249,7 @@ export const AppLockPassword = () => {
);
return;
}
const password = values.current.password;
setAppLockVerificationCipher(password);
await setAppLockVerificationCipher(values.current.password);
SettingsService.setProperty("appLockHasPasswordSecurity", true);
} else if (mode === "change") {
if (
@@ -320,6 +277,7 @@ export const AppLockPassword = () => {
);
return;
}
const isCurrentPasswordCorrect = await validateAppLockPassword(
values.current.currentPassword
);
@@ -335,10 +293,8 @@ export const AppLockPassword = () => {
return;
}
const password = values.current.password;
await clearAppLockVerificationCipher();
SettingsService.setProperty("appLockHasPasswordSecurity", true);
await setAppLockVerificationCipher(password);
await setAppLockVerificationCipher(values.current.password);
} else if (mode === "remove") {
if (!values.current.password) {
ToastManager.error(
@@ -349,20 +305,15 @@ export const AppLockPassword = () => {
return;
}
const isCurrentPasswordCorrect = accountPass
? await db.user.verifyPassword(values.current.password)
: await validateAppLockPassword(values.current.password);
const isCurrentPasswordCorrect = await validateAppLockPassword(
values.current.password
);
if (!isCurrentPasswordCorrect) {
ToastManager.error(
new Error(
accountPass
? "Account password incorrect"
: `${
keyboardType === "pin" ? "Pin" : "Password"
} incorrect`
`${keyboardType === "pin" ? "Pin" : "Password"} incorrect`
),
undefined,
"local"
);
return;
@@ -371,11 +322,10 @@ export const AppLockPassword = () => {
SettingsService.setProperty("appLockHasPasswordSecurity", false);
if (
!(await BiometricService.isBiometryAvailable()) ||
!(await BiometicService.isBiometryAvailable()) ||
SettingsService.getProperty("biometricsAuthEnabled") === false
) {
SettingsService.setProperty("appLockEnabled", false);
SettingsService.setPrivacyScreen(SettingsService.get());
ToastManager.show({
message: "App lock disabled",
type: "success"
@@ -390,9 +340,7 @@ export const AppLockPassword = () => {
close();
}}
positiveTitle={
mode === "remove" ? "Remove" : mode === "change" ? "Change" : "Save"
}
positiveTitle="Save"
negativeTitle="Cancel"
positiveType="transparent"
loading={false}

View File

@@ -1,196 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import {
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 { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Notice } from "../../ui/notice";
import Paragraph from "../../ui/typography/paragraph";
export default function AttachImage({
response,
onAttach,
close
}: {
response: ImageType[];
onAttach: ({ compress }: { compress: boolean }) => void;
close: ((ctx?: string | undefined) => void) | undefined;
}) {
const { colors } = useThemeColors();
const [compress, setCompress] = useState(true);
return (
<View
style={{
alignItems: "center",
paddingHorizontal: 12
}}
>
<View
style={{
backgroundColor: colors.secondary.background,
marginBottom: 12,
height: 140,
width: "100%",
borderRadius: 10,
padding: 5,
paddingHorizontal: 12
}}
>
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
Attaching {response?.length} image(s):
</Paragraph>
<ScrollView horizontal>
{response?.map?.((item) => (
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
<Image
source={{
uri:
Platform.OS === "ios"
? item.sourceURL || item.path
: item.path
}}
style={{
width: 100,
height: 100,
borderRadius: 5,
backgroundColor: "black",
marginRight: 6
}}
resizeMode="cover"
/>
</TouchableOpacity>
))}
</ScrollView>
</View>
<TouchableOpacity
activeOpacity={1}
style={{
flexDirection: "row",
alignSelf: "center",
marginBottom: 12,
alignItems: "center",
width: "100%"
}}
onPress={() => {
setCompress(!compress);
}}
>
<IconButton
size={SIZE.lg}
name={compress ? "checkbox-marked" : "checkbox-blank-outline"}
color={compress ? colors.primary.accent : colors.primary.icon}
style={{
width: 25,
height: 25
}}
onPress={() => {
setCompress(!compress);
}}
/>
<Paragraph
style={{
flexShrink: 1,
marginLeft: 3
}}
size={SIZE.sm}
>
Compress (recommended)
</Paragraph>
</TouchableOpacity>
{!compress ? (
<Notice
type="alert"
text="Images uploaded without compression are slow to load and take more bandwidth. We recommend compressing images unless you need image in original quality."
size="small"
style={{
width: "100%",
marginBottom: 12
}}
/>
) : (
<Notice
type="information"
text="Compressed images are uploaded in Full HD resolution and usually are good enough for most use cases."
size="small"
style={{
width: "100%",
marginBottom: 12
}}
/>
)}
<Button
title={`${
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
}`}
type="accent"
width="100%"
onPress={() => {
onAttach({
compress
});
}}
/>
</View>
);
}
AttachImage.present = (response: ImageType[], context?: string) => {
return new Promise<
| {
compress: boolean;
}
| undefined
>((resolve) => {
let resolved = false;
presentSheet({
context: context,
component: (ref, close, update) => (
<AttachImage
response={response}
close={close}
onAttach={(result) => {
resolved = true;
console.log("closing");
resolve(result);
close?.();
}}
/>
),
onClose: () => {
if (resolved) return;
resolve(undefined);
}
});
});
};

View File

@@ -32,7 +32,7 @@ import DialogContainer from "../../dialog/dialog-container";
import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import { PressableButton } from "../../ui/pressable";
const HEX_COLOR_REGEX_ALPHA =
/^#(?:(?:[\da-fA-F]{3}){1,2}|(?:[\da-fA-F]{4}){1,2})$/;
@@ -56,7 +56,6 @@ const ColorPicker = ({
<BaseDialog
visible={visible}
onRequestClose={() => {
title.current = undefined;
setVisible(false);
useSettingStore.getState().setSheetKeyboardHandler(true);
}}
@@ -75,7 +74,7 @@ const ColorPicker = ({
style={{
width: "100%",
height: 250,
backgroundColor: colors.primary.background,
backgroundColor: selectedColor,
borderRadius: 10
}}
>
@@ -128,10 +127,10 @@ const ColorPicker = ({
}}
defaultValue={colorRef.current}
/>
<Pressable
<PressableButton
type="accent"
accentColor={selectedColor || colors.secondary.background}
style={{
customStyle={{
width: 45,
height: 45,
borderRadius: 100,
@@ -146,16 +145,12 @@ const ColorPicker = ({
onChangeText={(value) => {
title.current = value;
}}
testID="color-title-input"
defaultValue={title.current}
placeholder={title.current || "Color title"}
/>
<Button
title="Add color"
style={{
marginBottom: 10
}}
onPress={async () => {
if (!selectedColor)
return ToastManager.error(
@@ -174,6 +169,7 @@ const ColorPicker = ({
return ToastManager.error(
new Error(`Color #${selectedColor} already exists`)
);
const id = await db.colors.add({
title: title.current,
colorCode: selectedColor
@@ -186,7 +182,7 @@ const ColorPicker = ({
onColorAdded?.(color);
}
}}
type={selectedColor ? "secondaryAccented" : "secondary"}
type={selectedColor ? "grayAccent" : "grayBg"}
width="100%"
/>
</View>

View File

@@ -41,7 +41,7 @@ import {
} from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import BaseDialog from "../../dialog/base-dialog";
import { Pressable } from "../../ui/pressable";
import { PressableButton } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
const JumpToSectionDialog = () => {
@@ -181,11 +181,11 @@ const JumpToSectionDialog = () => {
>
{groups?.map((item, index) => {
return (
<Pressable
<PressableButton
key={item.group.id}
onPress={() => onPress(item)}
type={currentIndex === index ? "accent" : "transparent"}
style={{
customStyle={{
minWidth: "20%",
width: null,
paddingHorizontal: 12,
@@ -208,7 +208,7 @@ const JumpToSectionDialog = () => {
>
{item.group.title}
</Paragraph>
</Pressable>
</PressableButton>
);
})}
</View>

View File

@@ -211,7 +211,7 @@ const PDFPreview = () => {
color={colors.static.white}
name="arrow-left"
onPress={close}
style={{
customStyle={{
marginRight: 12
}}
size={SIZE.xxl}

View File

@@ -23,6 +23,7 @@ import { InteractionManager, View } from "react-native";
import Share from "react-native-share";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { editorController } from "../../../screens/editor/tiptap/utils";
import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
@@ -34,11 +35,11 @@ import {
import Navigation from "../../../services/navigation";
import { getElevationStyle } from "../../../utils/elevation";
import {
eClearEditor,
eCloseActionSheet,
eCloseVaultDialog,
eOnLoadNote,
eOpenVaultDialog,
eUpdateNoteInEditor
eOpenVaultDialog
} from "../../../utils/events";
import { deleteItems } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
@@ -128,7 +129,7 @@ export class VaultDialog extends Component {
: this.state.copyNote
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.goToEditor
? "Unlock note to open it in editor."
? "Unlock note to open it in editor. 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.");
}
@@ -241,6 +242,16 @@ export class VaultDialog extends Component {
});
return;
}
if (this.password && this.password.length < 3) {
ToastManager.show({
heading: "Password too short",
message: "Password must be longer than 3 characters.",
type: "error",
context: "local"
});
return;
}
if (!this.state.novault) {
if (this.password !== this.confirmPassword) {
@@ -337,26 +348,7 @@ export class VaultDialog extends Component {
let verified = await db.user.verifyPassword(this.password);
if (!(await db.user.getUser())) verified = true;
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);
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
eSendEvent("vaultUpdated");
this.setState({
loading: false
@@ -383,22 +375,7 @@ export class VaultDialog extends Component {
loading: true
});
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);
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
this.setState({
loading: false
});
@@ -427,10 +404,9 @@ export class VaultDialog extends Component {
return;
} else {
await db.vault.add(this.state.note.id);
console.log("update note event...");
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
if (this.state.note.id === editorController.current?.note?.id) {
eSendEvent(eClearEditor);
}
this.close();
ToastManager.show({
message: "Note locked successfully",
@@ -535,7 +511,9 @@ export class VaultDialog extends Component {
}
if (this.state.note?.id) {
await db.vault.add(this.state.note.id);
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
if (this.state.note.id === editorController.current?.note?.id) {
eSendEvent(eClearEditor);
}
this.setState({
loading: false
});
@@ -565,7 +543,6 @@ export class VaultDialog extends Component {
type: "success",
context: "global"
});
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
})
.catch((e) => {
@@ -586,7 +563,7 @@ export class VaultDialog extends Component {
}
async _copyNote(note) {
Clipboard.setString((await convertNoteToText(note, true)) || "");
Clipboard.setString(await convertNoteToText(note));
ToastManager.show({
heading: "Note copied",
type: "success",
@@ -602,7 +579,7 @@ export class VaultDialog extends Component {
await Share.open({
heading: "Share note",
failOnCancel: false,
message: (await convertNoteToText(note)) || ""
message: await convertNoteToText(note)
});
} catch (e) {
console.error(e);
@@ -891,10 +868,10 @@ export class VaultDialog extends Component {
title="Biometric unlocking"
iconColor={
this.state.biometricUnlock
? colors.selected.accent
? colors.selected.icon
: colors.primary.icon
}
type={this.state.biometricUnlock ? "transparent" : "plain"}
type={this.state.biometricUnlock ? "transparent" : "selected"}
/>
) : null}
</View>

View File

@@ -23,7 +23,10 @@ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Dialog } from "../dialog";
import { Issue } from "../sheets/github/issue";
const error = (stack: string, component: string) => `
const error = (
stack: string,
component: string
) => `Please let us know what happened. What steps we can take to reproduce the issue here.
_______________________________
Stacktrace: In ${component}::${stack}`;
@@ -36,9 +39,9 @@ class ExceptionHandler extends React.Component<{
error: Error | null;
hasError: boolean;
} = {
hasError: false,
error: null
};
hasError: false,
error: null
};
static getDerivedStateFromError(error: Error) {
return { hasError: true, error: error };
}

View File

@@ -33,7 +33,6 @@ import { eScrollEvent } from "../../utils/events";
import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus";
import { Title } from "./title";
import { useNavigation } from "@react-navigation/native";
type HeaderRightButton = {
title: string;
@@ -67,8 +66,6 @@ export const Header = ({
hasSearch?: boolean;
onSearch?: () => void;
}) => {
const navigation = useNavigation();
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [borderHidden, setBorderHidden] = useState(true);

View File

@@ -54,7 +54,7 @@ export const LeftMenus = ({
return isTablet && !canGoBack ? null : (
<IconButton
testID={notesnook.ids.default.header.buttons.left}
style={{
customStyle={{
justifyContent: "center",
alignItems: "center",
height: 40,

View File

@@ -23,6 +23,8 @@ import { Platform, StyleSheet, View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import Menu from "react-native-reanimated-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import Navigation from "../../services/navigation";
import SearchService from "../../services/search";
import {
HeaderRightButton,
RouteName
@@ -61,7 +63,7 @@ export const RightMenus = ({
testID="icon-search"
name="magnify"
color={colors.primary.paragraph}
style={styles.rightBtn}
customStyle={styles.rightBtn}
/>
) : null}
@@ -71,7 +73,7 @@ export const RightMenus = ({
testID={notesnook.ids.default.addBtn}
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
iconSize={SIZE.xl}
type="accent"
type="shade"
hitSlop={{
top: 10,
right: 10,
@@ -82,7 +84,7 @@ export const RightMenus = ({
marginLeft: 10,
width: 32,
height: 32,
borderRadius: 100,
borderRadius: 5,
paddingHorizontal: 0,
borderWidth: 1,
borderColor: colors.primary.accent
@@ -108,7 +110,7 @@ export const RightMenus = ({
}}
name="dots-vertical"
color={colors.primary.paragraph}
style={styles.rightBtn}
customStyle={styles.rightBtn}
/>
}
>
@@ -119,7 +121,7 @@ export const RightMenus = ({
justifyContent: "flex-start",
borderRadius: 0
}}
type="plain"
type="gray"
buttonType={{
text: contextMenuColors.primary.paragraph
}}

View File

@@ -19,12 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Sodium from "@ammarahmed/react-native-sodium";
import dataurl from "@notesnook/core/dist/utils/dataurl";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";
import ImageViewer from "react-native-image-zoom-viewer";
import { db } from "../../common/database";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { cacheDir } from "../../common/filesystem/utils";
import {
@@ -34,13 +32,13 @@ import {
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
const ImagePreview = () => {
const { colors } = useThemeColors("dialog");
const [visible, setVisible] = useState(false);
const [image, setImage] = useState<string>();
const [loading, setLoading] = useState(false);
const imageRef = useRef<ImageAttributes>();
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
@@ -50,12 +48,11 @@ const ImagePreview = () => {
}, []);
const open = async (image: ImageAttributes) => {
imageRef.current = image;
setVisible(true);
setLoading(true);
setTimeout(async () => {
let hash = image.hash;
if (!hash && image.src && dataurl.toObject(image.src)) {
if (!hash && dataurl.toObject(image.src)) {
const data = dataurl.toObject(image.src);
if (!data) return;
hash = await Sodium.hashFile({
@@ -63,9 +60,6 @@ const ImagePreview = () => {
type: "base64",
uri: ""
});
if (imageRef.current) {
imageRef.current.hash = hash;
}
}
if (!hash) return;
//@ts-ignore // FIX ME
@@ -86,13 +80,7 @@ const ImagePreview = () => {
return (
visible && (
<BaseDialog
background="black"
animation="slide"
visible={true}
onRequestClose={close}
transparent
>
<BaseDialog animation="slide" visible={true} onRequestClose={close}>
<View
style={{
width: "100%",
@@ -113,21 +101,6 @@ const ImagePreview = () => {
color={colors.primary.accent}
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>
) : (
<ImageViewer
@@ -144,12 +117,13 @@ const ImagePreview = () => {
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 50,
paddingHorizontal: 24,
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
marginTop: Platform.OS === "android" ? 30 : 0
paddingTop: Platform.OS === "android" ? 30 : 0
}}
>
<IconButton
@@ -163,7 +137,7 @@ const ImagePreview = () => {
)}
imageUrls={[
{
url: image as string
url: image
}
]}
/>

View File

@@ -108,7 +108,7 @@ export const WelcomeNotice = () => {
eSendEvent(eCloseLoading);
}}
iconSize={20}
type="plain"
type="gray"
iconPosition="right"
icon="chevron-right"
height={25}

View File

@@ -19,6 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { View } from "react-native";
import React from "react";
export const Footer = ({ height = 150 }) => {
return <View style={{ height: height }} />;
export const Footer = () => {
return <View style={{ height: 150 }} />;
};

View File

@@ -95,7 +95,7 @@ export const NotebookHeader = ({
style={{
flexShrink: 1
}}
size={SIZE.lg}
size={SIZE.xxl}
>
{notebook.title}
</Heading>
@@ -109,12 +109,12 @@ export const NotebookHeader = ({
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
onPress={onPinNotebook}
tooltipText={"Create shortcut in side menu"}
style={{
customStyle={{
marginRight: 15,
width: 40,
height: 40
}}
type="transparent"
type={isPinnedToMenu ? "grayBg" : "grayBg"}
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
@@ -123,9 +123,9 @@ export const NotebookHeader = ({
onPress={onEditNotebook}
tooltipText="Edit this notebook"
name="pencil"
type="transparent"
type="grayBg"
color={colors.primary.icon}
style={{
customStyle={{
width: 40,
height: 40
}}
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
</View>
{notebook.description ? (
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
{notebook.description}
</Paragraph>
) : null}
@@ -148,6 +148,10 @@ export const NotebookHeader = ({
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{/* {notebook.topics.length === 1
? "1 topic"
: `${notebook.topics.length} topics`}
,{" "} */}
{notebook && totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1

View File

@@ -25,7 +25,7 @@ import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enab
import { presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { RouteName } from "../../../stores/use-navigation-store";
import { getContainerBorder } from "../../../utils/colors";
import { ColorValues } from "../../../utils/colors";
import { GROUP } from "../../../utils/constants";
import { SIZE } from "../../../utils/size";
import Sort from "../../sheets/sort";
@@ -80,8 +80,7 @@ export const SectionHeader = React.memo<
backgroundColor: colors.secondary.background,
alignSelf: "center",
borderRadius: 5,
marginVertical: 5,
...getContainerBorder(colors.secondary.background, 0.8)
marginVertical: 5
}}
>
<TouchableOpacity
@@ -96,9 +95,13 @@ export const SectionHeader = React.memo<
}}
>
<Heading
color={color || colors.primary.accent}
color={
ColorValues[color?.toLowerCase() as keyof typeof ColorValues] ||
colors.primary.accent
}
size={SIZE.sm}
style={{
minWidth: 60,
alignSelf: "center",
textAlignVertical: "center"
}}
@@ -113,7 +116,7 @@ export const SectionHeader = React.memo<
alignItems: "center"
}}
>
{index === 0 ? (
{index === 0 && (
<>
<Button
onPress={() => {
@@ -122,6 +125,7 @@ export const SectionHeader = React.memo<
component: <Sort screen={screen} type={dataType} />
});
}}
hidden={screen === "Reminders"}
tooltipText="Change sorting of items in list"
title={groupBy}
icon={
@@ -141,12 +145,12 @@ export const SectionHeader = React.memo<
? 10
: 0
}}
type="plain"
type="gray"
iconPosition="right"
/>
<IconButton
style={{
customStyle={{
width: 25,
height: 25
}}
@@ -175,7 +179,7 @@ export const SectionHeader = React.memo<
size={SIZE.lg - 2}
/>
</>
) : null}
)}
</View>
</View>
);

View File

@@ -33,12 +33,11 @@ import { notesnook } from "../../../../e2e/test.ids";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import NotebookScreen from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { useEditorStore } from "../../../stores/use-editor-store";
import useNavigationStore from "../../../stores/use-navigation-store";
import { useRelationStore } from "../../../stores/use-relation-store";
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 { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
@@ -46,6 +45,7 @@ import { ReminderTime } from "../../ui/reminder-time";
import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
type NoteItemProps = {
item: Note | BaseTrashItem<Note>;
@@ -73,9 +73,8 @@ const NoteItem = ({
locked,
noOpen = false
}: NoteItemProps) => {
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
const isEditingNote = useEditorStore(
(state) => state.currentEditingNote === item.id
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(
@@ -118,8 +117,8 @@ const NoteItem = ({
tooltipText={item.title}
key={item.id}
height={25}
icon="book-outline"
type="secondary"
icon={item.type === "notebook" ? "bookmark" : "book-outline"}
type="grayBg"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
textStyle={{
@@ -128,6 +127,8 @@ const NoteItem = ({
style={{
borderRadius: 5,
marginRight: 5,
borderWidth: 0.5,
borderColor: primaryColors.border,
paddingHorizontal: 6,
marginBottom: 5
}}
@@ -211,31 +212,6 @@ const NoteItem = ({
color={colors.error.accent}
/>
) : 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
style={{
fontSize: SIZE.xs,
@@ -251,13 +227,12 @@ const NoteItem = ({
style={{
flexDirection: "row",
alignItems: "center",
marginRight: 6,
gap: 2
marginRight: 6
}}
>
<Icon
name="attachment"
size={SIZE.sm}
size={SIZE.md}
color={primaryColors.icon}
/>
<Paragraph
@@ -297,7 +272,7 @@ const NoteItem = ({
<Icon
testID="icon-star"
name="star-outline"
size={SIZE.sm}
size={SIZE.md}
style={{
marginRight: 6
}}
@@ -312,10 +287,9 @@ const NoteItem = ({
title={"#" + item.title}
key={item.id}
height={23}
type="plain"
type="gray"
textStyle={{
textDecorationLine: "underline",
color: colors.secondary.paragraph
textDecorationLine: "underline"
}}
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
fontSize={SIZE.xs}
@@ -397,7 +371,7 @@ const NoteItem = ({
<Icon
testID="icon-star"
name="star-outline"
size={SIZE.sm}
size={SIZE.md}
style={{
marginRight: 6
}}
@@ -423,7 +397,7 @@ const NoteItem = ({
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && Properties.present(item)}
style={{
customStyle={{
justifyContent: "center",
height: 35,
width: 35,

View File

@@ -26,14 +26,17 @@ import { DDS } from "../../../services/device-detection";
import {
eSendEvent,
hideSheet,
openVault,
presentSheet
} 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 { tabBarRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import NotePreview from "../../note-history/preview";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
import SelectionWrapper from "../selection-wrapper";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
export const openNote = async (
item: Note,
@@ -46,22 +49,51 @@ export const openNote = async (
if (!isTrash) {
note = (await db.notes.note(item.id)) as Note;
}
if (useSelectionStore.getState().selectionMode === item.type) {
const {
selectedItemsList,
selectionMode,
clearSelection,
setSelectedItem
} = useSelectionStore.getState();
if (selectItem(item)) return;
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
} else {
clearSelection();
}
return;
}
if (note.conflicted) {
eSendEvent(eShowMergeDialog, note);
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 (!note.contentId) return;
const content = await db.content.get(note.contentId as string);
presentSheet({
component: <NotePreview note={item} content={content} />
component: (
<NotePreview note={item} content={{ type: "tiptap", data: content }} />
)
});
} else {
if (note?.readonly) {
useEditorStore.getState().setReadonly(note?.readonly);
}
eSendEvent(eOnLoadNote, {
item: note
});

View File

@@ -198,7 +198,7 @@ export const NotebookItem = ({
testID={notesnook.ids.notebook.menu}
size={SIZE.xl}
onPress={() => Properties.present(item)}
style={{
customStyle={{
justifyContent: "center",
height: 35,
width: 35,

Some files were not shown because too many files have changed in this diff Show More