Compare commits

...

270 Commits

Author SHA1 Message Date
Ammar Ahmed
fe3bc9743b clipper: add safe querySelector method 2024-09-09 14:55:24 +05:00
Abdullah Atta
2f7b8d3377 web: add clear instructions on how to import from other apps 2024-09-09 13:14:17 +05:00
Abdullah Atta
983220b2f2 web: add support for importing nested notebooks 2024-09-09 13:13:46 +05:00
Ammar Ahmed
ca2848e491 mobile: release v3.0.17 2024-09-06 12:58:36 +05:00
Ammar Ahmed
48e5c4d908 mobile: fix crash in background on boot 2024-09-06 12:33:34 +05:00
Ammar Ahmed
5774dd8c60 mobile: fix crash on opening app from tile 2024-09-06 12:33:00 +05:00
Steve Divskinsy
9d65184f42 docs(help): Fix typo in is-there-an-eta.md
docs(help): Fix typo in is-there-an-eta.md - unnecessary was incorrectly spelled

Signed-off-by: Stephen Argent <stevesbrain@users.noreply.github.com>

Signed-off-by: Steve Divskinsy <stevesbrain@users.noreply.github.com>
2024-09-05 20:10:54 +05:00
Abdullah Atta
395b2750e8 web: increase table viewport by 300px 2024-09-05 09:45:58 +05:00
Abdullah Atta
3bfb1d57a5 web: fix attachment downloading stuck on error 2024-09-05 09:45:44 +05:00
Abdullah Atta
e59b432007 web: ensure file exists & is of the right size after encryption 2024-09-05 09:41:06 +05:00
Abdullah Atta
efbc566386 web: compare uploaded file size with actual file size after upload 2024-09-05 09:40:45 +05:00
Abdullah Atta
2576f1dd00 web: handle errors in attachment manager 2024-09-05 09:39:29 +05:00
Abdullah Atta
7d9388bd27 web: check select all checkbox when all attachments are selected 2024-09-05 09:38:57 +05:00
Abdullah Atta
1979cd2ea4 web: show progress when rechecking attachments in bulk 2024-09-05 09:38:29 +05:00
Abdullah Atta
7bd5dfb772 web: add support for configuring server urls from login screen 2024-09-04 11:07:23 +05:00
Abdullah Atta
ac7c88218b core: fix custom server urls not working in production 2024-09-04 11:06:38 +05:00
Abdullah Atta
fb642233c8 docs(help): add xkcd comic just for kicks and laughs 2024-09-04 09:36:22 +05:00
Abdullah Atta
6e2a19d7e0 docs(help): add "is there an eta for X feature" FAQ 2024-09-04 09:20:02 +05:00
Abdullah Atta
4d4993c570 docs(help): improve merge conflicts documentation 2024-09-04 09:10:35 +05:00
Abdullah Atta
916e097cb3 clipper(web): bump version to v0.3.4 2024-09-03 11:52:53 +05:00
Abdullah Atta
6def76e1bb web: bump version to 3.0.17 2024-09-03 10:39:09 +05:00
Ammar Ahmed
d93ec27c9e mobile: release v3.0.16 2024-09-02 16:29:13 +05:00
Abdullah Atta
15329597a5 core: fix notes search for queries less than 3 letters (#6489) 2024-09-02 15:29:18 +05:00
Abdullah Atta
49caf23436 web: show loader when loading next page in themes 2024-09-02 14:58:13 +05:00
Abdullah Atta
e6d8401c56 desktop: fix multiple updates running simultaneously 2024-09-02 14:21:13 +05:00
Abdullah Atta
11a5e8c180 core: delete all relations with fromId/toId set to null 2024-09-02 14:20:58 +05:00
Abdullah Atta
5bd7a081c1 core: do not allow invalid reference to be added in relations 2024-09-02 14:20:58 +05:00
Abdullah Atta
a740a5db81 clipper(web): fix tags not assigned to clip 2024-09-02 14:20:58 +05:00
Abdullah Atta
a61f2efacf web: fix type errors 2024-09-02 13:43:05 +05:00
Brogio
51a253f3e2 mobile: fix typo in settings (#6485)
Fix typo

Signed-off-by: Brogio <contact@brog.io>
2024-09-02 12:18:49 +05:00
Abdullah Atta
19ed753079 clipper(web): only clip images if user is premium 2024-08-29 12:56:10 +05:00
Abdullah Atta
4a85c49f5d desktop: remove sodium-native package for now 2024-08-28 15:08:52 +05:00
Abdullah Atta
16dd4b3359 web: bump version to 3.0.16 2024-08-28 14:45:31 +05:00
Abdullah Atta
9279c671ac desktop: fix ft.dirname is not a function for "good" 2024-08-28 14:44:40 +05:00
Abdullah Atta
225b21977e crypto: remove libsodium-sumo patch 2024-08-28 13:46:29 +05:00
Abdullah Atta
026b9212c4 web: bump version to v3.0.15 2024-08-28 13:33:56 +05:00
Abdullah Atta
5ce59c6125 desktop: fix ft.dirname is not a function error 2024-08-28 13:32:38 +05:00
Ammar Ahmed
b7c81a956c mobile: release v3.0.15 2024-08-28 12:50:12 +05:00
Abdullah Atta
873f976fd3 crypto: fix ft.dirname is not a function 2024-08-28 12:49:14 +05:00
Ammar Ahmed
7d8a16c463 mobile: make sure settings get reset properly on logout 2024-08-28 10:29:05 +05:00
Ammar Ahmed
f5841985fa mobile: hide select folder button on ios 2024-08-28 10:29:05 +05:00
Ammar Ahmed
da86cbbe32 mobile: fix password input not focused automatically on login 2024-08-28 10:29:05 +05:00
Ammar Ahmed
ed115d3f9e mobile: set encrypted backups on signup 2024-08-28 10:29:05 +05:00
Ammar Ahmed
e52e74523a mobile: fix boot task 2024-08-28 10:29:05 +05:00
Ammar Ahmed
7e43c3b373 mobile: fix background sync 2024-08-28 10:29:05 +05:00
Ammar Ahmed
e6db186b62 mobile: fix automatic backups 2024-08-28 10:29:05 +05:00
Ammar Ahmed
998e62ade7 mobile: fix file picker does not attach file when tabId=0 2024-08-28 10:29:05 +05:00
Abdullah Atta
ac16bc74fc web: fix cache file store 2024-08-27 18:07:42 +05:00
Abdullah Atta
2aee26b082 web: restore attachment files even if they are already uploaded 2024-08-27 18:07:27 +05:00
Abdullah Atta
a188920d96 web: show but disable automatic backup settings for basic users 2024-08-27 18:07:11 +05:00
Abdullah Atta
1f3defc28c web: bump version to 3.0.14 2024-08-27 16:20:12 +05:00
Ammar Ahmed
7aeeabfb3a mobile: run sync on startup 2024-08-27 16:06:11 +05:00
Abdullah Atta
6641d3e625 web: prevent multiple simultaneous syncs 2024-08-27 16:06:11 +05:00
Abdullah Atta
72031617eb web: run sync immediately on init 2024-08-27 16:06:11 +05:00
Abdullah Atta
78090b700b web: remove debouncer for page visibility and connection changes 2024-08-27 16:06:11 +05:00
Abdullah Atta
f2f8d81b98 core: do not run sync before connecting sse 2024-08-27 16:06:11 +05:00
Abdullah Atta
84d68ca67b core: add more tests 2024-08-27 16:04:51 +05:00
Abdullah Atta
6368e47712 clipper(web): bump version to 0.3.3 2024-08-27 13:23:37 +05:00
Abdullah Atta
0f17b01a74 desktop: disable native crypto 2024-08-27 13:07:09 +05:00
Abdullah Atta
025e23356a editor: fix crash on drag dropping an embed node 2024-08-27 13:03:20 +05:00
Muhammad Ali
ac85002dff editor: fix crash when adding tasklist using md in heading or other nodes (#5923)
* editor: fixed adding tasklist using md in heading

* editor: reset nodes before converting them to a task list

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-08-27 12:49:19 +05:00
ChampionBuffalo
3a23e4fbaf web: Fix isNarrow overriding user-preference of navpane
Signed-off-by: ChampionBuffalo <49675578+ChampionBuffalo1@users.noreply.github.com>
2024-08-27 12:36:48 +05:00
Muhammad Ali
3547663af8 docs: add merge-conflicts file to help (#6420)
* Update merge-conflicts.md

edited merge conflict file

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* Update merge-conflicts.md

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>

* docs(help): update merge conflicts

* docs: added merge-conflicts file

---------

Signed-off-by: Muhammad Ali <alihamuh@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-08-23 16:43:41 +05:00
Abdullah Atta
c5dfc02085 web: minor refactors 2024-08-21 13:14:17 +05:00
Ammar Ahmed
bb9a6431bf mobile: remove telemetry 2024-08-20 15:08:41 +05:00
Abdullah Atta
624d3d7ce8 web: remove unused analytics import 2024-08-20 15:08:41 +05:00
Abdullah Atta
170b0aa49a web: get rid of all telemetry & analytics code 2024-08-20 15:08:41 +05:00
Muhammad Ali
07cfd74506 Delete docs/help/contents/merge-conflict.md
Signed-off-by: Muhammad Ali <alihamuh@gmail.com>
2024-08-20 14:56:56 +05:00
Muhammad Ali
1d40054ac0 Create merge-conflict.md
Signed-off-by: Muhammad Ali <alihamuh@gmail.com>
2024-08-20 14:56:09 +05:00
Muhammad Ali
5cd37f4b64 Create merge-conflicts.md
Signed-off-by: Muhammad Ali <alihamuh@gmail.com>
2024-08-20 14:53:51 +05:00
Tai Lam
25675f71e8 help: Capitalize all instances of "URL" (#6402)
Capitalize all instances of "URL"

Signed-off-by: Tai Lam <47955724+taivlam@users.noreply.github.com>
2024-08-20 12:55:27 +05:00
Ammar Ahmed
202534fca9 mobile: correctly handle error on backup failed 2024-08-19 10:51:14 +05:00
Ammar Ahmed
a57558d8ab mobile: fix restoring backups 2024-08-19 10:50:44 +05:00
Ammar Ahmed
f1c201dca9 core: fix reset attachment failed status 2024-08-19 10:50:44 +05:00
Ammar Ahmed
a73bff83fa mobile: bump version 2024-08-17 17:59:44 +05:00
Abdullah Atta
e4428299bc web: fix trial expired reminder dialog shown again and again 2024-08-16 18:10:35 +05:00
Abdullah Atta
487567ffbc web: upgrade paddle checkout code 2024-08-16 18:06:07 +05:00
Abdullah Atta
efd83e6de3 web: add debug logging to checkout 2024-08-16 11:22:13 +05:00
Ammar Ahmed
a07230f707 mobile: remove old patches 2024-08-16 10:52:16 +05:00
Abdullah Atta
35a2e11afd web: improve toast shown to basic users on using pro features 2024-08-16 10:28:39 +05:00
Abdullah Atta
cf4a7a3d34 core: limit notebooks to 20 on basic plan 2024-08-16 10:28:39 +05:00
Abdullah Atta
86e661ccd6 core: do not allow creating colors in basic plan 2024-08-16 10:28:39 +05:00
Abdullah Atta
e7be9b35ed core: limit tags to 5 on basic plan 2024-08-16 10:28:39 +05:00
Muhammad Ali
53a367b0b2 clipper(web): fix web clipper not capturing images (#6348)
* clipper: added config settings for images

* clipper(web): enable image clipping

* clipper(web): fix clipper not respecting cors proxy setting

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-08-16 10:28:14 +05:00
Ammar Ahmed
b6206e2ca7 mobile: take backup before logout 2024-08-14 12:53:57 +05:00
Abdullah Atta
a910c819f2 web: fix error text ui 2024-08-14 12:53:57 +05:00
Abdullah Atta
6dd7a4960f web: show warning if there are unsynced changes on logout 2024-08-14 12:53:57 +05:00
Abdullah Atta
c4e15b8f90 web: ask to create backup before logout in all cases 2024-08-14 12:53:57 +05:00
Abdullah Atta
f458a4b86a web: check unsynced changes and ask for backup before logout 2024-08-14 12:53:57 +05:00
Abdullah Atta
8a44a7b99a core: add api for checking unsynced changes 2024-08-14 12:53:57 +05:00
Ammar Ahmed
25065c01a0 mobile: Fix apk size increase in v3.0.13 2024-08-14 12:19:42 +05:00
Abdullah Atta
87ae9e86e7 editor: remove 500ms debounce from image observer 2024-08-14 09:18:26 +05:00
Abdullah Atta
782c53b622 web: impl chunkSize method for faster file size calculation 2024-08-14 09:18:26 +05:00
Abdullah Atta
cb52b390ab fs: add chunkSize method for faster file size calculation 2024-08-14 09:18:26 +05:00
Abdullah Atta
5a49941003 core: cache attachment key for faster attachment reading 2024-08-14 09:18:26 +05:00
Ammar Ahmed
6797b9b621 mobile: prompt before restoring a backup 2024-08-13 15:47:10 +05:00
Ammar Ahmed
031a1b31b4 mobile: fix backup success toast 2024-08-13 15:40:10 +05:00
Ammar Ahmed
4e5762be45 mobile: check server compatible 2024-08-13 11:19:47 +05:00
Abdullah Atta
7a6ba5d1d1 web: check server version compatibility when testing connection 2024-08-13 11:19:47 +05:00
Abdullah Atta
cc3ccd041d core: add function for checking compability with server 2024-08-13 11:19:47 +05:00
Ammar Ahmed
31453dc098 mobile: server configuration settings 2024-08-13 11:19:47 +05:00
Abdullah Atta
2e84099c80 web: fix typo 2024-08-13 11:19:47 +05:00
Abdullah Atta
5bbc0cd452 web: add support for changing server urls 2024-08-13 11:19:47 +05:00
Abdullah Atta
22aae401ba web: improve checks before download & upload of attachment 2024-08-13 11:09:03 +05:00
Ammar Ahmed
97f135cd07 mobile: fix attachment downloading 2024-08-13 11:09:03 +05:00
Abdullah Atta
976f933233 web: check for s3 errors on downloading attachment 2024-08-13 11:09:03 +05:00
Ammar Ahmed
95fe1044d6 mobile: clean temp backup folders and files 2024-08-13 11:09:03 +05:00
Ammar Ahmed
677201e979 mobile: fix wording 2024-08-13 11:09:03 +05:00
Ammar Ahmed
6ec9370afb mobile: add support for attachments cache 2024-08-13 11:09:03 +05:00
Abdullah Atta
bea0f1ab0e core: fix conflicts being shown on non-conflicted notes 2024-08-13 11:09:03 +05:00
Abdullah Atta
5357be4741 web: cancel attachments caching on disabling offline mode 2024-08-13 11:09:03 +05:00
Abdullah Atta
02fdec74ce web: fix bulk exists check for files 2024-08-13 11:09:03 +05:00
Abdullah Atta
dbc907ceeb web: write downloading files to temporary location before moving 2024-08-13 11:09:03 +05:00
Abdullah Atta
7bb53a5e0c fs: add support for moving files 2024-08-13 11:09:03 +05:00
Abdullah Atta
69cb24149d core: fix progress when bulk downloading files 2024-08-13 11:09:03 +05:00
Abdullah Atta
438d412ae9 web: fix "too much recursion" error 2024-08-13 11:09:03 +05:00
Abdullah Atta
1b71edef45 web: change offline mode api 2024-08-13 11:09:03 +05:00
Abdullah Atta
a4c0e8412b fs: add support for listing all files 2024-08-13 11:09:03 +05:00
Abdullah Atta
fbcb8675ca core: optimize attachment caching in offline mode 2024-08-13 11:09:03 +05:00
Ammar Ahmed
fbf5c82d04 mobile: update wording 2024-08-13 11:09:03 +05:00
Ammar Ahmed
fa40c539ed mobile: fix restoring backup files 2024-08-13 11:09:03 +05:00
Ammar Ahmed
f3218fdc31 mobile: fix restoring backup 2024-08-13 11:09:03 +05:00
Ammar Ahmed
d12ed967e3 mobile: add support for offline mode, backup with attachments 2024-08-13 11:09:03 +05:00
Abdullah Atta
b6021dc76b web: fix backup tests for good 2024-08-13 11:09:03 +05:00
Abdullah Atta
f95442a828 web: fix backup tests 2024-08-13 11:09:03 +05:00
Abdullah Atta
846aae312e core: fix backup tests 2024-08-13 11:09:03 +05:00
Abdullah Atta
68fdbcb7fc web: better status reports when taking backups in background 2024-08-13 11:09:03 +05:00
Abdullah Atta
b938c58b67 web: take automatic backups in background 2024-08-13 11:09:03 +05:00
Abdullah Atta
759fba06ae web: add options to create backups with attachments 2024-08-13 11:09:03 +05:00
Abdullah Atta
10e24ae632 web: add support for backup/restore of attachments 2024-08-13 11:09:03 +05:00
Abdullah Atta
341032c884 core: add support for including attachments in backups 2024-08-13 11:09:03 +05:00
Abdullah Atta
aa1fa0ca81 crypto: throw error if chunk decryption fails 2024-08-13 11:09:03 +05:00
Abdullah Atta
398432efde web: add setting to toggle full offline mode 2024-08-13 11:09:03 +05:00
Abdullah Atta
57ddaafc16 core: download all attachments when full offline mode is on 2024-08-13 11:09:03 +05:00
Abdullah Atta
e4a755c69f web: optimize attachment uploading 2024-08-13 11:09:03 +05:00
Abdullah Atta
053f70cdb4 fs: get rid of chunks property in file metadata 2024-08-13 11:09:03 +05:00
Abdullah Atta
cdcdc43cad web: fix encryption progress when attaching files 2024-08-13 11:09:03 +05:00
Abdullah Atta
f1111191b1 clipper: ignore stylesheets that throw cors error 2024-08-13 11:07:20 +05:00
Abdullah Atta
529e2a4dfd clipper(web): move to react 18 2024-08-13 11:07:20 +05:00
Abdullah Atta
d88d07a2b0 core: fix Cannot read properties of null (reading 'length') in migration 2024-08-11 11:48:19 +05:00
Abdullah Atta
197ff36c20 web: only show sync status in placeholder on first sync 2024-08-11 11:19:15 +05:00
Ammar Ahmed
d94474de79 mobile: fix android crash api 34 2024-08-10 16:29:05 +05:00
Ammar Ahmed
fcf0e0c531 mobile: update Podfile.lock 2024-08-10 12:35:34 +05:00
Ammar Ahmed
24048ebfbe mobile: remove USE_FULL_SCREEN_INTENT 2024-08-10 11:57:57 +05:00
Ammar Ahmed
5ae8cdd5b9 mobile: fix build errors 2024-08-10 11:31:37 +05:00
Ammar Ahmed
74187b335f ci: set java 17
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-08-09 15:26:12 +05:00
Ammar Ahmed
16fc689299 mobile: release v3.0.13 2024-08-09 15:01:19 +05:00
Ammar Ahmed
578a9de680 mobile: fix ios build 2024-08-09 12:25:59 +05:00
Ammar Ahmed
62d475b2ef mobile: upgrade to react native 0.74.4 2024-08-09 12:25:59 +05:00
Ammar Ahmed
69c902c6c1 mobile: fix title input changes not detected 2024-08-09 12:25:14 +05:00
Abdullah Atta
c8c1028cbc core: fix least relevant results shown on top 2024-08-09 11:52:55 +05:00
Abdullah Atta
313807c384 web: remove Report an issue button from status bar 2024-08-09 11:47:56 +05:00
Abdullah Atta
5dc0e15094 web: fix last change in editor not getting saved 2024-08-09 10:35:53 +05:00
Ammar Ahmed
12c5b6d227 mobile: release v3.0.12 2024-07-29 10:06:21 +05:00
Ammar Ahmed
364ae51e36 mobile: fix backup share button not working 2024-07-29 09:53:20 +05:00
Ammar Ahmed
18947e0c93 mobile: fix missing fonts in share sheet 2024-07-29 09:43:37 +05:00
François Martin
3880db4339 global: clarify explanations for force push and force pull (#6255)
Signed-off-by: François Martin <f.martin@fastmail.com>
2024-07-29 09:38:30 +05:00
Ammar Ahmed
ae5b0b6fcc mobile: fix share sheet not loading on ios 2024-07-29 09:25:46 +05:00
Abdullah Atta
2c729f90cc global: update lockfiles 2024-07-27 13:30:25 +05:00
Ammar Ahmed
800514d769 mobile: fix position of password input 2024-07-27 12:49:57 +05:00
Ammar Ahmed
b03b9482dc mobile: app stuck after unlock 2024-07-27 12:49:57 +05:00
Abdullah Atta
9019490dbd web: bump version to v3.0.13 2024-07-27 11:16:42 +05:00
Abdullah Atta
4a3e23c288 web: fix reminder can't be set in january 2024-07-27 11:16:09 +05:00
Ammar Ahmed
1800ebea2d mobile: fix keyboard blocks continue button on applock 2024-07-27 11:12:09 +05:00
Ammar Ahmed
a708c9e9eb mobile: fix home shortcut not working 2024-07-27 10:56:09 +05:00
Abdullah Atta
2aeb875fec web: fix crash on moving temporary tab 2024-07-27 10:08:38 +05:00
Abdullah Atta
a8164e9f6c web: enter in title should focus editor 2024-07-27 09:46:37 +05:00
Abdullah Atta
0ee8e738e5 web: fix attachments ui in properties 2024-07-27 09:46:23 +05:00
Abdullah Atta
8fdef2ac7d web: fix attachments flicker in editor note properties 2024-07-27 09:46:23 +05:00
Abdullah Atta
920b1d91f3 web: bump version to 3.0.12 2024-07-22 15:42:18 +05:00
Abdullah Atta
0ad382aced web: fix incorrect attachment upload progress 2024-07-22 15:30:38 +05:00
Abdullah Atta
c8aa8e37cf web: add support for clearing attachments cache 2024-07-22 15:30:21 +05:00
Abdullah Atta
9a37f7c506 web: fix image picker dialog shown for non-image attachments 2024-07-22 13:08:49 +05:00
Abdullah Atta
717750d5d4 web: subscribe to user related events before fetching user 2024-07-22 13:08:36 +05:00
Abdullah Atta
1274ecc3b5 core: refresh token on user subscription change 2024-07-22 13:08:12 +05:00
Abdullah Atta
4c2ad4567c setup: use listr2 2024-07-20 10:34:40 +05:00
Abdullah Atta
ebc01af34a global: publish packages 2024-07-20 10:34:28 +05:00
Abdullah Atta
e66d8eee13 ui: add support for ss 2024-07-20 01:16:46 +05:00
Abdullah Atta
55e6a4b65a editor: do not throw during ssr 2024-07-20 00:42:54 +05:00
Abdullah Atta
0aa1db584d editor: do not keep last line in view for readonly editor 2024-07-19 23:50:20 +05:00
Abdullah Atta
a0e6266d28 ui: fix Cannot access 'mouseMoveListenerAttached' before initialization 2024-07-19 23:26:35 +05:00
Abdullah Atta
bd0aaed863 ui: add support for ssr 2024-07-19 23:22:47 +05:00
Abdullah Atta
9410c42f29 web: fix search icon overlapping route header text 2024-07-19 12:42:18 +05:00
Abdullah Atta
80186c1812 web: fix type errors 2024-07-19 11:11:51 +05:00
Abdullah Atta
37dfda5c54 desktop: set updaterCacheDirName in app-update.yml 2024-07-19 10:52:59 +05:00
Abdullah Atta
ec83517dc5 desktop: fix update check gives no feedback in ui 2024-07-19 10:51:10 +05:00
Abdullah Atta
2eb4911592 web: bump version to 3.0.11 2024-07-18 17:41:26 +05:00
Abdullah Atta
cd0a6a8fcb editor: fix editing link adds a new link right beside it 2024-07-18 17:38:18 +05:00
Abdullah Atta
b15f192244 desktop: update esbuild 2024-07-18 16:47:08 +05:00
Abdullah Atta
406cb663c9 editor: pasting list item with single spaced paragraphs shouldn't break formatting 2024-07-18 12:02:39 +05:00
Abdullah Atta
301a30a50a editor: copying a link from a list item shouldn't add spaces at the end 2024-07-18 12:02:39 +05:00
Abdullah Atta
81e3432143 editor: fix copying multiple lists only copied the first one 2024-07-18 12:02:39 +05:00
Abdullah Atta
c43c2936ac web: use resizeobserver to wait for scroll container to resize 2024-07-18 11:56:48 +05:00
Abdullah Atta
fdea1807d2 web: fix editor scroll bar hiding sometimes on startup 2024-07-18 11:56:48 +05:00
Abdullah Atta
5439578fcf server(theme): add health endpoint 2024-07-17 15:25:52 +05:00
Abdullah Atta
e05e52f870 web: fix crash on sync 2024-07-16 17:18:25 +05:00
Abdullah Atta
1c6243d12e web: fix error on resetting database on crash 2024-07-16 17:18:25 +05:00
Abdullah Atta
a51dfb5b6e web: remove all traces of immer 2024-07-16 17:18:25 +05:00
Abdullah Atta
c98bddd35b web: use sqlite for storing editor session data 2024-07-16 17:18:25 +05:00
Abdullah Atta
1eb80ccc88 web: get rid of dialog controller
dialog controller was redundant and created unnecessary overhead.
It also coupled all the dialogs together so you couldn't import a dialog
independently. This new change isolates each dialog to its own module
while keeping the behaviour mostly the same.
2024-07-16 17:18:25 +05:00
Abdullah Atta
5a346aa7de web: fix tags & colors not updating/loading on opening a note 2024-07-16 17:18:25 +05:00
Abdullah Atta
910095a769 web: replace immer with mutative for perf reasons 2024-07-16 17:18:25 +05:00
Abdullah Atta
119059befa core: update tag on rename instead of upserting 2024-07-16 17:18:25 +05:00
Abdullah Atta
a8f5dea315 core: fix type definition 2024-07-16 17:18:25 +05:00
Abdullah Atta
ca0a4cb02c core: add localStorage like ConfigStorage based on SQLite 2024-07-16 17:18:25 +05:00
Abdullah Atta
a04cd6b8dc core: send event when directly unlinking a relation 2024-07-16 17:18:25 +05:00
Abdullah Atta
3e4ab0f6df setup: update typescript to 5.5 2024-07-16 17:18:25 +05:00
Abdullah Atta
a7fe1aca44 global: update lockfiles 2024-07-16 17:18:25 +05:00
Abdullah Atta
ff0188f602 web: fix app lock verification always succeeds 2024-07-12 14:30:37 +05:00
Ammar Ahmed
c846f8abc8 mobile: release v3.0.11 2024-07-11 12:08:15 +05:00
Abdullah Atta
8d8acfb99d web: fix crash on browsers that doesn't have asyncIterator 2024-07-11 11:55:17 +05:00
Abdullah Atta
3a5b5772ad web: bump version to 3.0.10 2024-07-10 10:44:44 +05:00
Abdullah Atta
8046932e4e web: fix tags & other items lists glitching 2024-07-10 10:43:06 +05:00
Abdullah Atta
c45aa84739 editor: fix pro popup shown on every paste 2024-07-10 09:04:40 +05:00
Abdullah Atta
d98be849e3 web: refresh editor on changing editor options 2024-07-10 09:04:40 +05:00
Abdullah Atta
727ac6d907 web: allow multi tab access when using idb vfs 2024-07-10 09:04:23 +05:00
Abdullah Atta
8302f8ba4c web: update wa-sqlite 2024-07-10 09:04:23 +05:00
Abdullah Atta
4db8b0db01 desktop: update electron-builder 2024-07-08 14:22:24 +05:00
Ammar Ahmed
3f1614ab9a mobile: fix stacktrace formatting 2024-07-08 13:58:04 +05:00
Ammar Ahmed
909d8c35f8 mobile: fix issue reporting 2024-07-08 13:52:57 +05:00
Ammar Ahmed
a73780f0b0 mobile: bump version to v3.0.10 2024-07-08 13:32:45 +05:00
Ammar Ahmed
286fdbeec0 mobile: show toast error if upload file url not resolved 2024-07-08 13:32:30 +05:00
Abdullah Atta
59099552f8 web: bump version to 3.0.9 2024-07-08 12:47:45 +05:00
Ammar Ahmed
0b6013be58 mobile: fix color popup on mobile 2024-07-08 12:07:39 +05:00
Abdullah Atta
2d93a17f34 web: fix all failing tests 2024-07-08 11:22:14 +05:00
Abdullah Atta
5ef7e39e17 web: optimize ui for mobile & tablet form factor 2024-07-08 10:24:50 +05:00
Abdullah Atta
f7de4c9084 editor: fix keyboard shortcut for internal link not working 2024-07-08 10:23:30 +05:00
Abdullah Atta
0672c864f7 web: fix drag/drop with multiple items 2024-07-08 10:23:02 +05:00
Abdullah Atta
cb3706c150 editor: fix editor options not applied 2024-07-08 10:22:25 +05:00
Abdullah Atta
08c6a99336 web: fix note toc not loading on app restart 2024-07-08 10:21:46 +05:00
Abdullah Atta
adf961fa08 editor: fix search options not respected 2024-07-08 10:21:10 +05:00
Abdullah Atta
0eb9ae47e9 web: restrict tab drag/drop to horizontal axis 2024-07-08 10:15:04 +05:00
Abdullah Atta
aa706300a4 web: fix tab not closing on single click 2024-07-04 11:14:20 +05:00
Ammar Ahmed
fd183a0e1e mobile: fix error in editor with incorrect tools data 2024-07-01 11:48:52 +05:00
Ammar Ahmed
b27cd4be0f mobile: set initial lastContentChangeTime to 0 2024-07-01 11:16:52 +05:00
Abdullah Atta
9575561832 web: fix real time editor sync not updating content 2024-07-01 11:16:52 +05:00
Ammar Ahmed
02f96f8d14 mobile: fix cannot read property jsx of undefined 2024-07-01 11:14:43 +05:00
Ammar Ahmed
3cf46e7ad1 mobile: bump deps 2024-07-01 10:12:08 +05:00
Abdullah Atta
93551aa0b7 web: improve focus item in list behavior 2024-07-01 09:51:58 +05:00
Abdullah Atta
d00c35bdaf web: fix tab focus requires 2 clicks 2024-07-01 09:51:44 +05:00
Abdullah Atta
6e6b793568 web: fix web app on mobile browsers 2024-07-01 09:51:29 +05:00
Ammar Ahmed
9e064d88c6 mobile: fix privacy screen 2024-07-01 09:48:14 +05:00
Abdullah Atta
1658ad8d18 core: fix SSE not connecting 2024-06-25 08:31:07 +05:00
Abdullah Atta
c94da3bdf0 core: use single listener for receiving pushed items 2024-06-25 08:30:41 +05:00
Abdullah Atta
1f0fc83a7c editor: remove images on paste if user is not pro 2024-06-07 10:31:02 +05:00
Abdullah Atta
a6af92c330 web: handle errors when resolving upload urls 2024-06-07 10:30:36 +05:00
Abdullah Atta
ef93ecc481 web: handle errors during bootstrapping 2024-06-07 10:30:16 +05:00
Abdullah Atta
039709822c desktop: add instructions for creating packages 2024-06-05 10:38:54 +05:00
Abdullah Atta
25c861ce77 global: publish packages to npm 2024-06-05 10:28:28 +05:00
Abdullah Atta
670f1570b5 setup: add script for publishing packages 2024-06-05 10:28:13 +05:00
Abdullah Atta
c93d82d2d6 web: bump version to 3.0.8 2024-06-03 17:34:53 +05:00
Abdullah Atta
bced20031b web: fix Please unlock the key store to get values 2024-06-03 17:31:44 +05:00
Abdullah Atta
f8bd089b51 web: bump version to 3.0.7 2024-06-03 13:32:01 +05:00
Abdullah Atta
2a4d04a26f mobile: use clearer wording on logout confirmation 2024-06-03 13:27:51 +05:00
Abdullah Atta
a0c32191f9 web: use clearer wording on logout confirmation 2024-06-03 13:27:51 +05:00
Abdullah Atta
662d59f60a web: use clearer wording on database corruption errors 2024-06-03 13:27:51 +05:00
Abdullah Atta
13befe1947 web: handle crashes during export 2024-06-03 13:27:21 +05:00
Abdullah Atta
21f8b6d557 web: allow resetting side bar order & hidden items 2024-06-03 13:08:20 +05:00
Abdullah Atta
aef53cace9 web: disable non pro items in context menu 2024-06-03 13:00:36 +05:00
Abdullah Atta
382b5b0240 web: improve web app loading performance by 10x (#5845)
* web: improve web app loading performance by 10x

* web: update test snapshots
2024-06-03 11:07:44 +05:00
Ammar Ahmed
a45b66d449 mobile: release v3.0.9 2024-06-01 14:14:01 +05:00
Ammar Ahmed
dd67b1a803 mobile: fix login 2fa sheet gets blocked by keyboard 2024-06-01 13:45:28 +05:00
Ammar Ahmed
00568ae839 mobile: add back undo/redo buttons 2024-06-01 13:21:27 +05:00
Abdullah Atta
2b2ea5717c web: bring back undo/redo buttons 2024-06-01 13:21:27 +05:00
Abdullah Atta
5672de8565 desktop: allow using native titlebar on desktop app (#5826) 2024-06-01 13:21:05 +05:00
Ammar Ahmed
1c70ad8cec web: fix tags & colors not included in imported notes (#5801)
* web: include tags and colors in imports

* web: refactors

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-06-01 11:40:16 +05:00
Abdullah Atta
0c344a2146 core: fix sqlite hangs on restoring notebook 2024-06-01 11:28:40 +05:00
Yoonjae Choi
38f69b514c web: fix readonly locked notes opening as editable (#5731)
Signed-off-by: Yoonjae Choi <dbswo9795@email.com>
2024-06-01 11:27:59 +05:00
Abdullah Atta
b502f6a08f web: fix CVE-2024-4367 2024-06-01 11:26:43 +05:00
Abdullah Atta
d68c3269d3 editor: fix extra padding around image 2024-06-01 11:26:28 +05:00
421 changed files with 26265 additions and 62522 deletions

View File

@@ -22,7 +22,7 @@ jobs:
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: "11"
java-version: "17"
architecture: "x64"
- name: Install node modules

View File

@@ -40,12 +40,35 @@ Once you are inside the `./notesnook` directory, run the preparation step:
npm install
```
Now you can finally start the desktop app:
Now you can finally start the desktop app for development:
```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,3 +1,4 @@
owner: streetwriters
repo: notesnook
provider: github
updaterCacheDirName: Notesnook

View File

@@ -1,23 +1,21 @@
{
"name": "@notesnook/desktop",
"version": "3.0.6",
"version": "3.0.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.0.6",
"version": "3.0.17",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.4",
"electron-updater": "^6.2.1",
"icojs": "^0.17.1",
"sodium-native": "^4.1.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
@@ -28,7 +26,7 @@
"chokidar": "^3.5.3",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.0",
"esbuild": "^0.23.0",
"kysely": "^0.27.3",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",
@@ -41,7 +39,8 @@
},
"../../packages/crypto": {
"name": "@notesnook/crypto",
"version": "1.1.1",
"version": "2.0.7",
"extraneous": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/sodium": "file:../sodium"
@@ -71,9 +70,9 @@
}
},
"node_modules/@electron/asar": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.9.tgz",
"integrity": "sha512-Vu2P3X2gcZ3MY9W7yH72X9+AMXwUQZEJBrsPIbX0JsdllLtoh62/Q8Wg370/DawIEVKOyfD6KtTLo645ezqxUA==",
"version": "3.2.10",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz",
"integrity": "sha512-mvBSwIBUeiRscrCeJE1LwctAriBj65eUDm0Pc11iE5gRwzkmsdbS7FnZ1XUWjpSeQWL1L5g12Fc/SchPM9DUOw==",
"dev": true,
"dependencies": {
"commander": "^5.0.0",
@@ -284,9 +283,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz",
"integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz",
"integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==",
"cpu": [
"ppc64"
],
@@ -296,13 +295,13 @@
"aix"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz",
"integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz",
"integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==",
"cpu": [
"arm"
],
@@ -312,13 +311,13 @@
"android"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz",
"integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz",
"integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==",
"cpu": [
"arm64"
],
@@ -328,13 +327,13 @@
"android"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz",
"integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz",
"integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==",
"cpu": [
"x64"
],
@@ -344,13 +343,13 @@
"android"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz",
"integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz",
"integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==",
"cpu": [
"arm64"
],
@@ -360,13 +359,13 @@
"darwin"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz",
"integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz",
"integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==",
"cpu": [
"x64"
],
@@ -376,13 +375,13 @@
"darwin"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz",
"integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz",
"integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==",
"cpu": [
"arm64"
],
@@ -392,13 +391,13 @@
"freebsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz",
"integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz",
"integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==",
"cpu": [
"x64"
],
@@ -408,13 +407,13 @@
"freebsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz",
"integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz",
"integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==",
"cpu": [
"arm"
],
@@ -424,13 +423,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz",
"integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz",
"integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==",
"cpu": [
"arm64"
],
@@ -440,13 +439,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz",
"integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz",
"integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==",
"cpu": [
"ia32"
],
@@ -456,13 +455,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz",
"integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz",
"integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==",
"cpu": [
"loong64"
],
@@ -472,13 +471,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz",
"integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz",
"integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==",
"cpu": [
"mips64el"
],
@@ -488,13 +487,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz",
"integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz",
"integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==",
"cpu": [
"ppc64"
],
@@ -504,13 +503,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz",
"integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz",
"integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==",
"cpu": [
"riscv64"
],
@@ -520,13 +519,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz",
"integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz",
"integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==",
"cpu": [
"s390x"
],
@@ -536,13 +535,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz",
"integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz",
"integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==",
"cpu": [
"x64"
],
@@ -552,13 +551,13 @@
"linux"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz",
"integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz",
"integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==",
"cpu": [
"x64"
],
@@ -568,13 +567,29 @@
"netbsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz",
"integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz",
"integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz",
"integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==",
"cpu": [
"x64"
],
@@ -584,13 +599,13 @@
"openbsd"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz",
"integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz",
"integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==",
"cpu": [
"x64"
],
@@ -600,13 +615,13 @@
"sunos"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz",
"integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz",
"integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==",
"cpu": [
"arm64"
],
@@ -616,13 +631,13 @@
"win32"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz",
"integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz",
"integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==",
"cpu": [
"ia32"
],
@@ -632,13 +647,13 @@
"win32"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz",
"integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz",
"integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==",
"cpu": [
"x64"
],
@@ -648,7 +663,7 @@
"win32"
],
"engines": {
"node": ">=12"
"node": ">=18"
}
},
"node_modules/@isaacs/cliui": {
@@ -799,10 +814,6 @@
"node": ">=10"
}
},
"node_modules/@notesnook/crypto": {
"resolved": "../../packages/crypto",
"link": true
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -969,6 +980,15 @@
"@types/node": "*"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"devOptional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/7zip-bin": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
@@ -1095,19 +1115,6 @@
"electron-builder-squirrel-windows": "24.13.3"
}
},
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/archiver": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
@@ -1418,22 +1425,9 @@
}
},
"node_modules/builder-util-runtime": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz",
"integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==",
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/builder-util/node_modules/builder-util-runtime": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
@@ -1659,31 +1653,32 @@
}
},
"node_modules/config-file-ts/node_modules/glob": {
"version": "10.3.12",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
"integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.3.tgz",
"integrity": "sha512-Q38SGlYRpVtDBPSWEylRyctn7uDeTp4NQERTLiCT1FqA9JXPYWqAVmQU6qh4r/zMM5ehxTcbaO8EjhWnvEhmyg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.10.2"
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/config-file-ts/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -1696,9 +1691,9 @@
}
},
"node_modules/config-file-ts/node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -1930,19 +1925,6 @@
"dmg-license": "^1.0.11"
}
},
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/dmg-license": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
@@ -2060,19 +2042,6 @@
"fs-extra": "^10.1.0"
}
},
"node_modules/electron-builder/node_modules/builder-util-runtime": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-publish": {
"version": "24.13.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
@@ -2088,19 +2057,6 @@
"mime": "^2.5.2"
}
},
"node_modules/electron-publish/node_modules/builder-util-runtime": {
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz",
"integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-trpc": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",
@@ -2115,11 +2071,11 @@
}
},
"node_modules/electron-updater": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz",
"integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz",
"integrity": "sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==",
"dependencies": {
"builder-util-runtime": "9.2.1",
"builder-util-runtime": "9.2.4",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
@@ -2171,41 +2127,42 @@
"optional": true
},
"node_modules/esbuild": {
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz",
"integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz",
"integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.0",
"@esbuild/android-arm": "0.20.0",
"@esbuild/android-arm64": "0.20.0",
"@esbuild/android-x64": "0.20.0",
"@esbuild/darwin-arm64": "0.20.0",
"@esbuild/darwin-x64": "0.20.0",
"@esbuild/freebsd-arm64": "0.20.0",
"@esbuild/freebsd-x64": "0.20.0",
"@esbuild/linux-arm": "0.20.0",
"@esbuild/linux-arm64": "0.20.0",
"@esbuild/linux-ia32": "0.20.0",
"@esbuild/linux-loong64": "0.20.0",
"@esbuild/linux-mips64el": "0.20.0",
"@esbuild/linux-ppc64": "0.20.0",
"@esbuild/linux-riscv64": "0.20.0",
"@esbuild/linux-s390x": "0.20.0",
"@esbuild/linux-x64": "0.20.0",
"@esbuild/netbsd-x64": "0.20.0",
"@esbuild/openbsd-x64": "0.20.0",
"@esbuild/sunos-x64": "0.20.0",
"@esbuild/win32-arm64": "0.20.0",
"@esbuild/win32-ia32": "0.20.0",
"@esbuild/win32-x64": "0.20.0"
"@esbuild/aix-ppc64": "0.23.0",
"@esbuild/android-arm": "0.23.0",
"@esbuild/android-arm64": "0.23.0",
"@esbuild/android-x64": "0.23.0",
"@esbuild/darwin-arm64": "0.23.0",
"@esbuild/darwin-x64": "0.23.0",
"@esbuild/freebsd-arm64": "0.23.0",
"@esbuild/freebsd-x64": "0.23.0",
"@esbuild/linux-arm": "0.23.0",
"@esbuild/linux-arm64": "0.23.0",
"@esbuild/linux-ia32": "0.23.0",
"@esbuild/linux-loong64": "0.23.0",
"@esbuild/linux-mips64el": "0.23.0",
"@esbuild/linux-ppc64": "0.23.0",
"@esbuild/linux-riscv64": "0.23.0",
"@esbuild/linux-s390x": "0.23.0",
"@esbuild/linux-x64": "0.23.0",
"@esbuild/netbsd-x64": "0.23.0",
"@esbuild/openbsd-arm64": "0.23.0",
"@esbuild/openbsd-x64": "0.23.0",
"@esbuild/sunos-x64": "0.23.0",
"@esbuild/win32-arm64": "0.23.0",
"@esbuild/win32-ia32": "0.23.0",
"@esbuild/win32-x64": "0.23.0"
}
},
"node_modules/escalade": {
@@ -2327,9 +2284,9 @@
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
"integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
@@ -2342,6 +2299,18 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/foreground-child/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -2470,6 +2439,7 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
@@ -2761,6 +2731,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"dependencies": {
"once": "^1.3.0",
@@ -2865,15 +2836,15 @@
"dev": true
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.1.tgz",
"integrity": "sha512-U23pQPDnmYybVkYjObcuYMk43VRlMLLqLI+RdZy8s8WV8WsxO9SnqSroKaluuvcNOdCAlauKszDwd+umbot5Mg==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -2883,9 +2854,9 @@
}
},
"node_modules/jake": {
"version": "10.8.7",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
"integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
"integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
"dev": true,
"dependencies": {
"async": "^3.2.3",
@@ -3271,6 +3242,7 @@
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
"dev": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
@@ -3334,6 +3306,12 @@
"node": ">=8"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
"dev": true
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -3353,28 +3331,37 @@
}
},
"node_modules/path-scurry": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.1.tgz",
"integrity": "sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==",
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.0.tgz",
"integrity": "sha512-bfJaPTuEiTYBu+ulDaeQ0F+uLmlfFkMgXj4cbwfuMSjgObGMzb55FMMbDvbRU0fAHZ4sLGkz2mKwcMg8Dvm8Ww==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
"node": ">=18"
}
},
"node_modules/path-scurry/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/peek-readable": {
@@ -3407,16 +3394,17 @@
}
},
"node_modules/plist": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz",
"integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
"devOptional": true,
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
},
"engines": {
"node": ">=6"
"node": ">=10.4.0"
}
},
"node_modules/pngjs": {
@@ -3773,18 +3761,6 @@
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@@ -3864,15 +3840,6 @@
"npm": ">= 3.0.0"
}
},
"node_modules/sodium-native": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz",
"integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.8.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -4172,9 +4139,9 @@
}
},
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -4216,9 +4183,9 @@
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
"integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==",
"dev": true
},
"node_modules/util-deprecate": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.6",
"version": "3.0.17",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -11,14 +11,12 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.4",
"electron-updater": "^6.2.1",
"icojs": "^0.17.1",
"sodium-native": "^4.1.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
@@ -29,7 +27,7 @@
"chokidar": "^3.5.3",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.0",
"esbuild": "^0.23.0",
"kysely": "^0.27.3",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",

View File

@@ -46,11 +46,11 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
// temporary until there's support for prebuilt binaries for linux ARM
if (os.platform() === "linux") 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 -t electron@${packageJson.devDependencies.electron}`,
// path.join(__dirname, "..", "node_modules", "sodium-native")
// );
await fs.cp(path.join(webAppPath, "build"), "build", {
recursive: true,

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 { dialog, nativeTheme, Notification, shell } from "electron";
import { app, dialog, nativeTheme, Notification, shell } from "electron";
import { AutoLaunch } from "../utils/autolaunch";
import { config, DesktopIntegration } from "../utils/config";
import { bringToFront } from "../utils/bring-to-front";
@@ -152,6 +152,10 @@ export const osIntegrationRouter = t.router({
await rm(input);
}),
restart: t.procedure.query(() => {
app.relaunch();
app.exit();
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {
@@ -191,7 +195,10 @@ export const osIntegrationRouter = t.router({
({ input: { theme, windowControlsIconColor, backgroundColor } }) => {
if (windowControlsIconColor) {
config.windowControlsIconColor = windowControlsIconColor;
if (process.platform === "win32")
if (
process.platform === "win32" &&
!config.desktopSettings.nativeTitlebar
)
globalThis.window?.setTitleBarOverlay({
symbolColor: windowControlsIconColor
});

View File

@@ -29,19 +29,37 @@ type Progress = { percent: number };
const t = initTRPC.create();
let cancellationToken: CancellationToken | undefined = undefined;
let downloadTimeout: NodeJS.Timeout | 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));
clearTimeout(downloadTimeout);
await new Promise<string[]>((resolve, reject) => {
downloadTimeout = setTimeout(async () => {
cancellationToken = new CancellationToken();
autoUpdater.isUpdaterActive();
await autoUpdater
.downloadUpdate(cancellationToken)
.then(resolve)
.catch(reject)
.finally(() => (cancellationToken = undefined));
}, 1000);
});
}),
check: t.procedure.query(async () => {
await autoUpdater.checkForUpdates().catch(console.error);
if (cancellationToken) return;
clearTimeout(downloadTimeout);
await new Promise<void>((resolve) => {
downloadTimeout = setTimeout(async () => {
await autoUpdater
.checkForUpdates()
.catch(console.error)
.finally(resolve);
}, 1000);
});
}),
toggleAutoUpdates: t.procedure
@@ -61,7 +79,18 @@ export const updaterRouter = t.router({
"update-not-available"
),
onAvailable: createSubscription<"update-available", UpdateInfo>(
"update-available"
"update-available",
() => {
if (!config.automaticUpdates) return false;
autoUpdater.emit("download-progress", {
bytesPerSecond: 0,
delta: 0,
percent: 0,
total: 100,
transferred: 0
});
return true;
}
),
onError: createSubscription("error")
});
@@ -69,10 +98,11 @@ export const updaterRouter = t.router({
function createSubscription<
TName extends keyof AppUpdaterEvents,
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
>(eventName: TName) {
>(eventName: TName, handler?: (args: TReturnType) => boolean) {
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);

View File

@@ -84,17 +84,21 @@ async function createWindow() {
format: process.platform === "win32" ? "ico" : "png"
}),
titleBarStyle: "hidden",
frame: process.platform === "win32" || process.platform === "darwin",
titleBarOverlay: {
height: 37,
color: "#00000000",
symbolColor: config.windowControlsIconColor
},
trafficLightPosition: {
x: 16,
y: 12
},
...(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,

View File

@@ -20,14 +20,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { ELECTRON_TRPC_CHANNEL } from "electron-trpc/main";
import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
import type { NNCrypto } from "@notesnook/crypto";
// import type { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
import { platform } from "os";
declare global {
var os: () => "mas" | ReturnType<typeof platform>;
var electronTRPC: RendererGlobalElectronTRPC;
var NativeNNCrypto: (new () => NNCrypto) | undefined;
// var NativeNNCrypto: (new () => NNCrypto) | undefined;
}
process.once("loaded", async () => {
@@ -40,5 +40,5 @@ process.once("loaded", async () => {
globalThis.electronTRPC = electronTRPC;
});
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
// globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());

View File

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

View File

@@ -25,7 +25,8 @@ export const DesktopIntegration = z.object({
autoStart: z.boolean().optional(),
startMinimized: z.boolean().optional(),
minimizeToSystemTray: z.boolean().optional(),
closeToSystemTray: z.boolean().optional()
closeToSystemTray: z.boolean().optional(),
nativeTitlebar: z.boolean().optional()
});
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
@@ -35,7 +36,8 @@ export const config = {
autoStart: false,
startMinimized: false,
minimizeToSystemTray: false,
closeToSystemTray: false
closeToSystemTray: false,
nativeTitlebar: false
},
privacyMode: false,
isSpellCheckerEnabled: true,

View File

@@ -38,7 +38,7 @@ DerivedData
*.ipa
*.xcuserstate
*.hprof
ios/.xcode.env.local
**/.xcode.env.local
# Android/IntelliJ
#
rn-build-deps/

View File

@@ -25,7 +25,6 @@ import { I18nManager, View } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { initializeLogger } from "./common/database/logger";
import AppLockedOverlay from "./components/app-lock-overlay";
import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
@@ -41,35 +40,25 @@ 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);
}
const App = () => {
const init = useAppEvents();
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
useEffect(() => {
initializeLogger()
.catch((e) => {
console.log(e);
})
.finally(() => {
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
SettingsService.onFirstLaunch();
setTimeout(async () => {
await Notifications.get();
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(true);
}
TipManager.init();
}, 100);
}, []);
return (
<View
style={{

View File

@@ -1,156 +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 { Platform } from "react-native";
import { MMKV } from "../database/mmkv";
import { useSettingStore } from "../../stores/use-setting-store";
const WEBSITE_ID = "3c6890ce-8410-49d5-8831-15fb2eb28a21";
const baseUrl = "https://analytics.streetwriters.co/api/collect";
const UA =
Platform.OS === "ios"
? "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1"
: `
Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36`;
/**
*
* @param {Routes} route
* @param {Routes[]} conditions
* @returns
*/
async function canUpdateAnalytics(route, conditions = []) {
if (!useSettingStore?.getState()?.settings?.telemetry) return false;
let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) {
eventsList = JSON.parse(eventsList);
}
if (eventsList && eventsList[route]) {
console.log("analytics: event already sent", route);
return false;
}
if (route !== "/welcome") {
for (let cond of conditions) {
if (!eventsList || !eventsList[cond]) {
console.log("analytics: conditions not met for event", route, cond);
return false;
}
}
}
console.log("analytics: will send event", route);
return true;
}
async function saveAnalytics(route, value = true) {
let eventsList = MMKV.getString("notesnookUserEvents");
if (eventsList) {
eventsList = JSON.parse(eventsList);
} else {
eventsList = {};
}
eventsList[route] = value;
MMKV.setString("notesnookUserEvents", JSON.stringify(eventsList));
}
/**
*@typedef {"/welcome" | "/home" | "/signup" | "/first-note" | "/account-created" | "/pro-sheet" | "/pro-plans" | "/iap-native" | "/pro-screen" | "/editor" | "/editor-toolbar" | "/properties" | "/sidemenu"} Routes
* @param {Routes} prevRoute
* @param {Routes} route
* @param {Routes[]} conditions
* @param {boolean} once
* @returns
*/
async function pageView(
route,
prevRoute = "",
conditions = ["/welcome"],
once = true
) {
if (__DEV__) return;
if (!(await canUpdateAnalytics(route, conditions)) && once) return;
let body = {
payload: {
website: WEBSITE_ID,
url: `notesnook-${Platform.OS}${prevRoute}${route}`,
referrer: `https://notesnook.com/notesnook-${Platform.OS}${prevRoute}`,
hostname: `notesnook-${Platform.OS}`,
language: "en-US",
screen: "1920x1080"
},
type: "pageview"
};
try {
let response = await fetch(baseUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": UA
},
body: JSON.stringify(body)
});
console.log("analytics: event sent", route);
await saveAnalytics(route);
return await response.text();
} catch (e) {
console.log(e);
}
}
async function sendEvent(type, value, once = true) {
if (__DEV__) return;
if (!(await canUpdateAnalytics(type)) && once) return;
let body = {
payload: {
website: WEBSITE_ID,
url: "/",
event_type: type,
event_value: value,
hostname: "notesnook-android-app",
language: "en-US",
screen: "1920x1080"
},
type: "event"
};
try {
let response = await fetch(baseUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": UA
},
body: JSON.stringify(body)
});
console.log(response.status);
return await response.text();
} catch (e) {
console.log(e);
}
}
export default {
sendEvent,
pageView,
saveAnalytics
};

View File

@@ -28,29 +28,7 @@ import filesystem from "../filesystem";
import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";
import { getDatabaseKey } from "./encryption";
database.host(
__DEV__
? {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.43.5:5264",
// AUTH_HOST: "http://192.168.43.5:8264",
// SSE_HOST: "http://192.168.43.5:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.5:9264",
// ISSUES_HOST: "http://192.168.43.5:2624"
}
: {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
import SettingsService from "../../services/settings";
export async function setupDatabase(password) {
const key = await getDatabaseKey(password);
@@ -59,6 +37,15 @@ export async function setupDatabase(password) {
console.log("Opening database with key:", !!key);
database.host({
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co",
...(SettingsService.getProperty("serverUrls") || {})
});
database.setup({
storage: Storage,
eventsource: Platform.OS === "ios" ? EventSource : AndroidEventSource,

View File

@@ -23,7 +23,9 @@ import { Platform } from "react-native";
import { setLogger } from ".";
import { RNSqliteDriver } from "./sqlite.kysely";
let loggerLoaded = false;
const initializeLogger = async () => {
if (loggerLoaded) return;
await initialize({
dialect: (name) => ({
createDriver: () => {
@@ -37,6 +39,7 @@ const initializeLogger = async () => {
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL"
});
setLogger();
loggerLoaded = true;
};
export { initializeLogger };

View File

@@ -21,15 +21,15 @@ import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import {
decrypt,
decryptMulti,
deriveCryptoKey,
encrypt,
encryptMulti,
generateCryptoKey,
getCryptoKey,
getRandomBytes,
hash,
removeCryptoKey,
decryptMulti,
encryptMulti
removeCryptoKey
} from "./encryption";
import { MMKV } from "./mmkv";

View File

@@ -20,7 +20,6 @@ import { Dimensions, Image, 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.

View File

@@ -18,24 +18,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import {
getFileNameWithExtension,
isImage,
isDocument
} from "@notesnook/core/dist/utils/filename";
import { getFileNameWithExtension } from "@notesnook/core/dist/utils/filename";
import React from "react";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { subscribe, zip } from "react-native-zip-archive";
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 { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir, exists } from "./io";
import { IOS_APPGROUPID } from "../../utils/constants";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
export const FileDownloadStatus = {
Success: 1,
@@ -211,7 +207,11 @@ export default async function downloadAttachment(
try {
await db
.fs()
.downloadFile(options.groupId || attachment.hash, attachment.hash);
.downloadFile(
options.groupId || attachment.hash,
attachment.hash,
attachment.chunkSize
);
if (!(await exists(attachment.hash))) {
DatabaseLogger.log("Attachment does not exist after download.");
return;
@@ -259,15 +259,6 @@ export default async function downloadAttachment(
});
}
if (
attachment.dateUploaded &&
!isImage(attachment.mimeType) &&
!isDocument(attachment.mimeType)
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
}
@@ -287,7 +278,6 @@ export default async function downloadAttachment(
return fileUri;
} catch (e) {
DatabaseLogger.error(e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
@@ -296,6 +286,7 @@ export default async function downloadAttachment(
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
}
DatabaseLogger.error(e);
useAttachmentStore.getState().remove(attachment.hash);
if (options.throwError) {
throw e;

View File

@@ -17,31 +17,54 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import hosts from "@notesnook/core/dist/utils/constants";
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 { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
import { ABYTES, cacheDir, getUploadedFileSize, parseS3Error } from "./utils";
export async function downloadFile(filename, data, cancelToken) {
if (!data) {
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
export async function downloadFile(filename, requestOptions, cancelToken) {
if (!requestOptions) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: No requestOptions`
);
return false;
}
DatabaseLogger.log(`Downloading ${filename}`);
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
let { url, headers, chunkSize } = requestOptions;
let tempFilePath = `${cacheDir}/${filename}_temp`;
let originalFilePath = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
DatabaseLogger.log(`File Exists already: ${filename}`);
return true;
}
const attachment = await db.attachments.attachment(filename);
const size = await getUploadedFileSize(filename);
if (size === -1) {
const error = `Uploaded file verification failed. (File hash: ${filename})`;
throw new Error(error);
}
if (size === 0) {
const error = `File length is 0. Please upload this file again from the attachment manager. (File hash: ${filename})`;
await db.attachments.markAsFailed(attachment.id, error);
throw new Error(error);
}
const totalChunks = Math.ceil(size / chunkSize);
const decryptedLength = size - totalChunks * ABYTES;
if (attachment && attachment.size !== decryptedLength) {
const error = `File length mismatch. Expected ${attachment.size} but got ${decryptedLength} bytes. Please upload this file again from the attachment manager. (File hash: ${filename})`;
await db.attachments.markAsFailed(attachment.id, error);
throw new Error(error);
}
let res = await fetch(url, {
method: "GET",
@@ -63,34 +86,51 @@ export async function downloadFile(filename, data, cancelToken) {
);
throw new Error("Unable to resolve download url");
}
let totalSize = 0;
DatabaseLogger.log(`Download starting: ${filename}`);
let request = RNFetchBlob.config({
path: path,
path: tempFilePath,
IOSBackgroundTask: true
})
.fetch("GET", downloadUrl, null)
.progress((recieved, total) => {
.progress(async (recieved, total) => {
useAttachmentStore
.getState()
.setProgress(0, total, filename, recieved, "download");
totalSize = total;
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
DatabaseLogger.log(`Download cancelled: ${filename}`);
};
let response = await request;
await fileCheck(response, totalSize);
console.log(response.info().headers);
const contentType =
response.info().headers?.["content-type"] ||
response.info().headers?.["Content-Type"];
if (contentType === "application/xml") {
const error = parseS3Error(await response.text());
throw new Error(`[${error.Code}] ${error.Message}`);
}
let status = response.info().status;
useAttachmentStore.getState().remove(filename);
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
if (!(await exists(filename))) {
throw new Error("File size mismatch");
}
return status >= 200 && status < 300;
} catch (e) {
if (e.message !== "canceled") {
if (e.message !== "canceled" && !e.message.includes("NoSuchKey")) {
ToastManager.show({
heading: "Error downloading file",
message: e.message,
@@ -106,7 +146,8 @@ export async function downloadFile(filename, data, cancelToken) {
}
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
DatabaseLogger.error(e, {
url,
headers
@@ -115,37 +156,23 @@ export async function downloadFile(filename, data, cancelToken) {
}
}
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;
}
}
export async function checkAttachment(hash) {
const internetState = await NetInfo.fetch();
const isInternetReachable =
internetState.isConnected && internetState.isInternetReachable;
if (!isInternetReachable) return { success: true };
if (!isInternetReachable) return;
const attachment = await db.attachments.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {
const size = await getUploadedFileSize(hash);
console.log("File Size", size);
if (size === -1) return { success: true };
if (size === 0) return { failed: "File length is 0." };
if (size === 0)
return {
failed: `File length is 0. Please upload this file again from the attachment manager. (File hash: ${hash})`
};
} catch (e) {
return { failed: e?.message };
}

View File

@@ -17,17 +17,22 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { checkAttachment, downloadFile, getUploadedFileSize } from "./download";
import { checkAttachment, downloadFile } from "./download";
import {
clearFileStorage,
deleteFile,
exists,
readEncrypted,
writeEncryptedBase64,
hashBase64
hashBase64,
clearCache,
deleteCacheFileByName,
deleteCacheFileByPath,
bulkExists,
getCacheSize
} from "./io";
import { uploadFile } from "./upload";
import { cancelable } from "./utils";
import { cancelable, getUploadedFileSize } from "./utils";
export default {
readEncrypted,
@@ -39,5 +44,10 @@ export default {
exists,
clearFileStorage,
getUploadedFileSize,
checkAttachment
checkAttachment,
clearCache,
deleteCacheFileByName,
deleteCacheFileByPath,
bulkExists,
getCacheSize
};

View File

@@ -20,9 +20,10 @@ 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 RNFetchBlob from "react-native-blob-util";
import { eSendEvent } from "../../services/event-manager";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
import { ABYTES, cacheDir, cacheDirOld, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
@@ -158,7 +159,35 @@ export async function migrateFilesFromCache() {
}
}
const ABYTES = 17;
export async function clearCache() {
await RNFetchBlob.fs.unlink(cacheDir).catch(console.log);
await createCacheDir();
eSendEvent("cache-cleared");
}
export async function deleteCacheFileByPath(path) {
await RNFetchBlob.fs.unlink(path).catch(console.log);
}
export async function deleteCacheFileByName(name) {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${name}`;
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(console.log);
}
export async function deleteDCacheFiles() {
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache")) {
await RNFetchBlob.fs.unlink(file).catch(console.log);
}
}
}
export async function exists(filename) {
let path = `${cacheDir}/${filename}`;
@@ -200,3 +229,29 @@ export async function exists(filename) {
}
return exists;
}
export async function bulkExists(files) {
const cacheFiles = await RNFetchBlob.fs.ls(cacheDir);
let missingFiles = files.filter((file) => !cacheFiles.includes(file));
if (Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupFiles = await RNFetchBlob.fs.ls(iosAppGroup);
missingFiles = missingFiles.filter((file) => !appGroupFiles.includes(file));
}
return missingFiles;
}
export async function getCacheSize() {
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
let total = 0;
console.log("Total files", stat.length);
stat.forEach((s) => {
total += parseInt(s.size);
});
return total;
}

View File

@@ -17,31 +17,30 @@ 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 RNFetchBlob from "react-native-blob-util";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { DatabaseLogger, db } from "../database";
import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { createCacheDir } from "./io";
import { getUploadedFileSize } from "./download";
import { cacheDir, getUploadedFileSize } from "./utils";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
export async function uploadFile(filename, requestOptions, cancelToken) {
if (!requestOptions) return false;
let { url, headers } = requestOptions;
await createCacheDir();
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
const uploadedFileSize = await getUploadedFileSize(filename);
if (uploadedFileSize === -1) {
DatabaseLogger.log("Upload verification failed.");
return false;
const error = `Uploaded file verification failed. (File hash: ${filename})`;
throw new Error(error);
}
const isUploaded = uploadedFileSize !== 0;
if (isUploaded) {
if (uploadedFileSize !== 0) {
DatabaseLogger.log(`File ${filename} is already uploaded.`);
return true;
}
@@ -50,9 +49,15 @@ export async function uploadFile(filename, data, cancelToken) {
method: "PUT",
headers
});
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");
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."
);
}
let uploadFilePath = `${cacheDir}/${filename}`;
const iosAppGroup =
@@ -110,9 +115,6 @@ export async function uploadFile(filename, data, cancelToken) {
);
let attachment = await db.attachments.attachment(filename);
if (!attachment) return result;
if (!isImage(attachment.mimeType) && !isDocument(attachment.mimeType)) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
} else {
const fileInfo = await RNFetchBlob.fs.stat(uploadFilePath);
throw new Error(
@@ -125,8 +127,10 @@ export async function uploadFile(filename, data, cancelToken) {
return result;
} catch (e) {
useAttachmentStore.getState().remove(filename);
DatabaseLogger.info(`File upload error: ${filename}, ${e}`);
DatabaseLogger.error(e);
ToastManager.error(e, "File upload failed");
DatabaseLogger.error(e, "File upload failed", {
filename
});
return false;
}
}

View File

@@ -17,10 +17,13 @@ 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 * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { DatabaseLogger, db } from "../database";
import hosts from "@notesnook/core/dist/utils/constants";
export const ABYTES = 17;
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
export const cacheDir =
@@ -34,24 +37,36 @@ export function getRandomId(prefix) {
.replace("0.", prefix || "");
}
export function extractValueFromXmlTag(code, xml) {
if (!xml.includes(code)) return `Unknown ${code}`;
return xml.slice(
xml.indexOf(`<${code}>`) + code.length + 2,
xml.indexOf(`</${code}>`)
);
}
/**
*
* @param {string | undefined} data
* @returns
*/
export function parseS3Error(data) {
const xml = typeof data === "string" ? data : null;
export async function fileCheck(response, totalSize) {
if (totalSize < 1000) {
let text = await response.text();
if (text.startsWith("<?xml")) {
let errorJson = {
Code: extractValueFromXmlTag("Code", text),
Message: extractValueFromXmlTag("Message", text)
};
throw new Error(`${errorJson.Code}: ${errorJson.Message}`);
const error = {
Code: "UNKNOWN",
Message: xml
};
try {
if (!xml) return error;
const doc = new DOMParser().parseFromString(xml, "text/xml");
const ErrorElement = doc.getElementsByTagName("Error")[0];
if (!ErrorElement) return error;
for (const child of ErrorElement.children) {
if (
child.textContent &&
(child.tagName === "Code" || child.tagName === "Message")
)
error[child.tagName] = child.textContent;
}
return error;
} catch (e) {
return error;
}
}
@@ -59,12 +74,11 @@ export function cancelable(operation) {
const cancelToken = {
cancel: () => {}
};
return (filename, { url, headers }) => {
return (filename, requestOptions) => {
return {
execute: () => operation(filename, { url, headers }, cancelToken),
execute: () => operation(filename, requestOptions, cancelToken),
cancel: async () => {
await cancelToken.cancel();
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
};
};
@@ -91,3 +105,21 @@ export async function releasePermissions(path) {
}
}
}
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;
}
}

View File

@@ -18,10 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef } from "react";
import { AppStateStatus, Platform, TextInput, View } from "react-native";
import {
AppStateStatus,
Platform,
TextInput,
useWindowDimensions,
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,
@@ -47,6 +52,7 @@ 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");
@@ -87,6 +93,7 @@ const AppLockedOverlay = () => {
const appState = useAppState();
const lastAppState = useRef<AppStateStatus>(appState);
const biometricUnlockAwaitingUserInput = useRef(false);
const { height } = useWindowDimensions();
const keyboardType = useSettingStore(
(state) => state.settings.applockKeyboardType
);
@@ -121,7 +128,6 @@ const AppLockedOverlay = () => {
);
if (unlocked) {
lockApp(false);
enabled(false);
password.current = undefined;
}
biometricUnlockAwaitingUserInput.current = false;
@@ -148,7 +154,6 @@ const AppLockedOverlay = () => {
}
lockApp(false);
enabled(false);
password.current = undefined;
} else {
ToastManager.show({
@@ -163,10 +168,6 @@ const AppLockedOverlay = () => {
};
useEffect(() => {
if (appState === "active") {
enabled(false);
}
const prevState = lastAppState.current;
lastAppState.current = appState;
@@ -192,26 +193,26 @@ const AppLockedOverlay = () => {
DatabaseLogger.info("Biometric unlock request");
onUnlockAppRequested();
}
enabled(false);
} else {
SettingsService.appEnteredBackground();
if (SettingsService.get().privacyScreen) {
enabled(true);
}
}
}, [appState, onUnlockAppRequested, appLocked]);
return appLocked ? (
<View
<KeyboardAwareScrollView
style={{
backgroundColor: colors.primary.background,
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999,
justifyContent: "center"
zIndex: 999
}}
contentContainerStyle={{
justifyContent: "center",
minHeight: height
}}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
>
<Toast context="local" />
<View
@@ -326,7 +327,7 @@ const AppLockedOverlay = () => {
</View>
</View>
</View>
</View>
</KeyboardAwareScrollView>
) : null;
};

View File

@@ -118,12 +118,14 @@ const Actions = ({
setLoading({
name: "Run file check"
});
const res = await filesystem.checkAttachment(attachment.hash);
if (res.failed) {
db.attachments.markAsFailed(attachment.id, res.failed);
setFailed(res.failed);
const result = await filesystem.checkAttachment(attachment.hash);
if (!result) return;
if (result.failed) {
db.attachments.markAsFailed(attachment.id, result.failed);
setFailed(result.failed);
ToastManager.show({
heading: "File check failed with error: " + res.failed,
heading: "File check failed with error: " + result.failed,
type: "error",
context: "local"
});

View File

@@ -122,10 +122,11 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
const attachment = (await attachments.item(i))?.item;
if (!attachment) continue;
const result = await filesystem.checkAttachment(attachment.hash);
if (!result) return;
if (result.failed) {
await db.attachments.markAsFailed(attachment.hash, result.failed);
} else {
await db.attachments.markAsFailed(attachment.id, undefined);
await db.attachments.markAsFailed(attachment.id);
}
}
refresh();
@@ -355,6 +356,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
AttachmentDialog.present = (note?: Note) => {
presentSheet({
component: () => <AttachmentDialog note={note} />
component: () => <AttachmentDialog note={note} />,
keyboardHandlerDisabled: true
});
};

View File

@@ -68,7 +68,8 @@ export const ChangePassword = () => {
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (!result) throw new Error("Failed to create backup");
if (result.error)
throw new Error(`Failed to create backup: ${result.error}`);
await db.user.clearSessions();
await db.user.changePassword(oldPassword.current, password.current);

View File

@@ -17,12 +17,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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 { useThemeColors } from "@notesnook/theme";
import Sync from "../../services/sync";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { eUserLoggedIn } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import SheetProvider from "../sheet-provider";
@@ -34,11 +38,6 @@ 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";
import { Notice } from "../ui/notice";
const LoginSteps = {
emailAuth: 1,
@@ -184,7 +183,11 @@ export const Login = ({ changeMode }) => {
defaultValue={email.current}
editable={step === LoginSteps.emailAuth && !loading}
onSubmit={() => {
passwordInputRef.current?.focus();
if (step === LoginSteps.emailAuth) {
login();
} else {
passwordInputRef.current?.focus();
}
}}
/>
@@ -243,6 +246,7 @@ export const Login = ({ changeMode }) => {
width: 250,
borderRadius: 100
}}
height={50}
fontSize={SIZE.md}
type="accent"
title={!loading ? "Continue" : null}

View File

@@ -35,6 +35,7 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { hideAuth } from "./common";
import { useSettingStore } from "../../stores/use-setting-store";
import SettingsService from "../../services/settings";
export const Signup = ({ changeMode, trial }) => {
const { colors } = useThemeColors();
@@ -77,6 +78,7 @@ export const Signup = ({ changeMode, trial }) => {
clearMessage();
setEmailVerifyMessage();
hideAuth();
SettingsService.setProperty("encryptedBackup", true);
await sleep(300);
if (trial) {
PremiumService.sheet(null, null, true);

View File

@@ -37,6 +37,7 @@ 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();
@@ -143,7 +144,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
return (
<View>
<ScrollView
keyboardShouldPersistTaps="handled"
keyboardDismissMode="interactive"
>
<View
style={{
alignItems: "center",
@@ -214,6 +218,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
code.current = value;
//onNext();
}}
onSubmitEditing={onNext}
caretHidden
inputStyle={{
fontSize: SIZE.lg,
@@ -225,6 +230,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
keyboardType={
currentMethod.method === "recoveryCode" ? "default" : "numeric"
}
enablesReturnKeyAutomatically
containerStyle={{
height: 60,
borderWidth: 0,
@@ -297,7 +303,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
</>
)}
</View>
</View>
</ScrollView>
);
};

View File

@@ -83,7 +83,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
setLoading(false);
setTimeout(() => {
passwordInputRef.current?.focus();
}, 1);
}, 500);
callback && callback(true);
}
callback && callback(false);

View File

@@ -23,6 +23,7 @@ import { AnnouncementDialog } from "../announcements";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog";
import { AppLockPassword } from "../dialogs/applock-password";
import JumpToSectionDialog from "../dialogs/jump-to-section";
import { LoadingDialog } from "../dialogs/loading";
import PDFPreview from "../dialogs/pdf-preview";
@@ -35,8 +36,7 @@ import { Expiring } from "../premium/expiring";
import SheetProvider from "../sheet-provider";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data";
import { AppLockPassword } from "../dialogs/applock-password";
import Progress from "../dialogs/progress";
const DialogProvider = () => {
const { colors } = useThemeColors();
@@ -52,7 +52,6 @@ const DialogProvider = () => {
<RecoveryKeySheet colors={colors} />
<SheetProvider />
<SheetProvider context="sync_progress" />
<RestoreDataSheet />
<ResultDialog />
<VaultDialog colors={colors} />
<RateAppSheet />
@@ -62,6 +61,7 @@ const DialogProvider = () => {
<SessionExpired />
<PDFPreview />
<JumpToSectionDialog />
<Progress />
</>
);
};

View File

@@ -32,21 +32,29 @@ const DialogContainer = ({
}: ViewProps & {
width?: any;
height?: any;
noBorder?: boolean;
}) => {
const { colors } = useThemeColors();
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={[
style,
{
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
},
restProps?.noBorder
? {}
: {
...getElevationStyle(5),
...getContainerBorder(colors.secondary.background, 0.8, 0.05)
}
]}
/>
);
};

View File

@@ -25,7 +25,7 @@ type DialogInfo = {
paragraph?: string;
positiveText?: string;
negativeText?: string;
positivePress?: (value: any) => void;
positivePress?: (...args: any[]) => void;
onClose?: () => void;
positiveType?:
| "transparent"
@@ -45,6 +45,15 @@ type DialogInfo = {
context: "global" | "local" | (string & {});
secureTextEntry?: boolean;
keyboardType?: string;
check?: {
info: string;
type?: string;
defaultValue?: boolean;
};
notice: {
text: string;
type: "alert" | "information";
};
};
export function presentDialog(data: Partial<DialogInfo>): void {

View File

@@ -37,6 +37,7 @@ import DialogHeader from "./dialog-header";
import { useCallback } from "react";
import { Button } from "../ui/button";
import { getContainerBorder } from "../../utils/colors";
import { Notice } from "../ui/notice";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
@@ -62,7 +63,8 @@ export const Dialog = ({ context = "global" }) => {
disableBackdropClosing: false,
check: {
info: "Check",
type: "transparent"
type: "transparent",
defaultValue: false
}
});
@@ -86,7 +88,7 @@ export const Dialog = ({ context = "global" }) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setChecked(false);
setChecked(data.check?.defaultValue);
values.current.inputValue = data.defaultValue;
setVisible(true);
},
@@ -180,6 +182,19 @@ export const Dialog = ({ context = "global" }) => {
</View>
) : null}
{dialogInfo?.notice ? (
<View
style={{
paddingHorizontal: 12
}}
>
<Notice
type={dialogInfo.notice.type || "information"}
text={dialogInfo.notice.text}
/>
</View>
) : null}
{dialogInfo.check ? (
<>
<Button
@@ -198,7 +213,7 @@ export const Dialog = ({ context = "global" }) => {
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={checked ? dialogInfo.check.type || "selected" : "plain"}
type={checked ? dialogInfo.check.type || "plain" : "plain"}
/>
</>
) : null}

View File

@@ -44,6 +44,7 @@ import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { sleep } from "../../../utils/time";
import { MMKV } from "../../../common/database/mmkv";
import { deleteCacheFileByPath } from "../../../common/filesystem/io";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
@@ -124,6 +125,7 @@ const PDFPreview = () => {
);
const close = () => {
deleteCacheFileByPath(pdfSource);
setPDFSource(null);
setVisible(false);
setPassword("");

View File

@@ -0,0 +1,198 @@
/*
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 { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
import { eSendEvent, eSubscribeEvent } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import BaseDialog from "../../dialog/base-dialog";
import DialogContainer from "../../dialog/dialog-container";
import { Button } from "../../ui/button";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
export type ProgressOptions = {
progress?: string;
cancelCallback?: () => void;
title?: string;
paragraph?: string;
fillBackground?: boolean;
canHideProgress?: boolean;
};
export const PROGRESS_EVENTS = {
start: "startProgress",
end: "endProgress",
update: "updateProgress"
};
export default function Progress() {
const { colors } = useThemeColors();
const [progress, setProgress] = React.useState<string | undefined>();
const [visible, setVisible] = React.useState(false);
const cancelCallback = React.useRef<() => void>();
const [data, setData] = React.useState<{
title?: string;
paragraph?: string;
fillBackground?: boolean;
canHideProgress?: boolean;
}>();
useEffect(() => {
const events = [
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
setProgress(options.progress);
cancelCallback.current = options.cancelCallback;
console.log("options", options.fillBackground);
setData({
title: options.title,
paragraph: options.paragraph,
fillBackground: options.fillBackground,
canHideProgress: options.canHideProgress
});
setVisible(true);
}),
eSubscribeEvent(PROGRESS_EVENTS.end, () => {
setProgress(undefined);
setVisible(false);
setData(undefined);
cancelCallback.current?.();
cancelCallback.current = undefined;
}),
eSubscribeEvent(PROGRESS_EVENTS.update, (options: ProgressOptions) => {
setProgress(options.progress);
if (options.cancelCallback) {
cancelCallback.current = options.cancelCallback;
}
const data: ProgressOptions = {};
if (options.title) data.title = options.title;
if (options.paragraph) data.paragraph = options.paragraph;
if (options.fillBackground)
data.fillBackground = options.fillBackground;
setData((current) => {
return {
...current,
...data
};
});
})
];
return () => {
events.forEach((event) => event?.unsubscribe());
cancelCallback.current = undefined;
};
}, []);
return !visible ? null : (
<BaseDialog
background={data?.fillBackground ? colors.primary.background : undefined}
visible
>
<DialogContainer
style={{
paddingHorizontal: 12,
paddingBottom: 10
}}
noBorder={data?.fillBackground ? true : false}
>
<Dialog context="local" />
<View
style={{
justifyContent: "center",
alignItems: "center",
paddingHorizontal: 12,
gap: 10,
paddingBottom: 20
}}
>
<Heading
style={{
textAlign: "center"
}}
color={colors.primary.paragraph}
size={SIZE.lg}
>
{data?.title}
</Heading>
<Paragraph
style={{
textAlign: "center"
}}
color={colors.secondary.paragraph}
>
{progress ? progress : data?.paragraph}
</Paragraph>
<View
style={{
flexDirection: "row",
width: 100,
paddingVertical: 12
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
/>
</View>
{data?.canHideProgress ? null : (
<Button
title={cancelCallback.current ? "Cancel" : "Hide"}
type="secondaryAccented"
onPress={() => {
if (cancelCallback.current) {
cancelCallback.current?.();
}
setVisible(false);
setProgress(undefined);
setData(undefined);
}}
width="100%"
/>
)}
</View>
</DialogContainer>
</BaseDialog>
);
}
export function startProgress(options: ProgressOptions) {
eSendEvent(PROGRESS_EVENTS.start, options);
}
export function endProgress() {
eSendEvent(PROGRESS_EVENTS.end);
}
export function updateProgress(options: ProgressOptions) {
eSendEvent(PROGRESS_EVENTS.update, options);
}

View File

@@ -23,10 +23,7 @@ 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
) => `Please let us know what happened. What steps we can take to reproduce the issue here.
const error = (stack: string, component: string) => `
_______________________________
Stacktrace: In ${component}::${stack}`;
@@ -39,9 +36,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

@@ -43,9 +43,6 @@ import { eSendEvent } from "../../services/event-manager";
const Intro = ({ navigation }) => {
const { colors } = useThemeColors();
const isTelemetryEnabled = useSettingStore(
(state) => state.settings.telemetry
);
const { width, height } = useWindowDimensions();
const deviceMode = useSettingStore((state) => state.deviceMode);
const insets = useGlobalSafeAreaInsets();
@@ -217,44 +214,6 @@ const Intro = ({ navigation }) => {
type="accent"
title="Get started"
/>
<TouchableOpacity
activeOpacity={1}
style={{
flexDirection: "row",
alignSelf: "center",
width: "90%",
marginBottom: 12,
paddingHorizontal: 12,
justifyContent: "center",
padding: 12,
maxWidth: 500
}}
onPress={() => {
SettingsService.set({ telemetry: !isTelemetryEnabled });
}}
>
<Icon
size={SIZE.md}
name={
isTelemetryEnabled ? "checkbox-marked" : "checkbox-blank-outline"
}
color={
isTelemetryEnabled ? colors.primary.accent : colors.primary.icon
}
/>
<Paragraph
style={{
flexShrink: 1,
marginLeft: 6
}}
size={SIZE.xs}
>
Help improve Notesnook by sending completely anonymized{" "}
<Heading size={SIZE.xs}>private analytics and bug reports.</Heading>
</Paragraph>
</TouchableOpacity>
</View>
</ScrollView>
);

View File

@@ -49,7 +49,8 @@ export const Card = ({ color }: { color?: string }) => {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
paddingHorizontal: 0
paddingHorizontal: 0,
width: "100%"
}}
>
<View
@@ -91,7 +92,6 @@ export const Card = ({ color }: { color?: string }) => {
<View
style={{
marginLeft: 10,
flexShrink: 1,
marginRight: 10
}}
>

View File

@@ -18,35 +18,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Debug } from "@notesnook/core/dist/api/debug";
import { getModel, getBrand, getSystemVersion } from "react-native-device-info";
import { useThemeColors } from "@notesnook/theme";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useRef, useState } from "react";
import { Linking, Platform, Text, TextInput, View } from "react-native";
import { getVersion } from "react-native-device-info";
import { useStoredRef } from "../../../hooks/use-stored-ref";
import { ToastManager, eSendEvent } from "../../../services/event-manager";
import { ToastManager } from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
import { useUserStore } from "../../../stores/use-user-store";
import { eCloseSheet } from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
const { colors } = useThemeColors();
const body = useStoredRef("issueBody", defaultBody);
const body = useStoredRef("issueBody");
const title = useStoredRef("issueTitle", defaultTitle);
const [done, setDone] = useState(false);
const user = useUserStore((state) => state.user);
const [loading, setLoading] = useState(false);
const bodyRef = useRef();
const initialLayout = useRef(false);
const issueUrl = useRef();
const onPress = async () => {
if (loading) return;
@@ -56,57 +54,24 @@ export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
try {
setLoading(true);
let issue_url = await Debug.report({
issueUrl.current = await Debug.report({
title: title.current,
body:
body.current +
`\n${defaultBody || ""}` +
`\n_______________
**Device information:**
App version: ${getVersion()}
Platform: ${Platform.OS}
Model: ${Platform.constants.Brand || ""}-${Platform.constants.Model || ""}-${
Platform.constants.Version || ""
}
Device: ${getBrand() || ""}-${getModel() || ""}-${getSystemVersion() || ""}
Pro: ${PremiumService.get()}
Logged in: ${user ? "yes" : "no"}`,
userId: user?.id
});
setLoading(false);
eSendEvent(eCloseSheet);
body.reset();
title.reset();
await sleep(300);
presentDialog({
title: "Issue reported",
paragraph: (
<Text>
You can track your issue at{" "}
<Text
style={{
textDecorationLine: "underline",
color: colors.primary.accent
}}
onPress={() => {
Linking.openURL(issue_url);
}}
>
{issue_url}.
</Text>{" "}
Please note that we will respond to your issue on the given link. We
recommend that you save it.
</Text>
),
positiveText: "Copy link",
positivePress: () => {
Clipboard.setString(issue_url);
ToastManager.show({
heading: "Issue url copied!",
type: "success",
context: "global"
});
},
negativeText: "Close"
});
setDone(true);
} catch (e) {
setLoading(false);
ToastManager.show({
@@ -124,125 +89,177 @@ Logged in: ${user ? "yes" : "no"}`,
width: "100%"
}}
>
<DialogHeader
title={issueTitle || "Report issue"}
paragraph={
issueTitle
? "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
: "Let us know if you have faced any issue/bug while using Notesnook."
}
/>
{done ? (
<>
<View
style={{
height: 250,
justifyContent: "center",
alignItems: "center",
gap: 10
}}
>
<Heading>Issue submitted</Heading>
<Paragraph
style={{
textAlign: "center"
}}
selectable={true}
>
You can track your issue at{" "}
<Paragraph
style={{
textDecorationLine: "underline",
color: colors.primary.accent
}}
onPress={() => {
Linking.openURL(issueUrl.current);
}}
>
{issueUrl.current}
</Paragraph>
. Please note that we will respond to your issue on the given
link. We recommend that you save it.
</Paragraph>
<Seperator half />
<TextInput
placeholder="Title"
onChangeText={(v) => (title.current = v)}
defaultValue={title.current}
style={{
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: 5,
padding: 12,
fontFamily: "OpenSans-Regular",
marginBottom: 10,
fontSize: SIZE.md,
color: colors.primary.heading
}}
placeholderTextColor={colors.primary.placeholder}
/>
<TextInput
ref={bodyRef}
placeholder={`Tell us more about the issue you are facing.
For example:
- Steps to reproduce the issue
- Things you have tried etc.`}
multiline
numberOfLines={5}
textAlignVertical="top"
onChangeText={(v) => (body.current = v)}
onLayout={() => {
if (initialLayout.current) return;
initialLayout.current = true;
if (body.current) {
bodyRef.current?.setNativeProps({
text: body.current,
selection: {
start: 0,
end: 0
}
});
}
}}
style={{
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: 5,
padding: 12,
fontFamily: "OpenSans-Regular",
maxHeight: 200,
fontSize: SIZE.sm,
marginBottom: 2.5,
color: colors.primary.paragraph
}}
placeholderTextColor={colors.primary.placeholder}
/>
<Paragraph
size={SIZE.xs}
color={colors.secondary.paragraph}
>{`App version: ${getVersion()} Platform: ${Platform.OS} Model: ${
Platform.constants.Brand
}-${Platform.constants.Model}-${Platform.constants.Version}`}</Paragraph>
<Seperator />
<Button
onPress={onPress}
title={loading ? null : "Submit"}
loading={loading}
width="100%"
type="accent"
/>
<Paragraph
color={colors.secondary.paragraph}
size={SIZE.xs}
style={{
marginTop: 10,
textAlign: "center"
}}
>
The information above will be publically available at{" "}
<Text
onPress={() => {
Linking.openURL("https://github.com/streetwriters/notesnook");
}}
style={{
textDecorationLine: "underline",
color: colors.primary.accent
}}
>
github.com/streetwriters/notesnook.
</Text>{" "}
If you want to ask something in general or need some assistance, we
would suggest that you{" "}
<Text
style={{
textDecorationLine: "underline",
color: colors.primary.accent
}}
onPress={async () => {
try {
await openLinkInBrowser("https://discord.gg/zQBK97EE22", colors);
} catch (e) {
console.error(e);
<Button
title="Open issue"
onPress={() => {
Linking.openURL(issueUrl.current);
}}
type="accent"
width="100%"
/>
</View>
</>
) : (
<>
<DialogHeader
title={issueTitle || "Report issue"}
paragraph={
issueTitle
? "We are sorry, it seems that the app crashed due to an error. You can submit a bug report below so we can fix this asap."
: "Let us know if you have faced any issue/bug while using Notesnook."
}
}}
>
join our community on Discord.
</Text>
</Paragraph>
/>
<Seperator half />
<TextInput
placeholder="Title"
onChangeText={(v) => (title.current = v)}
defaultValue={title.current}
style={{
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: 5,
padding: 12,
fontFamily: "OpenSans-Regular",
marginBottom: 10,
fontSize: SIZE.md,
color: colors.primary.heading
}}
placeholderTextColor={colors.primary.placeholder}
/>
<TextInput
ref={bodyRef}
multiline
placeholder={`Tell us more about the issue you are facing.
For example:
- What were you trying to do in the app?
- What did you expect to happen?
- Steps to reproduce the issue
- Things you have tried etc.`}
numberOfLines={5}
textAlignVertical="top"
onChangeText={(v) => (body.current = v)}
onLayout={() => {
if (initialLayout.current) return;
initialLayout.current = true;
if (body.current) {
bodyRef.current?.setNativeProps({
text: body.current,
selection: {
start: 0,
end: 0
}
});
}
}}
style={{
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: 5,
padding: 12,
fontFamily: "OpenSans-Regular",
maxHeight: 200,
fontSize: SIZE.sm,
marginBottom: 2.5,
color: colors.primary.paragraph
}}
placeholderTextColor={colors.primary.placeholder}
/>
<Paragraph
size={SIZE.xs}
color={colors.secondary.paragraph}
>{`App version: ${getVersion()} Platform: ${
Platform.OS
} Model: ${getBrand()}-${getModel()}-${getSystemVersion()}`}</Paragraph>
<Seperator />
<Button
onPress={onPress}
title={loading ? null : "Submit"}
loading={loading}
width="100%"
type="accent"
/>
<Paragraph
color={colors.secondary.paragraph}
size={SIZE.xs}
style={{
marginTop: 10,
textAlign: "center"
}}
>
The information above will be publically available at{" "}
<Text
onPress={() => {
Linking.openURL("https://github.com/streetwriters/notesnook");
}}
style={{
textDecorationLine: "underline",
color: colors.primary.accent
}}
>
github.com/streetwriters/notesnook.
</Text>{" "}
If you want to ask something in general or need some assistance, we
would suggest that you{" "}
<Text
style={{
textDecorationLine: "underline",
color: colors.primary.accent
}}
onPress={async () => {
try {
await openLinkInBrowser(
"https://discord.gg/zQBK97EE22",
colors
);
} catch (e) {
console.error(e);
}
}}
>
join our community on Discord.
</Text>
</Paragraph>
</>
)}
</View>
);
};

View File

@@ -42,10 +42,7 @@ import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { Issue } from "../github/issue";
export const makeError = (
stack: string,
component: string
) => `Please let us know what happened. What steps we can take to reproduce the issue here.
export const makeError = (stack: string, component: string) => `
_______________________________
Stacktrace: In ${component}::${stack}`;
@@ -98,9 +95,9 @@ export default function Migrate() {
await sleep(1);
const { error, report } = await BackupService.run(false, "local");
if (error) {
ToastManager.error(error, "Backup failed");
ToastManager.error(error as Error, "Backup failed");
if (report) {
reportError(error);
reportError(error as Error);
}
setLoading(false);
return;

View File

@@ -1,600 +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 { getFormattedDate } from "@notesnook/common";
import { EVENTS } from "@notesnook/core/dist/common";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
import {
ToastManager,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings";
import { refreshAllStores } from "../../../stores/create-db-collection-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { useUserStore } from "../../../stores/use-user-store";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
const [restoring, setRestoring] = useState(false);
const sheet = useRef();
useEffect(() => {
const open = async () => {
setVisible(true);
setTimeout(() => {
sheet.current?.show();
}, 1);
};
eSubscribeEvent(eOpenRestoreDialog, open);
eSubscribeEvent(eCloseRestoreDialog, close);
return () => {
eUnSubscribeEvent(eOpenRestoreDialog, open);
eUnSubscribeEvent(eCloseRestoreDialog, close);
};
}, [close]);
const close = useCallback(() => {
if (restoring) {
showIsWorking();
return;
}
sheet.current?.hide();
setTimeout(() => {
setVisible(false);
}, 150);
}, [restoring]);
const showIsWorking = () => {
ToastManager.show({
heading: "Restoring Backup",
message: "Your backup data is being restored. please wait.",
type: "error",
context: "local"
});
};
return !visible ? null : (
<SheetWrapper
fwdRef={sheet}
gestureEnabled={!restoring}
closeOnTouchBackdrop={!restoring}
onClose={() => {
setVisible(false);
close();
}}
>
<RestoreDataComponent
close={close}
restoring={restoring}
setRestoring={setRestoring}
actionSheetRef={sheet}
/>
<Toast context="local" />
</SheetWrapper>
);
};
export default RestoreDataSheet;
const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const { colors } = useThemeColors();
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false);
const [progress, setProgress] = useState();
useEffect(() => {
const subscription = db.eventManager.subscribe(
EVENTS.migrationProgress,
(progress) => {
setProgress(progress);
}
);
return () => {
subscription?.unsubscribe();
};
}, []);
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 1000);
}, []);
const restore = async (item) => {
if (restoring) {
return;
}
try {
const file = Platform.OS === "ios" ? item.path : item.uri;
console.log(file);
if (file.endsWith(".nnbackupz")) {
setRestoring(true);
if (Platform.OS === "android") {
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
}
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
console.log("copying");
await copyFileAsync(file, cacheFile);
console.log("copied");
await restoreFromZip(cacheFile);
} else {
await restoreFromZip(file, false);
}
} else if (file.endsWith(".nnbackup")) {
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(file, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(file, "utf8");
}
await restoreFromNNBackup(JSON.parse(backup));
}
} catch (e) {
console.log("error", e);
setRestoring(false);
backupError(e);
}
};
const withPassword = () => {
return new Promise((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password, isEncryptionKey) => {
resolve({
value: password,
isEncryptionKey
});
resolved = true;
return true;
},
check: {
info: "Use encryption key",
type: "transparent"
}
});
});
};
const checkBackups = async () => {
try {
let files = [];
if (Platform.OS === "android") {
let backupDirectory = SettingsService.get().backupDirectoryAndroid;
if (backupDirectory) {
setBackupDirectoryAndroid(backupDirectory);
files = await ScopedStorage.listFiles(backupDirectory.uri);
} else {
setLoading(false);
return;
}
} else {
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files
.filter((file) => {
const name = Platform.OS === "android" ? file.name : file.filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
setLoading(false);
} catch (e) {
console.log(e);
setLoading(false);
}
};
const restoreBackup = async (backup, password, key) => {
await db.transaction(async () => {
await db.backup.import(backup, password, key);
});
await db.initCollections();
refreshAllStores();
ToastManager.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
Navigation.queueRoutesForUpdate();
return true;
};
const backupError = (e) => {
console.log(e.stack);
ToastManager.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
/**
*
* @param {string} file
*/
async function restoreFromZip(file, remove) {
try {
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
await unzip(file, zipOutputFolder);
console.log("Unzipped files successfully to", zipOutputFolder);
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
// if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
// throw new Error("Backup file is invalid");
// }
await db.transaction(async () => {
let password;
let key;
console.log(
`Found ${backupFiles?.length} files to restore from backup`
);
for (const path of backupFiles) {
if (path === ".nnbackup") continue;
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const parsed = JSON.parse(data);
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
const { value, isEncryptionKey } = await withPassword();
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (!password && !key) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password, key);
console.log("Imported", path);
}
});
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (remove) {
RNFetchBlob.fs.unlink(file).catch(console.log);
}
refreshAllStores();
Navigation.queueRoutesForUpdate();
setRestoring(false);
close();
ToastManager.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
} catch (e) {
backupError(e);
setRestoring(false);
}
}
/**
*
* @param {string} file
*/
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const { value, isEncryptionKey } = await withPassword();
let key;
let password;
if (isEncryptionKey) {
key = value;
} else {
password = value;
}
if (key || password) {
try {
await restoreBackup(backup, password, key);
close();
setRestoring(false);
} catch (e) {
setRestoring(false);
backupError(e);
}
} else {
setRestoring(false);
}
} else {
await restoreBackup(backup);
setRestoring(false);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
}
const button = {
title: "Restore from files",
onPress: async () => {
if (restoring) {
return;
}
try {
useUserStore.setState({
disableAppLockRequests: true
});
console.log("disabled...");
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
if (file.name.endsWith(".nnbackupz")) {
setRestoring(true);
await restoreFromZip(file.fileCopyUri, true);
} else if (file.name.endsWith(".nnbackup")) {
RNFetchBlob.fs.unlink(file.fileCopyUri).catch(console.log);
setRestoring(true);
const data = await fetch(file.uri);
await restoreFromNNBackup(await data.json());
}
} catch (e) {
console.log("error", e.stack);
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
setRestoring(false);
backupError(e);
}
}
};
const renderItem = ({ item, index }) => (
<View
style={{
minHeight: 50,
justifyContent: "space-between",
alignItems: "center",
width: "100%",
borderRadius: 0,
flexDirection: "row",
borderBottomWidth: 0.5,
borderBottomColor: colors.primary.border
}}
>
<View
style={{
maxWidth: "75%"
}}
>
<Paragraph size={SIZE.sm} style={{ width: "100%", maxWidth: "100%" }}>
{getFormattedDate(item?.lastModified * 1)}
</Paragraph>
<Paragraph size={SIZE.xs}>
{(item.filename || item.name).replace(".nnbackup", "")}
</Paragraph>
</View>
<Button
title="Restore"
height={30}
type="accent"
style={{
borderRadius: 100,
paddingHorizontal: 12
}}
fontSize={SIZE.sm - 1}
onPress={() => restore(item, index)}
/>
</View>
);
return (
<>
<View>
<Dialog context="local" />
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 8,
paddingRight: 8,
alignItems: "center",
paddingTop: restoring ? 8 : 0
}}
>
<DialogHeader
title="Backups"
paragraph={`All the backups are stored in ${
Platform.OS === "ios"
? "File Manager/Notesnook/Backups"
: "selected backups folder."
}`}
button={button}
/>
</View>
<Seperator half />
<FlatList
ListEmptyComponent={
!restoring ? (
loading ? (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 100
}}
>
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.lg}
/>
</View>
) : (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 100
}}
>
{Platform.OS === "android" && !backupDirectoryAndroid ? (
<>
<Button
title="Select backups folder"
icon="folder"
onPress={async () => {
let folder = await ScopedStorage.openDocumentTree(
true
);
let subfolder;
if (folder.name !== "Notesnook backups") {
subfolder = await ScopedStorage.createDirectory(
folder.uri,
"Notesnook backups"
);
} else {
subfolder = folder;
}
SettingsService.set({
backupDirectoryAndroid: subfolder
});
setBackupDirectoryAndroid(subfolder);
setLoading(true);
checkBackups();
}}
style={{
marginTop: 10,
paddingHorizontal: 12
}}
height={30}
width={null}
/>
<Paragraph
style={{
textAlign: "center",
marginTop: 5
}}
size={SIZE.xs}
textBreakStrategy="balanced"
color={colors.secondary.paragraph}
>
Select the folder that includes your backup files to
list them here.
</Paragraph>
</>
) : (
<Paragraph color={colors.secondary.paragraph}>
No backups found
</Paragraph>
)}
</View>
)
) : (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 200
}}
>
<ActivityIndicator color={colors.primary.accent} />
<Paragraph color={colors.secondary.paragraph}>
Restoring {progress ? progress?.collection : null}
{progress ? `(${progress.current}/${progress.total}) ` : null}
...Please wait.
</Paragraph>
</View>
)
}
keyExtractor={(item) => item.name || item.filename}
style={{
paddingHorizontal: 12
}}
data={restoring || loading ? [] : files}
renderItem={renderItem}
ListFooterComponent={
restoring || loading || files.length === 0 ? null : (
<View
style={{
height: 200
}}
/>
)
}
/>
</View>
</>
);
};

View File

@@ -25,7 +25,8 @@ import {
DimensionValue,
TextStyle,
View,
ViewStyle
ViewStyle,
useWindowDimensions
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useUserStore } from "../../../stores/use-user-store";
@@ -92,6 +93,9 @@ export const Button = ({
});
const textColor = buttonType?.text ? buttonType.text : text;
const { fontScale } = useWindowDimensions();
const growFactor = 1 + (fontScale - 1) / 10;
const Component = bold ? Heading : Paragraph;
return (
@@ -108,7 +112,7 @@ export const Button = ({
NativeTooltip.show(event, tooltipText, NativeTooltip.POSITIONS.TOP);
}
}}
disabled={loading}
disabled={loading || restProps.disabled}
type={type}
accentColor={accentColor}
accentText={accentText}
@@ -117,14 +121,18 @@ export const Button = ({
customOpacity={buttonType?.opacity}
customAlpha={buttonType?.alpha}
style={{
height: height,
width: (width as DimensionValue) || undefined,
height: typeof height === "number" ? height * growFactor : height,
width:
typeof width === "number"
? width * growFactor
: (width as DimensionValue) || undefined,
paddingHorizontal: 12,
borderRadius: 5,
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
opacity: restProps?.disabled ? 0.5 : 1,
...(style as ViewStyle)
}}
>

View File

@@ -41,6 +41,7 @@ import { SIZE } from "../../../utils/size";
import { IconButton } from "../icon-button";
import Paragraph from "../typography/paragraph";
import phone from "phone";
import isURL from "validator/lib/isURL";
interface InputProps extends TextInputProps {
fwdRef?: RefObject<TextInput>;
@@ -49,7 +50,8 @@ interface InputProps extends TextInputProps {
| "email"
| "confirmPassword"
| "username"
| "phonenumber";
| "phonenumber"
| "url";
loading?: boolean;
onSubmit?: (
event: NativeSyntheticEvent<TextInputSubmitEditingEventData>
@@ -151,6 +153,9 @@ const Input = ({
case "confirmPassword":
isError = customValidator && value === customValidator();
break;
case "url":
isError = isURL(value);
break;
case "phonenumber": {
const result = phone(value, {
strictDetection: true,

View File

@@ -21,11 +21,12 @@ import { VariantsWithStaticColors, useThemeColors } from "@notesnook/theme";
import React, { RefObject, useCallback } from "react";
import {
ColorValue,
PressableStateCallbackType,
Pressable as RNPressable,
PressableProps as RNPressableProps,
PressableStateCallbackType,
View,
ViewStyle
ViewStyle,
useWindowDimensions
} from "react-native";
import {
RGB_Linear_Shade,
@@ -256,6 +257,8 @@ export const Pressable = ({
? 1
: colorOpacity;
const alpha = customAlpha ? customAlpha : isDark ? 0.03 : -0.03;
const { fontScale } = useWindowDimensions();
const growFactor = 1 + (fontScale - 1) / 8;
const getStyle = useCallback(
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
@@ -276,7 +279,13 @@ export const Pressable = ({
: borderColor || "transparent",
borderWidth: borderWidth
},
style
style,
{
height:
typeof style.height === "number"
? style.height * growFactor
: style.height
}
],
[
alpha,
@@ -288,7 +297,8 @@ export const Pressable = ({
borderSelectedColor,
borderColor,
borderWidth,
style
style,
growFactor
]
);

View File

@@ -26,10 +26,7 @@ import {
} from "@notesnook/core/dist/common";
import { EventManagerSubscription } from "@notesnook/core/dist/utils/event-manager";
import notifee from "@notifee/react-native";
import NetInfo, {
NetInfoState,
NetInfoSubscription
} from "@react-native-community/netinfo";
import NetInfo, { NetInfoSubscription } from "@react-native-community/netinfo";
import React, { useCallback, useEffect, useRef } from "react";
import {
AppState,
@@ -47,6 +44,7 @@ import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import * as RNIap from "react-native-iap";
import { DatabaseLogger, db, setupDatabase } from "../common/database";
import { initializeLogger } from "../common/database/logger";
import { MMKV } from "../common/database/mmkv";
import Migrate from "../components/sheets/migrate";
import NewFeature from "../components/sheets/new-feature";
@@ -280,8 +278,109 @@ const onSubscriptionError = async (error: RNIap.PurchaseError) => {
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
const doAppLoadActions = async () => {
if (SettingsService.get().sessionExpired) {
eSendEvent(eLoginSessionExpired);
return;
}
notifee.setBadgeCount(0);
if (!(await db.user.getUser())) {
setLoginMessage();
return;
}
await useMessageStore.getState().setAnnouncement();
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;
if (await checkForRateAppRequest()) return;
if (await PremiumService.getRemainingTrialDaysStatus()) return;
if (SettingsService.get().introCompleted) {
useMessageStore.subscribe((state) => {
const dialogs = state.dialogs;
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
}
});
}
};
const checkAppUpdateAvailable = async () => {
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
return;
try {
const version =
Config.GITHUB_RELEASE === "true"
? await getGithubVersion()
: await checkVersion();
if (!version || !version?.needsUpdate) return false;
setUpdateAvailableMessage(version);
return true;
} catch (e) {
return false;
}
};
const checkForRateAppRequest = async () => {
const rateApp = SettingsService.get().rateApp as number;
if (
rateApp &&
rateApp < Date.now() &&
!useMessageStore.getState().message?.visible
) {
setRateAppMessage();
return false;
}
return false;
};
const IsDatabaseMigrationRequired = () => {
if (!db.migrations.required() || useUserStore.getState().appLocked)
return false;
presentSheet({
component: <Migrate />,
onClose: () => {
if (!db.migrations.required()) {
initializeDatabase();
}
},
disableClosing: true
});
return true;
};
const initializeDatabase = async (password?: string) => {
if (useUserStore.getState().appLocked) return;
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
try {
await setupDatabase(password);
await db.init();
Sync.run();
} catch (e) {
DatabaseLogger.error(e as Error);
ToastManager.error(e as Error, "Error initializing database", "global");
}
}
if (IsDatabaseMigrationRequired()) return;
if (db.isInitialized) {
Notifications.setupReminders(true);
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}
useSettingStore.getState().setAppLoading(false);
DatabaseLogger.info("Database initialized");
}
Walkthrough.init();
};
export const useAppEvents = () => {
const loading = useSettingStore((state) => state.isAppLoading);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
state.setLastSynced,
state.setUser,
@@ -290,18 +389,15 @@ export const useAppEvents = () => {
]);
const syncedOnLaunch = useRef(false);
const refValues = useRef<
Partial<{
subsriptionSuccessListener: EmitterSubscription;
subsriptionErrorListener: EmitterSubscription;
isUserReady: boolean;
prevState: AppStateStatus;
showingDialog: boolean;
removeInternetStateListener: NetInfoSubscription;
isReconnecting: boolean;
initialUrl: string;
backupDidWait: boolean;
isConnectingSSE: boolean;
}>
>({});
@@ -312,7 +408,7 @@ export const useAppEvents = () => {
}, [setLastSynced]);
useEffect(() => {
if (loading) return;
if (isAppLoading) return;
let subscriptions: EventManagerSubscription[] = [];
const eventManager = db.eventManager;
@@ -327,7 +423,7 @@ export const useAppEvents = () => {
return () => {
subscriptions.forEach((sub) => sub?.unsubscribe?.());
};
}, [loading, onSyncComplete]);
}, [isAppLoading, onSyncComplete]);
const subscribeToPurchaseListeners = useCallback(async () => {
if (Platform.OS === "android") {
@@ -364,14 +460,31 @@ export const useAppEvents = () => {
const user = await db.user.getUser();
if (PremiumService.get() && user) {
if (
await BackupService.checkBackupRequired(SettingsService.get().reminder)
SettingsService.get().backupDirectoryAndroid ||
Platform.OS !== "android"
) {
if (
!SettingsService.get().backupDirectoryAndroid &&
Platform.OS === "android"
)
return;
sleep(2000).then(() => BackupService.run());
const partialBackup = await BackupService.checkBackupRequired(
SettingsService.get().reminder,
"lastBackupDate"
);
const fullBackup = await BackupService.checkBackupRequired(
SettingsService.get().fullBackupReminder,
"lastFullBackupDate"
);
await sleep(2000);
if (partialBackup) {
await BackupService.run();
}
if (fullBackup) {
await BackupService.run(true, undefined, "full");
}
}
if (SettingsService.getProperty("offlineMode")) {
db.attachments.cacheAttachments().catch(console.log);
}
}
}, []);
@@ -432,7 +545,6 @@ export const useAppEvents = () => {
setRecoveryKeyMessage();
}
if (!user?.isEmailConfirmed) setEmailVerifyMessage();
refValues.current.isUserReady = true;
syncedOnLaunch.current = true;
if (!isLogin) {
@@ -506,18 +618,19 @@ export const useAppEvents = () => {
}, [onSyncComplete, onUserUpdated]);
useEffect(() => {
const onInternetStateChanged = async (state: NetInfoState) => {
const onInternetStateChanged = async () => {
if (!syncedOnLaunch.current) return;
reconnectSSE(state);
Sync.run("global", false, "full");
reconnectSSE();
};
const onAppStateChanged = async (state: AppStateStatus) => {
if (state === "active") {
notifee.setBadgeCount(0);
updateStatusBarColor();
checkAutoBackup();
await reconnectSSE();
Sync.run("global", false, "full");
reconnectSSE();
await checkForShareExtensionLaunchedInBackground();
MMKV.removeItem("appState");
let user = await db.user.getUser();
@@ -572,7 +685,7 @@ export const useAppEvents = () => {
});
}
let sub: NativeEventSubscription;
if (!loading && !appLocked) {
if (!isAppLoading && !appLocked) {
setTimeout(() => {
sub = AppState.addEventListener("change", onAppStateChanged);
if (
@@ -594,7 +707,7 @@ export const useAppEvents = () => {
sub?.remove();
unsubscribePurchaseListeners();
};
}, [loading, appLocked, checkAutoBackup]);
}, [isAppLoading, appLocked, checkAutoBackup]);
useEffect(() => {
if (!appLocked && !syncing && refValues.current.backupDidWait) {
@@ -603,167 +716,35 @@ export const useAppEvents = () => {
}
}, [appLocked, syncing, checkAutoBackup]);
async function reconnectSSE(connection?: NetInfoState) {
if (refValues.current?.isReconnecting || !refValues.current?.isUserReady)
return;
if (useSettingStore.getState().appDidEnterBackgroundForAction) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
console.log("AppDidEnterForegroundAfterAction");
return;
}
if (SettingsService.get().sessionExpired) {
refValues.current.isReconnecting = false;
return;
}
refValues.current.isReconnecting = true;
let connectionState = connection;
async function reconnectSSE() {
try {
if (!connectionState) {
connectionState = await NetInfo.fetch();
}
const user = await db.user.getUser();
if (
user &&
connectionState.isConnected &&
connectionState.isInternetReachable
) {
await db.connectSSE();
} else {
useUserStore.getState().setSyncing(false);
await db.syncer.stop();
}
refValues.current.isReconnecting = false;
if (refValues.current?.isConnectingSSE) return;
refValues.current.isConnectingSSE = true;
await db.connectSSE();
refValues.current.isConnectingSSE = false;
} catch (e) {
refValues.current.isReconnecting = false;
refValues.current.isConnectingSSE = false;
DatabaseLogger.error(e as Error);
}
}
useEffect(() => {
if (!loading) {
if (!isAppLoading) {
onUserUpdated();
doAppLoadActions();
}
}, [loading, onUserUpdated]);
const initializeDatabase = useCallback(async (password?: string) => {
const IsDatabaseMigrationRequired = () => {
if (!db.migrations.required() || useUserStore.getState().appLocked)
return false;
presentSheet({
component: <Migrate />,
onClose: () => {
if (!db.migrations.required()) {
initializeDatabase();
}
},
disableClosing: true
});
return true;
};
if (useUserStore.getState().appLocked) return;
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
try {
await setupDatabase(password);
await db.init();
} catch (e) {
DatabaseLogger.error(e as Error);
ToastManager.error(e as Error, "Error initializing database", "global");
}
}
if (IsDatabaseMigrationRequired()) return;
if (db.isInitialized) {
Notifications.setupReminders(true);
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}
useSettingStore.getState().setAppLoading(false);
DatabaseLogger.info("Database initialized");
}
Walkthrough.init();
}, []);
}, [isAppLoading, onUserUpdated]);
useEffect(() => {
let sub: () => void;
if (appLocked) {
const sub = useUserStore.subscribe((state) => {
if (!state.appLocked && useSettingStore.getState().isAppLoading) {
console.log("DB initialized");
if (!appLocked && isAppLoading) {
initializeLogger()
.catch((e) => {
console.log(e);
})
.finally(() => {
//@ts-ignore
initializeDatabase();
sub();
}
});
});
}
return () => {
sub?.();
};
}, [appLocked, initializeDatabase]);
return initializeDatabase;
};
const doAppLoadActions = async () => {
if (SettingsService.get().sessionExpired) {
eSendEvent(eLoginSessionExpired);
return;
}
notifee.setBadgeCount(0);
if (!(await db.user.getUser())) {
setLoginMessage();
return;
}
await useMessageStore.getState().setAnnouncement();
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;
if (await checkForRateAppRequest()) return;
if (await PremiumService.getRemainingTrialDaysStatus()) return;
if (SettingsService.get().introCompleted) {
useMessageStore.subscribe((state) => {
const dialogs = state.dialogs;
if (dialogs.length > 0) {
eSendEvent(eOpenAnnouncementDialog, dialogs[0]);
}
});
}
};
const checkAppUpdateAvailable = async () => {
if (__DEV__ || Config.isTesting === "true" || Config.FDROID_BUILD || BETA)
return;
try {
const version =
Config.GITHUB_RELEASE === "true"
? await getGithubVersion()
: await checkVersion();
if (!version || !version?.needsUpdate) return false;
setUpdateAvailableMessage(version);
return true;
} catch (e) {
return false;
}
};
const checkForRateAppRequest = async () => {
const rateApp = SettingsService.get().rateApp as number;
if (
rateApp &&
rateApp < Date.now() &&
!useMessageStore.getState().message?.visible
) {
setRateAppMessage();
return false;
}
return false;
}, [appLocked, isAppLoading]);
};

View File

@@ -62,7 +62,6 @@ import {
} from "../services/event-manager";
import { useSettingStore } from "../stores/use-setting-store";
import {
eClearEditor,
eCloseFullscreenEditor,
eOnEnterEditor,
eOnExitEditor,
@@ -286,9 +285,10 @@ const _TabsHolder = () => {
break;
case "mobile":
if (
state &&
!state?.movedAway &&
useTabStore.getState().getCurrentNoteId()
(state &&
!state?.movedAway &&
useTabStore.getState().getCurrentNoteId()) ||
editorState().movedAway === false
) {
tabBarRef.current?.goToIndex(2, false);
} else {

View File

@@ -16,7 +16,7 @@
"html-to-text": "9.0.5",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "0.9.3",
"react-native-actions-sheet": "0.9.7",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/dist/utils/filename";
import { basename } from "pathe";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker, {
@@ -41,7 +42,6 @@ import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
import { eCloseSheet } from "../../../utils/events";
import { useTabStore } from "./use-tab-store";
import { editorController, editorState } from "./utils";
import { basename } from "pathe";
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
@@ -123,13 +123,13 @@ const file = async (fileOptions: PickerOptions) => {
file.name,
fileOptions
))
)
return;
) {
throw new Error("Failed to attach file");
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
if (!fileOptions.tabId) return;
if (
fileOptions.tabId &&
fileOptions.tabId !== undefined &&
fileOptions.noteId &&
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
fileOptions.noteId
@@ -158,16 +158,16 @@ const file = async (fileOptions: PickerOptions) => {
fileOptions.tabId
);
}
} else {
throw new Error("Failed to attach file, no tabId is set");
}
setTimeout(() => {
eSendEvent(eCloseSheet);
}, 1000);
eSendEvent(eCloseSheet);
} catch (e) {
eSendEvent(eCloseSheet);
ToastManager.show({
heading: (e as Error).message,
message: "You need internet access to attach a file",
heading: "Failed to attach file",
message: (e as Error).message,
type: "error",
context: "global"
});
@@ -203,7 +203,6 @@ const camera = async (options: PickerOptions) => {
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};

View File

@@ -569,7 +569,7 @@ export const useEditor = (
return;
}
lastContentChangeTime.current[item.id] = item.dateEdited;
lastContentChangeTime.current[item.id] = 0;
currentLoadingNoteId.current = item.id;
currentNotes.current[item.id] = item;

View File

@@ -32,9 +32,12 @@ import {
TimeFormatPicker,
TrashIntervalPicker,
BackupReminderPicker,
ApplockTimerPicker
ApplockTimerPicker,
BackupWithAttachmentsReminderPicker
} from "./picker/pickers";
import ThemeSelector from "./theme-selector";
import { RestoreBackup } from "./restore-backup";
import { ServersConfiguration } from "./server-config";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
@@ -51,5 +54,8 @@ export const components: { [name: string]: ReactElement } = {
"date-format-selector": <DateFormatPicker />,
"time-format-selector": <TimeFormatPicker />,
"theme-selector": <ThemeSelector />,
"applock-timer": <ApplockTimerPicker />
"applock-timer": <ApplockTimerPicker />,
autobackupsattachments: <BackupWithAttachmentsReminderPicker />,
backuprestore: <RestoreBackup />,
"server-config": <ServersConfiguration />
};

View File

@@ -18,35 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React, { useEffect, useState } from "react";
import React from "react";
import { View } from "react-native";
import Animated, { FadeInDown } from "react-native-reanimated";
import DelayLayout from "../../components/delay-layout";
import BaseDialog from "../../components/dialog/base-dialog";
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { Header } from "../../components/header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { SectionGroup } from "./section-group";
import { settingsGroups } from "./settings-data";
import { RouteParams, SettingSection } from "./types";
import SettingsUserSection from "./user-section";
import { Header } from "../../components/header";
const keyExtractor = (item: SettingSection) => item.id;
const Home = ({
navigation
}: NativeStackScreenProps<RouteParams, "SettingsHome">) => {
const { colors } = useThemeColors();
const [loading, setLoading] = useState(false);
useNavigationFocus(navigation, {
onFocus: () => {
useNavigationStore.getState().setFocusedRouteId("Settings");
@@ -62,16 +49,6 @@ const Home = ({
<SectionGroup item={item} />
);
useEffect(() => {
function settingsLoading(state: boolean) {
setLoading(state || true);
}
eSubscribeEvent("settings-loading", settingsLoading);
return () => {
eUnSubscribeEvent("settings-loading", settingsLoading);
};
}, []);
return (
<>
<Header
@@ -81,57 +58,6 @@ const Home = ({
id="Settings"
/>
<DelayLayout delay={300} type="settings">
{loading && (
//@ts-ignore // Migrate to typescript required.
<BaseDialog animated={false} bounce={false} visible={true}>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.primary.background,
justifyContent: "center",
alignItems: "center"
}}
>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.primary.background,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading color={colors.primary.paragraph} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.secondary.paragraph}>
Please wait while we log out and clear app data.
</Paragraph>
<View
style={{
flexDirection: "row",
width: 100,
marginTop: 15
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
/>
</View>
</View>
</View>
</BaseDialog>
)}
<Animated.FlatList
entering={FadeInDown}
data={settingsGroups}

View File

@@ -121,8 +121,8 @@ export const BackupReminderPicker = createSettingsPicker({
SettingsService.set({ reminder: item });
},
formatValue: (item) => {
return item === "useroff"
? "Never"
return item === "useroff" || item === "off" || item === "never"
? "Off"
: item.slice(0, 1).toUpperCase() + item.slice(1);
},
getItemKey: (item) => item,
@@ -140,6 +140,31 @@ export const BackupReminderPicker = createSettingsPicker({
}
});
export const BackupWithAttachmentsReminderPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.fullBackupReminder,
updateValue: (item) => {
SettingsService.set({ fullBackupReminder: item });
},
formatValue: (item) => {
return item === "useroff" || item === "off" || item === "never"
? "Off"
: item.slice(0, 1).toUpperCase() + item.slice(1);
},
getItemKey: (item) => item,
options: ["never", "weekly", "monthly"],
compareValue: (current, item) => current === item,
premium: true,
requiresVerification: () => {
return (
!useSettingStore.getState().settings.encryptedBackup &&
useUserStore.getState().user
);
},
onCheckOptionIsPremium: (item) => {
return item !== "never";
}
});
export const ApplockTimerPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.appLockTimer,
updateValue: (item) => {

View File

@@ -0,0 +1,558 @@
/*
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 { formatBytes, getFormattedDate } from "@notesnook/common";
import { LegacyBackupFile } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { ActivityIndicator, FlatList, Platform, View } from "react-native";
import RNFetchBlob, { ReactNativeBlobUtilStat } from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { unzip } from "react-native-zip-archive";
import { DatabaseLogger, db } from "../../../common/database";
import storage from "../../../common/database/storage";
import { deleteCacheFileByName } from "../../../common/filesystem/io";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
import { presentDialog } from "../../../components/dialog/functions";
import {
endProgress,
startProgress,
updateProgress
} from "../../../components/dialogs/progress";
import { Button } from "../../../components/ui/button";
import Heading from "../../../components/ui/typography/heading";
import Paragraph from "../../../components/ui/typography/paragraph";
import { SectionItem } from "../../../screens/settings/section-item";
import { ToastManager } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings";
import { refreshAllStores } from "../../../stores/create-db-collection-store";
import { useUserStore } from "../../../stores/use-user-store";
import { SIZE } from "../../../utils/size";
type PasswordOrKey = { password?: string; encryptionKey?: string };
const withPassword = () => {
return new Promise<PasswordOrKey>((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve({});
},
negativeText: "Cancel",
positivePress: async (password, isEncryptionKey) => {
resolve({
encryptionKey: isEncryptionKey ? password : undefined,
password: isEncryptionKey ? undefined : password
});
resolved = true;
return true;
},
check: {
info: "Use encryption key",
type: "transparent"
}
});
});
};
const restoreBackup = async (options: {
uri: string;
deleteFile?: boolean;
}) => {
try {
const isLegacyBackup = options.uri.endsWith(".nnbackup");
startProgress({
title: "Restoring backup",
paragraph: "Preparing to restore backup file...",
canHideProgress: false
});
let filePath = options.uri;
let deleteBackupFile = options.deleteFile;
if (!isLegacyBackup) {
if (Platform.OS === "android") {
updateProgress({
progress: `Copying backup file to cache...`
});
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
}
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
if (filePath.startsWith("content://")) {
await copyFileAsync(filePath, cacheFile);
} else {
await RNFetchBlob.fs.cp(filePath, cacheFile);
}
filePath = cacheFile;
deleteBackupFile = true;
}
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
updateProgress({
progress: `Extracting files from backup...`
});
await unzip(filePath, zipOutputFolder);
const extractedBackupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
const extractedAttachments = extractedBackupFiles.includes("attachments")
? await RNFetchBlob.fs.ls(`${zipOutputFolder}/attachments`)
: [];
const attachmentsKeyPath: any = extractedAttachments?.find(
(path) => path === ".attachments_key"
);
const attachmentsKey = attachmentsKeyPath
? await JSON.parse(
await RNFetchBlob.fs.readFile(
`${zipOutputFolder}/attachments/${attachmentsKeyPath}`,
"utf8"
)
)
: undefined;
let count = 0;
await db.transaction(async () => {
let passwordOrKey: PasswordOrKey;
for (const path of extractedBackupFiles) {
if (path === ".nnbackup" || path === "attachments") continue;
updateProgress({
progress: `Restoring data (${count++}/${
extractedBackupFiles.length
})`
});
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const backup = JSON.parse(data);
const isEncryptedBackup = backup.encrypted;
passwordOrKey = !isEncryptedBackup
? ({} as PasswordOrKey)
: await withPassword();
if (
isEncryptedBackup &&
!passwordOrKey?.encryptionKey &&
!passwordOrKey?.password
) {
endProgress();
throw new Error("Failed to decrypt backup");
}
await db.backup.import(backup, {
...passwordOrKey,
attachmentsKey: attachmentsKey
});
}
});
await db.initCollections();
count = 0;
for (const path of extractedAttachments) {
if (path === ".attachments_key") continue;
updateProgress({
progress: `Restoring attachments (${count++}/${
extractedAttachments.length
})`
});
const hash = path;
const attachment = await db.attachments.attachment(hash as string);
if (!attachment) continue;
console.log("Saving attachment file", hash);
await deleteCacheFileByName(hash);
await RNFetchBlob.fs.cp(
`${zipOutputFolder}/attachments/${hash}`,
`${cacheDir}/${hash}`
);
}
updateProgress({
progress: `Cleaning up...`
});
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (Platform.OS === "android" || deleteBackupFile) {
RNFetchBlob.fs.unlink(filePath).catch(console.log);
}
} else {
updateProgress({
progress: `Reading backup file...`
});
const rawData =
Platform.OS === "android"
? await ScopedStorage.readFile(filePath, "utf8")
: await RNFetchBlob.fs.readFile(filePath, "utf8");
const backup: LegacyBackupFile = JSON.parse(rawData) as LegacyBackupFile;
const isEncryptedBackup =
typeof backup.data !== "string" && backup.data.cipher;
updateProgress({
progress: isEncryptedBackup
? `Backup is encrypted, decrypting...`
: "Preparing to restore backup file..."
});
const { encryptionKey, password } = isEncryptedBackup
? ({} as PasswordOrKey)
: await withPassword();
if (isEncryptedBackup && !encryptionKey && !password) {
endProgress();
throw new Error("Failed to decrypt backup");
}
await db.transaction(async () => {
updateProgress({
progress: "Restoring backup..."
});
await db.backup.import(backup, {
encryptionKey,
password
});
});
endProgress();
}
ToastManager.show({
heading: "Backup restored successfully",
type: "success"
});
await db.initCollections();
refreshAllStores();
Navigation.queueRoutesForUpdate();
endProgress();
} catch (e) {
endProgress();
DatabaseLogger.error(e as Error);
ToastManager.error(e as Error, `Failed to restore backup`);
}
};
const BACKUP_FILES_CACHE: (ReactNativeBlobUtilStat | ScopedStorage.FileType)[] =
[];
export const RestoreBackup = () => {
const { colors } = useThemeColors();
const [files, setFiles] =
useState<(ReactNativeBlobUtilStat | ScopedStorage.FileType)[]>(
BACKUP_FILES_CACHE
);
const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] =
useState<ScopedStorage.FileType>();
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 1000);
}, []);
const checkBackups = async () => {
try {
let files: (ReactNativeBlobUtilStat | ScopedStorage.FileType)[] = [];
if (Platform.OS === "android") {
const backupDirectory = SettingsService.get().backupDirectoryAndroid;
if (backupDirectory) {
setBackupDirectoryAndroid(backupDirectory);
files = await ScopedStorage.listFiles(backupDirectory.uri);
} else {
setLoading(false);
return;
}
} else {
const path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files
.filter((file) => {
const name =
Platform.OS === "android"
? (file as ScopedStorage.FileType).name
: (file as ReactNativeBlobUtilStat).filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
const timeA = a.lastModified;
const timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
BACKUP_FILES_CACHE.splice(0, BACKUP_FILES_CACHE.length, ...files);
} catch (e) {
e;
} finally {
setLoading(false);
}
};
const renderItem = React.useCallback(
({
item,
index
}: {
item: ReactNativeBlobUtilStat | ScopedStorage.FileType;
index: number;
}) => <BackupItem item={item} index={index} />,
[]
);
return (
<>
<FlatList
data={[0]}
renderItem={() => {
return (
<View>
<SectionItem
item={{
id: "restore-from-files",
name: "Restore from files",
icon: "folder",
modifer: async () => {
useUserStore.setState({
disableAppLockRequests: true
});
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
restoreBackup({
uri:
Platform.OS === "android"
? (("file://" + file.fileCopyUri) as string)
: (file.fileCopyUri as string),
deleteFile: true
});
},
description:
"Select a backup file from your device to restore backup"
}}
/>
{Platform.OS === "android" ? (
<SectionItem
item={{
id: "select-backup-folder",
name: "Select folder with backup files",
icon: "folder",
modifer: async () => {
const folder = await ScopedStorage.openDocumentTree(true);
let subfolder;
if (folder.name !== "Notesnook backups") {
subfolder = await ScopedStorage.createDirectory(
folder.uri,
"Notesnook backups"
);
} else {
subfolder = folder;
}
SettingsService.set({
backupDirectoryAndroid: subfolder
});
setBackupDirectoryAndroid(subfolder);
setLoading(true);
checkBackups();
},
description:
"Select folder where Notesnook backup files are stored to view and restore them from the app"
}}
/>
) : null}
<FlatList
ListHeaderComponent={
<View
style={{
backgroundColor: colors.primary.background,
marginBottom: 10
}}
>
<Heading color={colors.primary.accent} size={SIZE.xs}>
RECENT BACKUPS
</Heading>
</View>
}
stickyHeaderIndices={[0]}
ListEmptyComponent={
loading ? (
<View
style={{
justifyContent: "center",
alignItems: "center",
height: 300,
paddingHorizontal: 50
}}
>
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.lg}
/>
</View>
) : (
<View
style={{
justifyContent: "center",
alignItems: "center",
gap: 12,
height: 300,
paddingHorizontal: 50
}}
>
<Paragraph
style={{
textAlign: "center"
}}
color={colors.secondary.paragraph}
>
No backups were found.{" "}
{!backupDirectoryAndroid
? `Please select a folder with backup files to view them here.`
: null}
</Paragraph>
</View>
)
}
windowSize={2}
keyExtractor={(item) =>
(item as ScopedStorage.FileType).name ||
(item as ReactNativeBlobUtilStat).filename
}
style={{
paddingHorizontal: 12
}}
ListFooterComponent={
<View
style={{
height: 200
}}
/>
}
data={files}
renderItem={renderItem}
/>
</View>
);
}}
/>
</>
);
};
const BackupItem = ({
item,
index
}: {
item: ReactNativeBlobUtilStat | ScopedStorage.FileType;
index: number;
}) => {
const { colors } = useThemeColors();
const isLegacyBackup =
item.path?.endsWith(".nnbackup") ||
(item as ScopedStorage.FileType).uri?.endsWith(".nnbackup");
const itemName = (
(item as ReactNativeBlobUtilStat).filename ||
(item as ScopedStorage.FileType).name
)
.replace(".nnbackupz", "")
.replace(".nnbackup", "");
return (
<View
style={{
justifyContent: "space-between",
alignItems: "center",
width: "100%",
borderRadius: 0,
flexDirection: "row",
borderBottomWidth: 0.5,
borderBottomColor: colors.primary.border,
paddingVertical: 12
}}
>
<View>
<Paragraph size={SIZE.sm}>{itemName}</Paragraph>
<Paragraph
size={SIZE.xs}
color={colors.secondary.paragraph}
style={{ width: "100%", maxWidth: "100%" }}
>
Created on {getFormattedDate(item?.lastModified, "date-time")}
{isLegacyBackup ? "(Legacy backup)" : ""} (
{formatBytes((item as ReactNativeBlobUtilStat).size)})
</Paragraph>
</View>
<Button
title="Restore"
type="secondaryAccented"
style={{
paddingHorizontal: 12,
height: 35
}}
onPress={() => {
presentDialog({
title: `Restore ${itemName}`,
paragraph: `Are you sure you want to restore this backup?`,
positiveText: "Restore",
negativeText: "Cancel",
positivePress: async () => {
console.log("file path", (item as ScopedStorage.FileType).uri);
restoreBackup({
uri:
Platform.OS === "android"
? (item as ScopedStorage.FileType).uri
: (item as ReactNativeBlobUtilStat).path
});
}
});
}}
/>
</View>
);
};

View File

@@ -0,0 +1,213 @@
/*
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 { isServerCompatible } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useState } from "react";
import { View } from "react-native";
import { presentDialog } from "../../components/dialog/functions";
import { Button } from "../../components/ui/button";
import Input from "../../components/ui/input";
import { Notice } from "../../components/ui/notice";
import Paragraph from "../../components/ui/typography/paragraph";
import SettingsService from "../../services/settings";
import { HostId, HostIds } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { ToastManager } from "../../services/event-manager";
export const ServerIds = ["notesnook-sync", "auth", "sse"] as const;
export type ServerId = (typeof ServerIds)[number];
type Server = {
id: ServerId;
host: HostId;
title: string;
example: string;
description: string;
};
type VersionResponse = {
version: number;
id: string;
instance: string;
};
const SERVERS: Server[] = [
{
id: "notesnook-sync",
host: "API_HOST",
title: "Sync server",
example: "http://localhost:4326",
description: "Server used to sync your notes & other data between devices."
},
{
id: "auth",
host: "AUTH_HOST",
title: "Auth server",
example: "http://localhost:5326",
description: "Server used for login/sign up and authentication."
},
{
id: "sse",
host: "SSE_HOST",
title: "Events server",
example: "http://localhost:7326",
description: "Server used to receive important notifications & events."
}
];
export function ServersConfiguration() {
const { colors } = useThemeColors();
const [error, setError] = useState<string>();
const [success, setSuccess] = useState<boolean>();
const [urls, setUrls] = useState<Partial<Record<HostId, string>>>(
SettingsService.getProperty("serverUrls") || {}
);
const isLoggedIn = useUserStore((state) => !!state.user);
return (
<View
style={{
paddingHorizontal: 12,
gap: 12,
marginTop: 12
}}
>
{isLoggedIn ? (
<Notice
text="You must log out in order to change/reset server URLs."
type="alert"
/>
) : null}
<View style={{ flexDirection: "column" }}>
{SERVERS.map((server) => (
<Input
key={server.id}
editable={!isLoggedIn}
placeholder={`${server.id} e.g. ${server.example}`}
validationType="url"
defaultValue={urls[server.host]}
errorMessage="Please enter a valid URL."
onChangeText={(value) =>
setUrls((s) => {
s[server.host] = value;
return s;
})
}
/>
))}
{error ? (
<Paragraph
style={{
paddingVertical: 12
}}
color={colors.error.paragraph}
>
{error}
</Paragraph>
) : null}
{success === true ? (
<Paragraph
style={{
paddingVertical: 12
}}
color={colors.success.paragraph}
>
Connected to all servers sucessfully.
</Paragraph>
) : null}
<View style={{ marginTop: 1, justifyContent: "flex-end", gap: 12 }}>
<Button
disabled={isLoggedIn}
type="secondary"
width="100%"
onPress={async () => {
try {
for (const host of HostIds) {
const url = urls[host];
const server = SERVERS.find((s) => s.host === host)!;
if (!url) throw new Error("All server urls are required.");
const version = await fetch(`${url}/version`)
.then((r) => r.json() as Promise<VersionResponse>)
.catch(() => undefined);
if (!version)
throw new Error(`Could not connect to ${server.title}.`);
if (version.id !== server.id)
throw new Error(
`The URL you have given (${url}) does not point to the ${server.title}.`
);
if (!isServerCompatible(version.version)) {
throw new Error(
`The server version is not compatible with the app.`
);
}
}
setSuccess(true);
setError(undefined);
} catch (e) {
setError((e as Error).message);
}
}}
title="Test connection"
/>
<Button
type="accent"
disabled={isLoggedIn}
width="100%"
onPress={async () => {
if (!success) {
ToastManager.show({
heading: "Test connection before changing server urls"
});
return;
}
SettingsService.setProperty(
"serverUrls",
urls as Record<HostId, string>
);
presentDialog({
title: "Server url changed",
paragraph: "Restart the app for changes to take effect.",
negativeText: "Done"
});
}}
title="Save"
/>
<Button
disabled={isLoggedIn}
type="error"
width="100%"
title="Reset server urls"
onPress={async () => {
if (isLoggedIn) return;
SettingsService.setProperty("serverUrls", undefined);
presentDialog({
title: "Server urls reset",
paragraph: "Restart the app for changes to take effect.",
negativeText: "Done"
});
}}
/>
</View>
</View>
</View>
);
}

View File

@@ -17,6 +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 { formatBytes } from "@notesnook/common";
import notifee from "@notifee/react-native";
import dayjs from "dayjs";
import React from "react";
@@ -24,12 +25,19 @@ import { Appearance, Linking, Platform } from "react-native";
import { getVersion } from "react-native-device-info";
import * as RNIap from "react-native-iap";
import { enabled } from "react-native-privacy-snapshot";
import { db } from "../../common/database";
import ScreenGuardModule from "react-native-screenguard";
import { DatabaseLogger, db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import filesystem from "../../common/filesystem";
import { AttachmentDialog } from "../../components/attachments";
import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions";
import { AppLockPassword } from "../../components/dialogs/applock-password";
import {
endProgress,
startProgress,
updateProgress
} from "../../components/dialogs/progress";
import { ChangeEmail } from "../../components/sheets/change-email";
import ExportNotesSheet from "../../components/sheets/export-notes";
import { Issue } from "../../components/sheets/github/issue";
@@ -42,6 +50,7 @@ import BiometricService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
openVault,
presentSheet
} from "../../services/event-manager";
@@ -60,8 +69,7 @@ import {
eCloseSheet,
eCloseSimpleDialog,
eOpenLoginDialog,
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
eOpenRecoveryKeyDialog
} from "../../utils/events";
import { NotesnookModule } from "../../utils/notesnook-module";
import { sleep } from "../../utils/time";
@@ -330,43 +338,144 @@ export const settingsGroups: SettingSection[] = [
},
description: "Verify your subscription to Notesnook Pro"
},
{
id: "clear-cache",
name: "Clear cache",
icon: "delete",
modifer: async () => {
presentDialog({
title: "Clear cache",
paragraph: "Are you sure you want to clear the cache?",
positiveText: "Clear",
positivePress: async () => {
filesystem.clearCache();
ToastManager.show({
heading: "Cache cleared",
message: "All cached attachments have been removed",
type: "success"
});
}
});
},
description(current) {
return `Clear all cached attachments. Current cache size: ${
current as number
}`;
},
useHook: () => {
const [cacheSize, setCacheSize] = React.useState(0);
React.useEffect(() => {
filesystem.getCacheSize().then(setCacheSize).catch(console.log);
const sub = eSubscribeEvent("cache-cleared", () => {
setCacheSize(0);
});
return () => {
sub?.unsubscribe();
};
}, []);
return formatBytes(cacheSize);
}
},
{
id: "logout",
name: "Log out",
description: "Clear all your data and reset the app.",
name: "Logout",
description:
"Logging out will clear all data stored on this device.",
icon: "logout",
modifer: () => {
modifer: async () => {
const hasUnsyncedChanges = await db.hasUnsyncedChanges();
presentDialog({
title: "Logout",
paragraph: "Clear all your data and reset the app.",
paragraph:
"Are you sure you want to logout and clear all data stored on this device?",
positiveText: "Logout",
positivePress: async () => {
try {
eSendEvent(eCloseSimpleDialog);
setTimeout(async () => {
eSendEvent("settings-loading", true);
Navigation.popToTop();
check: {
info: "Take a backup before logging out",
defaultValue: true
},
notice: hasUnsyncedChanges
? {
text: "You have unsynced notes. Take a backup or sync your notes to avoid losing your critical data.",
type: "alert"
}
: undefined,
positivePress: async (_, takeBackup) => {
eSendEvent(eCloseSimpleDialog);
setTimeout(async () => {
try {
startProgress({
fillBackground: true,
title: "Logging out",
canHideProgress: true,
paragraph:
"Please wait while we log out and clear app data."
});
Navigation.navigate("Notes");
if (takeBackup) {
updateProgress({
progress: "Taking a backup of your notes"
});
try {
const result = await BackupService.run(
false,
"local",
"partial"
);
if (result.error) throw result.error as Error;
} catch (e) {
DatabaseLogger.error(e);
const error = e;
const canLogout = await new Promise((resolve) => {
presentDialog({
context: "local",
title: "Backup failed",
paragraph: `${
(error as Error).message
}. Do you want to continue logging out?`,
positiveText: "Continue",
positivePress: () => {
resolve(true);
},
onClose: () => {
resolve(false);
}
});
});
if (!canLogout) {
endProgress();
return;
}
}
}
updateProgress({
progress: "Logging out... please wait"
});
await db.user?.logout();
setLoginMessage();
await PremiumService.setPremiumStatus();
await BiometricService.resetCredentials();
MMKV.clearStore();
clearAllStores();
refreshAllStores();
setImmediate(() => {
refreshAllStores();
});
Navigation.queueRoutesForUpdate();
SettingsService.resetSettings();
useUserStore.getState().setUser(null);
useUserStore.getState().setSyncing(false);
Navigation.goBack();
Navigation.popToTop();
setTimeout(() => {
eSendEvent("settings-loading", false);
}, 3000);
}, 300);
} catch (e) {
ToastManager.error(e as Error, "Error logging out");
eSendEvent("settings-loading", false);
}
endProgress();
} catch (e) {
DatabaseLogger.error(e);
ToastManager.error(e as Error, "Error logging out");
endProgress();
}
}, 300);
}
});
}
@@ -392,12 +501,18 @@ export const settingsGroups: SettingSection[] = [
try {
const verified = await db.user?.verifyPassword(value);
if (verified) {
eSendEvent("settings-loading", true);
await db.user?.deleteUser(value);
await BiometricService.resetCredentials();
SettingsService.set({
introCompleted: true
});
setTimeout(async () => {
startProgress({
title: "Deleting account",
paragraph: "Please wait while we delete your account"
});
Navigation.navigate("Notes");
await db.user?.deleteUser(value);
await BiometricService.resetCredentials();
SettingsService.set({
introCompleted: true
});
}, 300);
} else {
ToastManager.show({
heading: "Incorrect password",
@@ -408,9 +523,9 @@ export const settingsGroups: SettingSection[] = [
});
}
eSendEvent("settings-loading", false);
endProgress();
} catch (e) {
eSendEvent("settings-loading", false);
endProgress();
console.log(e);
ToastManager.error(
e as Error,
@@ -431,6 +546,25 @@ export const settingsGroups: SettingSection[] = [
type: "screen",
icon: "autorenew",
sections: [
{
id: "offline-mode",
name: "Full offline mode",
description: "Download everything including attachments on sync",
type: "switch",
property: "offlineMode",
modifer: () => {
const current = SettingsService.get().offlineMode;
if (current) {
SettingsService.setProperty("offlineMode", false);
db.fs().cancel("offline-mode");
return;
}
PremiumService.verify(() => {
SettingsService.setProperty("offlineMode", true);
db.attachments.cacheAttachments().catch(console.log);
});
}
},
{
id: "auto-sync",
name: "Disable auto sync",
@@ -472,7 +606,7 @@ export const settingsGroups: SettingSection[] = [
{
id: "pull-sync",
name: "Force pull changes",
description: `Use this if some changes are not appearing on this device from other devices. This will pull everything from the server and overwrite with whatever is one this device.\n\nThese must only be used for troubleshooting. Using them regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.`,
description: `Use this if changes from other devices are not appearing on this device. This will overwrite the data on this device with the latest data from the server.\n\nThis must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.`,
modifer: () => {
presentDialog({
title: "Force Pull changes",
@@ -494,7 +628,7 @@ export const settingsGroups: SettingSection[] = [
{
id: "push-sync",
name: "Force push changes",
description: `Use this if some changes are not appearing on this device from other devices. This will pull everything from the server and overwrite with whatever is one this device.`,
description: `Use this if changes made on this device are not appearing on other devices. This will overwrite the data on the server with the data from this device.\n\nThis must only be used for troubleshooting. Using it regularly for sync is not recommended and will lead to unexpected data loss and other issues. If you are having persistent issues with sync, please report them to us at support@streetwriters.co.`,
modifer: () => {
presentDialog({
title: "Force Push changes",
@@ -686,6 +820,14 @@ export const settingsGroups: SettingSection[] = [
type: "switch"
}
]
},
{
id: "servers",
type: "screen",
name: "Servers",
description: "Configure server URLs for Notesnook",
icon: "server",
component: "server-config"
}
]
},
@@ -693,15 +835,6 @@ export const settingsGroups: SettingSection[] = [
id: "privacy-security",
name: "Privacy and security",
sections: [
{
id: "temeltery",
type: "switch",
name: "Telemetry",
icon: "radar",
description:
"Contribute towards a better Notesnook. All tracking information is anonymous.",
property: "telemetry"
},
{
id: "marketing-emails",
type: "switch",
@@ -848,9 +981,18 @@ export const settingsGroups: SettingSection[] = [
"Hide app contents when you switch to other apps. This will also disable screenshot taking in the app.",
modifer: () => {
const settings = SettingsService.get();
Platform.OS === "android"
? NotesnookModule.setSecureMode(!settings.privacyScreen)
: enabled(true);
if (Platform.OS === "ios") {
enabled(!settings.privacyScreen);
if (settings.privacyScreen) {
ScreenGuardModule.unregister();
} else {
ScreenGuardModule.register({
backgroundColor: "#000000"
});
}
} else {
NotesnookModule.setSecureMode(!settings.privacyScreen);
}
SettingsService.set({ privacyScreen: !settings.privacyScreen });
},
@@ -1029,7 +1171,8 @@ export const settingsGroups: SettingSection[] = [
{
id: "backup-now",
name: "Backup now",
description: "Create a backup of your data",
description:
"Take a partial backup of your data that does not include attachments",
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
@@ -1040,14 +1183,41 @@ export const settingsGroups: SettingSection[] = [
verifyUser(null, () => BackupService.run(true));
}
},
{
id: "backup-now",
name: "Backup now with attachments",
hidden: () => !useUserStore.getState().user,
description: "Take a full backup of your data with all attachments",
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
await BackupService.run(true, undefined, "full");
return;
}
verifyUser(null, () =>
BackupService.run(true, undefined, "full")
);
}
},
{
id: "auto-backups",
type: "component",
name: "Automatic backups",
description:
"Backup your data once every week or daily automatically.",
"Set the interval to create a partial backup (without attachments) automatically.",
component: "autobackups"
},
{
id: "auto-backups-with-attachments",
type: "component",
hidden: () => !useUserStore.getState().user,
name: "Automatic backups with attachments",
description: `Set the interval to create a backup (with attachments) automatically.
NOTE: Creating a backup with attachments can take a while, and also fail completely. The app will try to resume/restart the backup in case of interruptions.`,
component: "autobackupsattachments"
},
{
id: "select-backup-dir",
name: "Select backup directory",
@@ -1139,9 +1309,8 @@ export const settingsGroups: SettingSection[] = [
id: "restore-backup",
name: "Restore backup",
description: "Restore backup from phone storage.",
modifer: () => {
eSendEvent(eOpenRestoreDialog);
}
type: "screen",
component: "backuprestore"
},
{
id: "export-notes",
@@ -1321,9 +1490,9 @@ export const settingsGroups: SettingSection[] = [
}
},
{
id: "join-mastodom",
id: "join-mastodon",
name: "Follow us on Mastodon",
description: "We are on mastodom",
description: "We are on mastodon",
icon: "mastodon",
modifer: () => {
Linking.openURL("https://fosstodon.org/@notesnook").catch(

View File

@@ -16,14 +16,15 @@ 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 { Platform } from "react-native";
import BackgroundFetch from "@ammarahmed/react-native-background-fetch";
import { AppRegistry, AppState, Platform } from "react-native";
import {
beginBackgroundTask,
endBackgroundTask
} from "react-native-begin-background-task";
import BackgroundFetch from "@ammarahmed/react-native-background-fetch";
import { DatabaseLogger, db, setupDatabase } from "../common/database";
import { AppState, AppRegistry } from "react-native";
import { deleteDCacheFiles } from "../common/filesystem/io";
import { useUserStore } from "../stores/use-user-store";
import Notifications from "./notifications";
import SettingsService from "./settings";
@@ -114,15 +115,19 @@ async function onBackgroundSyncStarted() {
await db.init();
}
const user = await db.user?.getUser();
if (user) {
if (user && !useUserStore.getState().syncing) {
useUserStore.getState().setSyncing(true);
await db.sync({
type: "full",
force: false
});
useUserStore.getState().setSyncing(false);
}
await Notifications.setupReminders();
deleteDCacheFiles();
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
} catch (e) {
useUserStore.getState().setSyncing(false);
DatabaseLogger.error(e as Error);
console.log("BACKGROUND SYNC ERROR", (e as Error).message);
}
@@ -132,16 +137,17 @@ const onBoot = async () => {
try {
if (!SettingsService.getProperty("backgroundSync")) {
DatabaseLogger.info("BACKGROUND SYNC ON BOOT DISABLED");
return;
}
DatabaseLogger.info("BOOT TASK STARTED");
if (!db.isInitialized) {
await setupDatabase();
await db.init();
}
await Notifications.setupReminders();
SettingsService.init();
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}

View File

@@ -17,6 +17,8 @@ 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 { sanitizeFilename } from "@notesnook/common";
import { formatDate } from "@notesnook/core/dist/utils/date";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import FileViewer from "react-native-file-viewer";
@@ -25,8 +27,14 @@ import Share from "react-native-share";
import { zip } from "react-native-zip-archive";
import { DatabaseLogger, db } from "../common/database";
import storage from "../common/database/storage";
import filesystem from "../common/filesystem";
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
import { presentDialog } from "../components/dialog/functions";
import {
endProgress,
startProgress,
updateProgress
} from "../components/dialogs/progress";
import { eCloseSheet } from "../utils/events";
import { sleep } from "../utils/time";
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
@@ -37,14 +45,14 @@ const MS_WEEK = MS_DAY * 7;
const MONTH = MS_DAY * 30;
async function getDirectoryAndroid() {
let folder = await ScopedStorage.openDocumentTree(true);
const folder = await ScopedStorage.openDocumentTree(true);
if (!folder) return null;
let subfolder;
if (!folder.name.includes("Notesnook backups")) {
let folderFiles = await ScopedStorage.listFiles(folder.uri);
for (let f of folderFiles) {
if (f.type === "directory" && f.name === "Notesnook backups") {
subfolder = f;
const files = await ScopedStorage.listFiles(folder.uri);
for (const file of files) {
if (file.type === "directory" && file.name === "Notesnook backups") {
subfolder = file;
}
}
if (!subfolder) {
@@ -67,10 +75,10 @@ async function checkBackupDirExists(reset = false, context = "global") {
let dir = SettingsService.get().backupDirectoryAndroid;
if (reset) dir = null;
if (dir) {
let allDirs = await ScopedStorage.getPersistedUriPermissions();
const allDirs = await ScopedStorage.getPersistedUriPermissions();
let exists = allDirs.findIndex((d) => {
return d === dir.uri || dir.uri.includes(d);
});
return d === dir?.uri || dir?.uri.includes(d);
}) as number | boolean;
exists = exists !== -1;
dir = exists ? dir : null;
}
@@ -100,7 +108,7 @@ async function checkBackupDirExists(reset = false, context = "global") {
return dir;
}
async function presentBackupCompleteSheet(backupFilePath) {
async function presentBackupCompleteSheet(backupFilePath: string) {
presentSheet({
title: "Backup complete",
icon: "cloud-upload",
@@ -123,7 +131,7 @@ async function presentBackupCompleteSheet(backupFilePath) {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(console.log);
} as any).catch(console.log);
}
},
actionText: "Share"
@@ -142,10 +150,10 @@ async function presentBackupCompleteSheet(backupFilePath) {
});
}
async function updateNextBackupTime() {
async function updateNextBackupTime(type: "full" | "partial") {
SettingsService.set({
nextBackupRequestTime: Date.now() + 86400000 * 3,
lastBackupDate: Date.now()
[type === "full" ? "lastFullBackupDate" : "lastBackupDate"]: Date.now()
});
}
/**
@@ -153,78 +161,124 @@ async function updateNextBackupTime() {
* @param {string=} context
* @returns {Promise<{path?: string, error?: Error, report?: boolean}}>
*/
async function run(progress = false, context) {
let androidBackupDirectory = await checkBackupDirExists(false, context);
async function run(
progress = false,
context?: string,
backupType: "full" | "partial" = "partial"
) {
console.log("Creating backup:", backupType, progress, context);
const androidBackupDirectory = (await checkBackupDirExists(
false,
context
)) as ScopedStorage.FileType;
if (!androidBackupDirectory)
return {
error: new Error("Backup directory not selected"),
report: false
};
let path;
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups");
}
const backupFileName = sanitizeFilename(
`${formatDate(Date.now(), {
type: "date-time",
dateFormat: "YYYY-MM-DD",
timeFormat: "24-hour"
})}-${new Date().getSeconds()}${backupType === "full" ? "-full" : ""}`,
{ replacement: "-" }
);
if (progress) {
presentSheet({
title: "Backing up your data",
paragraph:
"All your backups are stored in 'Phone Storage/Notesnook/backups/' folder",
progress: true
startProgress({
title: `Creating ${backupType === "full" ? "a full " : ""}backup`,
paragraph: `Please wait while we create a backup of your data. This may take a few minutes.`,
progress: undefined
});
}
let path;
let backupFilePath;
let backupFileName = "notesnook_backup_" + Date.now();
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups/");
}
const zipSourceFolder = `${cacheDir}/${backupFileName}`;
const zipSourceFolder = `${cacheDir}/backup_temp`;
const zipOutputFile =
Platform.OS === "ios"
? `${path}/${backupFileName}.nnbackupz`
: `${cacheDir}/${backupFileName}.nnbackupz`;
try {
if (await RNFetchBlob.fs.exists(zipSourceFolder))
await RNFetchBlob.fs.unlink(zipSourceFolder);
} catch (e) {}
await RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
await RNFetchBlob.fs.mkdir(zipSourceFolder);
const attachmentsDir = zipSourceFolder + "/attachments";
if (backupType === "full") {
await RNFetchBlob.fs.mkdir(attachmentsDir);
}
try {
const user = await db.user.getUser();
for await (const file of db.backup.export(
"mobile",
SettingsService.get().encryptedBackup && user
)) {
console.log("Writing backup chunk of size...", file?.data?.length);
await RNFetchBlob.fs.writeFile(
`${zipSourceFolder}/${file.path}`,
file.data,
"utf8"
);
for await (const file of db.backup.export({
type: "mobile",
encrypt: SettingsService.get().encryptedBackup && !!user,
mode: backupType
})) {
if (file.type === "file") {
updateProgress({
progress: `Writing backup chunk of size... ${file?.data?.length}`
});
await RNFetchBlob.fs.writeFile(
`${zipSourceFolder}/${file.path}`,
file.data,
"utf8"
);
} else if (file.type === "attachment") {
updateProgress({
progress: `Saving attachments in backup... ${file.hash}`
});
if (await filesystem.exists(file.hash)) {
await RNFetchBlob.fs.cp(
`${cacheDir}/${file.hash}`,
`${attachmentsDir}/${file.hash}`
);
}
}
}
updateProgress({
progress: "Creating backup zip file..."
});
await zip(zipSourceFolder, zipOutputFile);
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
androidBackupDirectory.uri,
androidBackupDirectory?.uri,
`${backupFileName}.nnbackupz`,
"application/nnbackupz"
);
console.log("Copying zip file...");
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
console.log("Copyied zip file...");
console.log("Copied zip file...");
path = file.uri;
} else {
path = zipOutputFile;
}
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
updateNextBackupTime();
let showBackupCompleteSheet =
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
updateNextBackupTime(backupType || "partial");
if (progress) {
endProgress();
}
const canShowCompletionStatus =
progress && SettingsService.get().showBackupCompleteSheet;
if (context) {
@@ -235,15 +289,12 @@ async function run(progress = false, context) {
await sleep(300);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
} else {
progress && eSendEvent(eCloseSheet);
if (canShowCompletionStatus) {
presentBackupCompleteSheet(path);
}
ToastManager.show({
heading: "Backup successful",
message: "Your backup is stored in Notesnook folder on your phone.",
type: "success",
context: "global"
});
@@ -252,16 +303,26 @@ async function run(progress = false, context) {
path: path
};
} catch (e) {
ToastManager.error(e, "Backup failed", context || "global");
ToastManager.error(e as Error, "Backup failed", context || "global");
if (e?.message?.includes("android.net.Uri") && androidBackupDirectory) {
if (
(e as Error)?.message?.includes("android.net.Uri") &&
androidBackupDirectory
) {
SettingsService.setProperty("backupDirectoryAndroid", null);
return run(progress, context);
return run(progress, context, backupType);
}
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
DatabaseLogger.error(e);
await sleep(300);
progress && eSendEvent(eCloseSheet);
if (progress) {
endProgress();
}
return {
error: e,
report: true
@@ -269,34 +330,41 @@ async function run(progress = false, context) {
}
}
async function getLastBackupDate() {
return SettingsService.get().lastBackupDate;
}
async function checkBackupRequired(
type: "daily" | "off" | "useroff" | "weekly" | "monthly" | "never",
lastBackupDateType: "lastBackupDate" | "lastFullBackupDate" = "lastBackupDate"
) {
console.log(lastBackupDateType, type);
if (type === "off" || type === "useroff" || type === "never" || !type) return;
const now = Date.now();
const lastBackupDate = SettingsService.getProperty(lastBackupDateType) as
| number
| "never";
async function checkBackupRequired(type) {
if (type === "off" || type === "useroff") return;
let now = Date.now();
let lastBackupDate = await getLastBackupDate();
if (!lastBackupDate || lastBackupDate === "never") {
if (
lastBackupDate === undefined ||
lastBackupDate === "never" ||
lastBackupDate === 0
) {
return true;
}
lastBackupDate = parseInt(lastBackupDate);
if (type === "daily" && lastBackupDate + MS_DAY < now) {
DatabaseLogger.info("Daily backup started");
DatabaseLogger.info(`Daily backup started: ${type}`);
return true;
} else if (type === "weekly" && lastBackupDate + MS_WEEK < now) {
DatabaseLogger.info("Weekly backup started");
DatabaseLogger.info(`Weekly backup started: ${type}`);
return true;
} else if (type === "monthly" && lastBackupDate + MONTH < now) {
DatabaseLogger.info("Monthly backup started");
DatabaseLogger.info(`Monthly backup started: ${type}`);
return true;
}
return false;
}
const checkAndRun = async () => {
let settings = SettingsService.get();
if (await checkBackupRequired(settings.reminder)) {
const settings = SettingsService.get();
if (await checkBackupRequired(settings?.reminder)) {
try {
await run();
} catch (e) {

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 { Platform } from "react-native";
import { NativeModules, Platform } from "react-native";
import { enabled } from "react-native-privacy-snapshot";
import { MMKV } from "../common/database/mmkv";
import {
@@ -29,6 +29,8 @@ import { NotesnookModule } from "../utils/notesnook-module";
import { scale, updateSize } from "../utils/size";
import { DatabaseLogger } from "../common/database";
import { useUserStore } from "../stores/use-user-store";
import ScreenGuardModule from "react-native-screenguard";
function reset() {
const settings = get();
if (settings.reminder !== "off" && settings.reminder !== "useroff") {
@@ -39,10 +41,15 @@ function reset() {
}
function resetSettings() {
MMKV.setString(
"appSettings",
JSON.stringify({ ...defaultSettings, introCompleted: true })
);
const settings: SettingStore["settings"] = {
...defaultSettings,
introCompleted: true,
serverUrls: get().serverUrls,
backupDirectoryAndroid: undefined
};
MMKV.setString("appSettings", JSON.stringify(settings));
set(settings);
init();
}
@@ -117,12 +124,18 @@ function setPrivacyScreen(settings: SettingStore["settings"]) {
NotesnookModule.setSecureMode(true);
} else {
enabled(true);
if (NativeModules.ScreenGuard) {
ScreenGuardModule.register({ backgroundColor: "#000000" });
}
}
} else {
if (Platform.OS === "android") {
NotesnookModule.setSecureMode(false);
} else {
enabled(false);
if (NativeModules.ScreenGuard) {
ScreenGuardModule.unregister();
}
}
}
}

View File

@@ -30,7 +30,9 @@ export const ignoredMessages = [
"Sync already running",
"Not allowed to start service intent",
"WebSocket failed to connect",
"Failed to start the HttpConnection before"
"Failed to start the HttpConnection before",
"Could not connect to the Sync server",
"Network request failed"
];
let pendingSync: any = undefined;
let syncTimer: NodeJS.Timeout;
@@ -61,58 +63,45 @@ const run = async (
clearTimeout(syncTimer);
syncTimer = setTimeout(async () => {
const status = await NetInfo.fetch();
const userstore = useUserStore.getState();
userstore.setSyncing(true);
const user = await db.user.getUser();
if (!status.isInternetReachable) {
DatabaseLogger.warn("Internet not reachable");
}
if (
!user ||
!status.isInternetReachable ||
SettingsService.get().disableSync
) {
if (!user || SettingsService.get().disableSync) {
userstore.setSyncing(false);
initAfterSync();
pendingSync = undefined;
return onCompleted?.(SyncStatus.Failed);
}
userstore.setSyncing(true);
let error = null;
let error: Error | undefined = undefined;
try {
await BackgroundSync.doInBackground(async () => {
try {
await db.sync({
type: type,
force: forced
force: forced,
offlineMode: SettingsService.get().offlineMode
});
} catch (e) {
error = e;
error = e as Error;
}
});
if (error) {
throw error;
}
if (error) throw error;
} catch (e) {
error = e;
error = e as Error;
DatabaseLogger.error(error, "[Client] Failed to sync");
if (
!ignoredMessages.find((message) =>
(e as Error).message?.includes(message)
) &&
userstore.user &&
status.isConnected &&
status.isInternetReachable
!ignoredMessages.find((message) => error?.message?.includes(message)) &&
userstore.user
) {
userstore.setSyncing(false, SyncStatus.Failed);
const status = await NetInfo.fetch();
if (status.isConnected && status.isInternetReachable) {
ToastManager.error(e as Error, "Sync failed", context);
}
}
DatabaseLogger.error(e, "[Client] Failed to sync");
} finally {
initAfterSync();
userstore.setSyncing(
@@ -120,16 +109,14 @@ const run = async (
error ? SyncStatus.Failed : SyncStatus.Passed
);
onCompleted?.(error ? SyncStatus.Failed : SyncStatus.Passed);
setImmediate(() => {
if (pendingSync)
Sync.run(
pendingSync.context,
pendingSync.forced,
pendingSync.type,
pendingSync.onCompleted,
pendingSync.lastSyncTime
);
});
if (pendingSync)
Sync.run(
pendingSync.context,
pendingSync.forced,
pendingSync.type,
pendingSync.onCompleted,
pendingSync.lastSyncTime
);
}
}, 300);
};

View File

@@ -25,6 +25,8 @@ import { FileType } from "react-native-scoped-storage";
import create, { State } from "zustand";
import { ThemeDark, ThemeLight, ThemeDefinition } from "@notesnook/theme";
import { Reminder } from "@notesnook/core/dist/types";
export const HostIds = ["API_HOST", "AUTH_HOST", "SSE_HOST"] as const;
export type HostId = (typeof HostIds)[number];
export type Settings = {
showToolbarOnTop?: boolean;
@@ -32,7 +34,8 @@ export type Settings = {
fontScale?: number;
forcePortraitOnTablet?: boolean;
useSystemTheme?: boolean;
reminder?: string;
reminder: "daily" | "off" | "useroff" | "weekly" | "monthly";
fullBackupReminder: "never" | "weekly" | "monthly";
encryptedBackup?: boolean;
homepage?: string;
sort?: string;
@@ -42,7 +45,6 @@ export type Settings = {
appLockTimer: number;
appLockEnabled?: boolean;
appLockMode?: "none" | "background" | "launch";
telemetry?: boolean;
notebooksListMode?: "normal" | "compact";
notesListMode?: "normal" | "compact";
devMode?: boolean;
@@ -52,8 +54,8 @@ export type Settings = {
rateApp?: boolean | number;
migrated?: boolean;
introCompleted?: boolean;
nextBackupRequestTime?: number | undefined;
lastBackupDate?: number | undefined;
nextBackupRequestTime?: number;
lastBackupDate?: number;
userEmailConfirmed?: boolean;
recoveryKeySaved?: boolean;
backupDirectoryAndroid?: FileType | null;
@@ -82,6 +84,10 @@ export type Settings = {
backgroundSync?: boolean;
applockKeyboardType: "numeric" | "default";
settingsVersion?: number;
backupType: "full" | "partial";
offlineMode?: boolean;
lastFullBackupDate?: number;
serverUrls?: Record<HostId, string>;
};
type DimensionsType = {
@@ -127,6 +133,12 @@ export const defaultSettings: SettingStore["settings"] = {
applockKeyboardType: "numeric",
appLockTimer: 0,
showToolbarOnTop: false,
disableAutoSync: false,
disableRealtimeSync: false,
disableSync: false,
appLockEnabled: false,
backupDirectoryAndroid: null,
offlineMode: false,
showKeyboardOnOpen: false,
fontScale: 1,
forcePortraitOnTablet: false,
@@ -139,7 +151,6 @@ export const defaultSettings: SettingStore["settings"] = {
screenshotMode: true,
privacyScreen: false,
appLockMode: "none",
telemetry: false,
notebooksListMode: "normal",
notesListMode: "normal",
devMode: false,
@@ -150,7 +161,7 @@ export const defaultSettings: SettingStore["settings"] = {
migrated: false,
introCompleted: Config.isTesting ? true : false,
nextBackupRequestTime: undefined,
lastBackupDate: undefined,
lastBackupDate: 0,
userEmailConfirmed: false,
recoveryKeySaved: false,
showBackupCompleteSheet: true,
@@ -171,7 +182,10 @@ export const defaultSettings: SettingStore["settings"] = {
biometricsAuthEnabled: false,
appLockHasPasswordSecurity: false,
backgroundSync: true,
settingsVersion: 0
settingsVersion: 0,
backupType: "partial",
fullBackupReminder: "never",
lastFullBackupDate: 0
};
export const useSettingStore = create<SettingStore>((set, get) => ({

View File

@@ -17,8 +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 { Profile } from "@notesnook/core";
import { User } from "@notesnook/core";
import { Profile, User } from "@notesnook/core";
import create, { State } from "zustand";
export enum SyncStatus {
@@ -56,7 +55,9 @@ export const useUserStore = create<UserStore>((set) => ({
set({ syncing: syncing, lastSyncStatus: status });
},
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
lockApp: (appLocked) => set({ appLocked }),
lockApp: (appLocked) => {
set({ appLocked });
},
lastSyncStatus: SyncStatus.Never,
disableAppLockRequests: false,
setDisableAppLockRequests: (disableAppLockRequests) => {

View File

@@ -1,4 +1,7 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
gem 'cocoapods', '~> 1.12'
# Cocoapods 1.15 introduced a bug which break the build. We will remove the upper
# bound in the template on Cocoapods with next React Native release.
gem 'cocoapods', '>= 1.13', '< 1.15'
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'

View File

@@ -1,5 +1,7 @@
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
import com.android.build.OutputFile
@@ -47,7 +49,6 @@ react {
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
hermesFlags = ["-O", "-output-source-map"]
}
println(projectDir);
/**
* Set this to true to create two separate APKs instead of one:
@@ -96,10 +97,14 @@ def getNpmVersion() {
return jsonPackage["version"]
}
def isBuildingAAB = gradle.startParameter.getTaskNames().any { it.contains("bundleRelease") }
android {
compileSdkVersion rootProject.ext.compileSdkVersion
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -111,7 +116,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3016
versionCode 3027
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -163,8 +168,12 @@ android {
pickFirst 'lib/x86_64/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
jniLibs {
useLegacyPackaging isBuildingAAB == false
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
@@ -178,9 +187,15 @@ android {
defaultConfig.versionCode * 5 + versionCodes.get(abi)
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
} else {
println("Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
output.versionCodeOverride =
if (isBuildingAAB) {
output.versionCodeOverride = 4 * 1048576 + defaultConfig.versionCode
println("AAB version code: ${output.versionCodeOverride}");
} else {
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
println("Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
}
}
}
@@ -203,13 +218,7 @@ dependencies {
androidTestImplementation('com.wix:detox:+')
implementation 'androidx.appcompat:appcompat:1.1.0'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {

View File

@@ -2,12 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false" />
</application>
</manifest>

View File

@@ -1,71 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.streetwriters.notesnook;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View File

@@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:node="remove" />
<queries>
<intent>

View File

@@ -3,6 +3,7 @@ package com.streetwriters.notesnook;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.app.ActivityManager;
import com.facebook.react.HeadlessJsTaskService;
@@ -19,8 +20,12 @@ public class BootRecieverService extends BroadcastReceiver {
network connections
**/
Intent serviceIntent = new Intent(context, BootTaskService.class);
context.startService(serviceIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}

View File

@@ -1,15 +1,24 @@
package com.streetwriters.notesnook;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;
public class BootTaskService extends HeadlessJsTaskService {
Notification notification;
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Log.d("BootTask", "Task Started");
return new HeadlessJsTaskConfig(
"com.streetwriters.notesnook.BOOT_TASK",
Arguments.createMap(),
@@ -17,4 +26,31 @@ public class BootTaskService extends HeadlessJsTaskService {
false // optional: defines whether or not the task is allowed in foreground. Default is false
);
}
@Override
public void onHeadlessJsTaskFinish(int taskId) {
super.onHeadlessJsTaskFinish(taskId);
Log.d("BootTask", "Task completed");
stopSelf();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
"Default",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
notification = new NotificationCompat.Builder(this, channel.getId())
.setContentTitle("Sync on boot")
.setContentText("")
.setSmallIcon(R.drawable.ic_stat_name)
.build();
startForeground(1, notification);
}
return super.onStartCommand(intent, flags, startId);
}
}

View File

@@ -1,65 +0,0 @@
package com.streetwriters.notesnook;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.List;
import androidx.multidex.MultiDexApplication;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
public class MainApplication extends MultiDexApplication implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new NNativeModulePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
com.streetwriters.notesnook.ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
}

View File

@@ -0,0 +1,44 @@
package com.streetwriters.notesnook
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(NNativeModulePackage());
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
}
}

View File

@@ -1,5 +1,7 @@
package com.streetwriters.notesnook;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Build;
import android.service.quicksettings.Tile;
@@ -9,11 +11,24 @@ import androidx.annotation.RequiresApi;
@RequiresApi(api = Build.VERSION_CODES.N)
public class NotesnookTileService extends TileService {
@SuppressLint("StartActivityAndCollapseDeprecated")
@Override
public void onClick() {
super.onClick();
Intent intent = new Intent(this.getApplicationContext(), ShareActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityAndCollapse(intent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(
PendingIntent.getActivity(
this.getApplicationContext(),
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
);
} else {
startActivityAndCollapse(intent);
}
}
}

View File

@@ -67,7 +67,6 @@ public class ShareApplication extends MultiDexApplication implements ReactApplic
@Override
public void onCreate() {
super.onCreate();
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
}

View File

@@ -3,11 +3,11 @@ import org.apache.tools.ant.taskdefs.condition.Os
buildscript {
ext {
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
ext.kotlinVersion = '1.8.0'
buildToolsVersion = "34.0.0"
minSdkVersion = 23
compileSdkVersion = 34
targetSdkVersion = 34
kotlinVersion = '1.9.22'
androidXAnnotation = "1.1.0"
androidXCoreVersion = "1.7.0"
androidXCore = "1.7.0"
@@ -23,10 +23,11 @@ buildscript {
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
apply plugin: "com.facebook.react.rootproject"
allprojects {
repositories {
@@ -38,9 +39,9 @@ allprojects {
maven { url 'https://www.jitpack.io' }
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "11"
}
}
// tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// kotlinOptions {
// jvmTarget = "11"
// }
// }
}

View File

@@ -24,9 +24,6 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.182.0
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -83,10 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

View File

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View File

@@ -1,3 +1,6 @@
- Bug fixes and performance improvements
- Fix crash when launching Notesnook from Notification tiles
- Fix app crashing on background on boot
- Fix self-hosted server urls not working
- Bug fixes and small improvements
Thank you for using Notesnook!

View File

@@ -2,7 +2,7 @@ const env = process.env.BABEL_ENV || process.env.NODE_ENV;
const configs = {
env: {
development: {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
plugins: [
'@babel/plugin-transform-named-capturing-groups-regex',
'react-native-reanimated/plugin',
@@ -18,7 +18,7 @@ const configs = {
]
},
test: {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
plugins: [
'@babel/plugin-transform-named-capturing-groups-regex',
'react-native-reanimated/plugin',
@@ -40,7 +40,7 @@ const configs = {
},
production: {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
plugins: [
'transform-remove-console',
'@babel/plugin-transform-named-capturing-groups-regex',

View File

@@ -11,6 +11,10 @@ import NetInfo from "@react-native-community/netinfo";
import { enableFreeze } from "react-native-screens";
import {BackgroundSync} from '../app/services/background-sync';
BackgroundSync.registerHeadlessTask();
BackgroundSync.start();
Notifications.init();
enableFreeze(true);
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
@@ -19,8 +23,7 @@ NetInfo.configure({
return response?.status >= 200 && response?.status < 300;
}
});
BackgroundSync.start();
Notifications.init();
const appName = appJson.name;
if (Config.isTesting) {

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -14,6 +14,7 @@
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* NotesnookTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* NotesnookTests.m */; };
2E4BF736C2EB268ED3FD9C29 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */; };
6510E6D72877215700DACAA9 /* build.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6510E6D62877215700DACAA9 /* build.bundle */; };
6515C42F2580AA3000E83E39 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6515C42E2580AA2F00E83E39 /* StoreKit.framework */; };
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */; };
@@ -21,9 +22,9 @@
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; };
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
656835812BB29A9800144BAB /* OpenSans-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 656835802BB29A8300144BAB /* OpenSans-Italic.ttf */; };
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AD2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
6569927F2C5754F10041CD41 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; };
656992802C5754F10041CD41 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BE2B6838EB0079FF37 /* OpenSans-Regular.ttf */; };
656992812C5754F10041CD41 /* OpenSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7BF2B6838EB0079FF37 /* OpenSans-SemiBold.ttf */; };
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
@@ -42,6 +43,7 @@
87CE5B71DC72F3D95B7B1AA5 /* libPods-Notesnook-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A03E3F5851D27269540E147C /* libPods-Notesnook-tvOSTests.a */; };
A79A93DC9D0CAE8703462151 /* libPods-Notesnook-NotesnookTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 068FA9BDFA207A50B0A9E597 /* libPods-Notesnook-NotesnookTests.a */; };
C619D096A9DE2070DBEAC70F /* libPods-Make Note.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */; };
E7F9D56665C2F19F4A97BF49 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */; };
F50BF8BC21F8B58EB2B5A31A /* libPods-Notesnook-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 16C0296DE7A2B4102817F5C1 /* libPods-Notesnook-tvOS.a */; };
/* End PBXBuildFile section */
@@ -110,6 +112,7 @@
2D02E4901E0B4A5D006451C7 /* Notesnook-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Notesnook-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
41D57E9831C724F59F18FBAE /* libPods-Notesnook-Add to Notes.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook-Add to Notes.a"; sourceTree = BUILT_PRODUCTS_DIR; };
47AE097A087A651BD60CAD7B /* Pods-Notesnook-Make Note.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-Make Note.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-Make Note/Pods-Notesnook-Make Note.release.xcconfig"; sourceTree = "<group>"; };
4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
4BD34AE0C0FD2416E48803B8 /* libPods-Notesnook.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook.a"; sourceTree = BUILT_PRODUCTS_DIR; };
4C4AF691C324D95337F2FB0A /* libPods-Make Note.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Make Note.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6510E6D62877215700DACAA9 /* build.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = build.bundle; path = "../../../../packages/editor-mobile/build.bundle"; sourceTree = "<group>"; };
@@ -149,6 +152,7 @@
88211800FA8BF60638327ADE /* Pods-Make Note.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.debug.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.debug.xcconfig"; sourceTree = "<group>"; };
A03E3F5851D27269540E147C /* libPods-Notesnook-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Notesnook-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A84F3F0D50641D1CD4569B0E /* Pods-Make Note.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Make Note.release.xcconfig"; path = "Target Support Files/Pods-Make Note/Pods-Make Note.release.xcconfig"; sourceTree = "<group>"; };
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Notesnook/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
B9989E5C777BB11343A62D09 /* Pods-Notesnook-NotesnookTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-NotesnookTests.debug.xcconfig"; path = "Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests.debug.xcconfig"; sourceTree = "<group>"; };
D1710CA05FED221CC56D2BE1 /* Pods-Notesnook-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Notesnook-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-Notesnook-tvOSTests/Pods-Notesnook-tvOSTests.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -243,6 +247,7 @@
13B07FB71A68108700A75B9A /* main.m */,
65E0340A257B9FF100793428 /* File.swift */,
65E03409257B9FF100793428 /* Notesnook-Bridging-Header.h */,
4B679E87FDFB1312CAD6CF45 /* PrivacyInfo.xcprivacy */,
);
name = Notesnook;
sourceTree = "<group>";
@@ -315,6 +320,7 @@
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
DA84645424CFBF19A44D64B3 /* Pods */,
B5216B66C0B4A91C0A69128B /* PrivacyInfo.xcprivacy */,
);
indentWidth = 2;
sourceTree = "<group>";
@@ -367,6 +373,7 @@
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
819D480147D052141ACC55A9 /* [CP] Copy Pods Resources */,
310C868ED8D8299D75460154 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -390,6 +397,7 @@
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
240525450DF9ABA0F332B52E /* [CP] Copy Pods Resources */,
65A7F34B255687E600699170 /* Embed App Extensions */,
48C834D962D612F18A0388B3 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -556,6 +564,7 @@
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
6517B7C12B6838EB0079FF37 /* OpenSans-Regular.ttf in Resources */,
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */,
E7F9D56665C2F19F4A97BF49 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -564,6 +573,7 @@
buildActionMask = 2147483647;
files = (
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */,
2E4BF736C2EB268ED3FD9C29 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -586,9 +596,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AD2B1891DF00A362EA /* BuildFile in Resources */,
6569927F2C5754F10041CD41 /* OpenSans-Bold.ttf in Resources */,
656992802C5754F10041CD41 /* OpenSans-Regular.ttf in Resources */,
656992812C5754F10041CD41 /* OpenSans-SemiBold.ttf in Resources */,
65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
65C149872A61151B005C40F1 /* extension.bundle in Resources */,
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
@@ -642,13 +652,13 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -692,6 +702,42 @@
shellPath = /bin/sh;
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh";
};
310C868ED8D8299D75460154 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
48C834D962D612F18A0388B3 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
52B9E42E0FD77D180139A8C7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -763,13 +809,13 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -806,11 +852,11 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Make Note/Pods-Make Note-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -1015,7 +1061,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2105;
CURRENT_PROJECT_VERSION = 2114;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1084,12 +1130,12 @@
"${PODS_ROOT}/Headers/Public/#{s.name}/**",
);
INFOPLIST_FILE = Notesnook/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.8;
MARKETING_VERSION = 3.0.17;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1120,7 +1166,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2105;
CURRENT_PROJECT_VERSION = 2114;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1189,12 +1235,12 @@
"${PODS_ROOT}/Headers/Public/#{s.name}/**",
);
INFOPLIST_FILE = Notesnook/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.8;
MARKETING_VERSION = 3.0.17;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1353,7 +1399,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2105;
CURRENT_PROJECT_VERSION = 2114;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1365,7 +1411,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.8;
MARKETING_VERSION = 3.0.17;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1396,7 +1442,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2105;
CURRENT_PROJECT_VERSION = 2114;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1409,7 +1455,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.8;
MARKETING_VERSION = 3.0.17;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1439,7 +1485,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2105;
CURRENT_PROJECT_VERSION = 2114;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1513,7 +1559,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.8;
MARKETING_VERSION = 3.0.17;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1544,7 +1590,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2105;
CURRENT_PROJECT_VERSION = 2114;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1619,7 +1665,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.8;
MARKETING_VERSION = 3.0.17;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1638,7 +1684,7 @@
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1696,8 +1742,13 @@
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
};
name = Debug;
};
@@ -1707,7 +1758,7 @@
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -1758,8 +1809,13 @@
ONLY_ACTIVE_ARCH = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = "$(inherited)";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -19,9 +19,16 @@
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [self bundleURL];
}
- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

View File

@@ -71,14 +71,11 @@
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>Take a picture and add it to your note.</string>
@@ -115,7 +112,7 @@
<string>BootSplash</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -14,16 +14,7 @@ require Pod::Executable.execute_command('node', ['-p',
platform :ios, min_ios_version_supported
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
# dependencies: {
# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
@@ -38,22 +29,14 @@ pod 'MMKV'
target 'Notesnook' do
config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => false,
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
# :flipper_configuration => flipper_config,
# An absolute path to your application root.
:hermes_enabled => true,
:fabric_enabled => false,
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
@@ -71,7 +54,7 @@ target 'Make Note' do
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => false,
:hermes_enabled => true,
:fabric_enabled => false,
# Enables Flipper.
#
@@ -118,9 +101,9 @@ post_install do |installer|
react_native_path = "../../node_modules/react-native",
# Set `mac_catalyst_enabled` to `true` in order to apply patches
# necessary for Mac Catalyst builds
:mac_catalyst_enabled => false
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
@@ -130,7 +113,7 @@ post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == "react-native-quick-sqlite" then
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'SQLITE_ENABLE_FTS5=1 SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT=1 HAVE_CIPHER_AES_128_CBC=0 HAVE_CIPHER_AES_256_CBC=0 HAVE_CIPHER_SQLCIPHER=0 HAVE_CIPHER_RC4=0 HAVE_CIPHER_CHACHA20=1 SQLITE_OMIT_PROGRESS_CALLBACK=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_DQS=0 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_USE_ALLOCA=1'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = 'SQLITE_ENABLE_FTS5=1 SQLITE3MC_OMIT_AES_HARDWARE_SUPPORT=1 HAVE_CIPHER_AES_128_CBC=0 HAVE_CIPHER_AES_256_CBC=0 HAVE_CIPHER_SQLCIPHER=0 HAVE_CIPHER_RC4=0 HAVE_CIPHER_CHACHA20=1 SQLITE_OMIT_PROGRESS_CALLBACK=1 SQLITE_MAX_EXPR_DEPTH=0 SQLITE_OMIT_DEPRECATED=1 SQLITE_LIKE_DOESNT_MATCH_BLOBS=1 SQLITE_DQS=0 SQLITE_DEFAULT_MEMSTATUS=0 SQLITE_USE_ALLOCA=1'
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -4,12 +4,16 @@
"version": "1.0.0",
"main": "index.js",
"license": "GPL-3.0-or-later",
"overrides": {
"react-native": "0.74.5",
"react": "18.2.0"
},
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-share-extension": "^3.0.0",
"@ammarahmed/react-native-sodium": "1.5.4",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.2.0",
"@callstack/repack": "^4.1.1",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
"@react-native-community/datetimepicker": "6.6.0",
@@ -20,9 +24,8 @@
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "6.6.2",
"@sayem314/react-native-keep-awake": "^1.0.4",
"@shopify/flash-list": "1.4.0",
"react": "18.2.0",
"react-native": "0.72.0",
"react-native": "0.74.5",
"react-native-actions-shortcuts": "^1.0.1",
"react-native-background-actions": "^2.6.6",
"react-native-begin-background-task": "https://github.com/blockfirm/react-native-begin-background-task.git",
@@ -36,37 +39,39 @@
"react-native-exit-app": "https://github.com/ammarahm-ed/react-native-exit-app.git",
"react-native-file-viewer": "^2.1.1",
"react-native-fingerprint-scanner": "https://github.com/ammarahm-ed/react-native-fingerprint-scanner.git",
"react-native-gesture-handler": "^2.12.0",
"react-native-gesture-handler": "^2.18.0",
"react-native-reanimated": "3.14.0",
"react-native-safe-area-context": "^4.10.8",
"react-native-screens": "^3.33.0",
"react-native-webview": "^13.10.0",
"@shopify/flash-list": "1.7.0",
"react-native-vector-icons": "9.2.0",
"react-native-get-random-values": "^1.7.0",
"react-native-gzip": "1.1.0",
"react-native-html-to-pdf-lite": "^0.9.1",
"react-native-iap": "12.11.0",
"react-native-iap": "12.15.1",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.12",
"react-native-mmkv-storage": "^0.10.2",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation": "https://github.com/yamill/react-native-orientation.git",
"react-native-pdf": "6.6.2",
"react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git",
"react-native-reanimated": "3.3.0",
"react-native-safe-area-context": "^4.3.1",
"react-native-scoped-storage": "^1.9.5",
"react-native-screens": "^3.13.1",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^7.2.0",
"react-native-svg": "^12.3.0",
"react-native-swiper-flatlist": "3.2.2",
"react-native-tooltips": "^1.0.3",
"react-native-vector-icons": "9.2.0",
"react-native-webview": "^13.10.0",
"react-native-zip-archive": "6.0.9",
"react-native-quick-sqlite": "^8.0.6",
"react-native-theme-switch-animation": "^0.6.0",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"react-native-image-crop-picker": "^0.40.2",
"react-native-url-polyfill": "^2.0.0"
"react-native-url-polyfill": "^2.0.0",
"react-native-screenguard": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
@@ -75,19 +80,19 @@
"@babel/plugin-transform-private-methods": "^7.22.5",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "^0.72.2",
"@react-native/metro-config": "^0.72.6",
"@react-native/eslint-config": "0.74.86",
"@react-native/metro-config": "0.74.86",
"@tsconfig/react-native": "^3.0.2",
"@types/html-to-text": "^8.0.1",
"@types/jest": "^29.2.1",
"@types/metro-config": "^0.76.3",
"@types/react": "^18.0.24",
"@types/react": "^18.2.6",
"@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"babel-jest": "^29.6.2",
"babel-jest": "^29.6.3",
"babel-loader": "^8.2.5",
"babel-plugin-module-resolver": "^4.1.0",
"babel-plugin-transform-remove-console": "6.9.4",
@@ -99,12 +104,12 @@
"eslint-plugin-react-native": "^4.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"expect": "^29.6.2",
"jest": "^29.6.2",
"jest": "^29.6.3",
"jest-circus": "^29.6.2",
"jest-image-snapshot": "^6.2.0",
"metro-react-native-babel-preset": "0.76.5",
"@react-native/babel-preset": "0.74.86",
"pixelmatch": "^5.3.0",
"prettier": "^2.4.1",
"prettier": "2.8.8",
"react-native-actions-shortcuts": "^1.0.1",
"react-native-bundle-visualizer": "^3.1.1",
"react-native-cli-bump-version": "^1.3.0",

View File

@@ -17,6 +17,12 @@ config.dependencies['react-native-vector-icons'] = {
},
}
config.dependencies['react-native-screenguard'] = {
platforms: {
android: null,
},
}
if (isGithubRelease) {
config.dependencies["react-native-iap"] = {
platforms: {
@@ -30,4 +36,10 @@ if (isGithubRelease) {
}
}
const repackCommands = require('@callstack/repack/commands');
config.commands = repackCommands.filter((command) =>
command.name.startsWith('webpack')
),
module.exports = config;

View File

@@ -26,7 +26,7 @@ module.exports = (env) => {
mode = "development",
context = __dirname,
entry = "./index.js",
platform,
platform = process.env.PLATFORM,
minimize = mode === "production",
devServer = undefined,
bundleFilename = undefined,
@@ -118,7 +118,8 @@ module.exports = (env) => {
*/
output: {
clean: true,
path: path.join(__dirname, "build/generated", platform),
hashFunction: 'xxhash64',
path: path.join(__dirname, 'build/generated', platform),
filename: "index.bundle",
chunkFilename: "[name].chunk.bundle",
publicPath: Repack.getPublicPath({ platform, devServer }),
@@ -172,7 +173,7 @@ module.exports = (env) => {
"node_modules/.webpack-cache"
),
babelrc: false,
presets: ["module:metro-react-native-babel-preset"],
presets: ["module:@react-native/babel-preset"],
plugins: [
"react-native-reanimated/plugin",
"@babel/plugin-transform-named-capturing-groups-regex",
@@ -184,7 +185,7 @@ module.exports = (env) => {
{
test: /\.mjs$|cjs$|js$|jsx$|ts$|tsx$/,
include: [
/node_modules(.*[/\\])+react/,
/node_modules(.*[/\\])+react-native/,
/node_modules(.*[/\\])+@react-native/,
/node_modules(.*[/\\])+@react-navigation/,
/node_modules(.*[/\\])+@react-native-community/,
@@ -220,6 +221,7 @@ module.exports = (env) => {
/node_modules(.*[/\\])+whatwg-url/,
/node_modules(.*[/\\])+react-native-url-polyfill/,
/node_modules(.*[/\\])+diffblazer/,
/node_modules(.*[/\\])+react-freeze/,
],
use: {
loader: "babel-loader",
@@ -230,7 +232,7 @@ module.exports = (env) => {
"node_modules/.webpack-cache"
),
babelrc: false,
presets: ["module:metro-react-native-babel-preset"],
presets: ["module:@react-native/babel-preset"],
plugins: [
"react-native-reanimated/plugin",
"@babel/plugin-transform-named-capturing-groups-regex",
@@ -258,7 +260,7 @@ module.exports = (env) => {
"node_modules/.webpack-cache"
),
babelrc: false,
presets: [["module:metro-react-native-babel-preset"]],
presets: [["module:@react-native/babel-preset"]],
plugins:
devServer && devServer.hmr
? [

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.0.8",
"version": "3.0.17",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -25,10 +25,15 @@
},
"devDependencies": {
"fonteditor-core": "^2.1.11",
"listr": "^0.14.3",
"otplib": "12.0.1",
"patch-package": "7.0.0",
"react-refresh": "0.14.0"
},
"overrides": {
"react-native": "0.74.5",
"react": "18.2.0"
},
"dependencies": {
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
@@ -39,6 +44,8 @@
"@notesnook/themes-server": "file:../../servers/themes",
"diffblazer": "^1.0.1",
"react": "18.2.0",
"react-native": "0.72.0"
"react-native": "0.74.5",
"react-native-mmkv-storage": "^0.10.2",
"react-native-actions-sheet": "^0.9.7"
}
}

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