Compare commits

...

222 Commits

Author SHA1 Message Date
Ammar Ahmed
dd94d9685c mobile: fix cannot read property 'groupBy' of undefined 2024-05-11 11:09:30 +05:00
Abdullah Atta
5028bb1275 web: update snapshots 2024-05-10 15:30:35 +05:00
Abdullah Atta
27ae5bb5a2 desktop: use npx prebuildify instead of yarn 2024-05-10 15:24:39 +05:00
Abdullah Atta
83316fe299 web: update snapshots for linux 2024-05-10 15:20:57 +05:00
Ammar Ahmed
8869224de2 mobile: fix image preview not rendered on iOS 2024-05-10 15:04:28 +05:00
Ammar Ahmed
c4152f0fc0 mobile: set editor reset time to 10 minutes 2024-05-10 14:53:10 +05:00
Ammar Ahmed
d803005637 mobile: release v3.0.3 2024-05-10 14:47:27 +05:00
Ammar Ahmed
53d896be2b mobile: fix opening exported files after export 2024-05-10 14:44:12 +05:00
Abdullah Atta
8c37c6409a ci: use actions/upload-artifact@v4 2024-05-10 12:36:56 +05:00
Abdullah Atta
9195244890 web: fix tests 2024-05-10 12:31:55 +05:00
Ammar Ahmed
152794ccd0 mobile: fix image filename in picker 2024-05-10 12:24:40 +05:00
Ammar Ahmed
2e8b234b63 mobile: fix editor content update on realtime sync 2024-05-10 12:24:40 +05:00
Abdullah Atta
055c647698 web: add Audios tab in attachments dialog 2024-05-10 12:08:38 +05:00
Abdullah Atta
2b7bcfe7c6 web: bump version to 3.0.3 2024-05-10 12:00:06 +05:00
Abdullah Atta
3c8b8a7bc9 web: fix real time sync test 2024-05-10 11:58:56 +05:00
Abdullah Atta
fde45fb6de web: saving timeout notice shown every time 2024-05-10 11:58:44 +05:00
Abdullah Atta
4a616a3e67 web: fix tab not updating when note is locked/unlocked 2024-05-10 11:27:26 +05:00
Abdullah Atta
bd0a338dd6 core: fix dateEdited not updating on saving locked content 2024-05-10 10:46:11 +05:00
Abdullah Atta
5ab721e3e2 core: fix async iterator on sql collection only returning dateCreated & id 2024-05-10 10:22:03 +05:00
Ammar Ahmed
cb70bb9429 mobile: fix errors 2024-05-10 09:22:23 +05:00
Abdullah Atta
82cadd473c web: show error if saving takes more than 30 seconds 2024-05-10 09:22:23 +05:00
Ammar Ahmed
0e8ea3ddeb mobile: fix editor loading 2024-05-10 09:22:23 +05:00
Ammar Ahmed
511acc5fca mobile: handle editor not responding 2024-05-10 09:22:23 +05:00
Ammar Ahmed
001cf8ee92 mobile: add editor lock safety 2024-05-10 09:22:23 +05:00
Muhammad Ali
a965cd694b web: fix color not updating on note (#5573) 2024-05-10 09:16:59 +05:00
Ammar Ahmed
d3c1c1959b mobile: fix wording of push/pull sync 2024-05-09 17:39:35 +05:00
Abdullah Atta
9a866a552a web: replace force sync with force push/pull 2024-05-09 17:39:35 +05:00
Ammar Ahmed
837c296821 mobile: push/pull force sync 2024-05-09 17:39:35 +05:00
Abdullah Atta
9aed6faed8 common: fix locked notes not getting exported 2024-05-09 17:39:16 +05:00
Abdullah Atta
734f491e8d editor: fix crash due to undefined is not an object (evaluating 'a.nodeSize') (#5553) 2024-05-09 17:38:18 +05:00
Ammar Ahmed
0ea13024e9 mobile: fix editor crash due to position (#5519)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-05-09 15:55:33 +05:00
Abdullah Atta
cb27039245 web: close shared service on window close 2024-05-09 10:30:46 +05:00
Abdullah Atta
714ac2deac core: do not use auth access tokens as normal access tokens 2024-05-09 10:30:25 +05:00
Abdullah Atta
d605bc29bf desktop: fix crash on toggling custom dns (#5538) 2024-05-09 10:15:14 +05:00
Abdullah Atta
080101bc45 core: fix database disk image malformed error (#5525) 2024-05-09 10:08:44 +05:00
Abdullah Atta
4819e17da3 core: rerun sync if there are unsynced changes after sync complete 2024-05-08 16:48:59 +05:00
Ammar Ahmed
69a5d5a1b6 global: update .gitignore 2024-05-08 10:43:35 +05:00
Ammar Ahmed
68a6d6c313 mobile: fix crash when taking camera picture 2024-05-08 09:48:28 +05:00
Ammar Ahmed
c51ef4d5f6 mobile: fix camera permission 2024-05-08 09:48:28 +05:00
Ammar Ahmed
d9c5c1f893 mobile: remove margin in editor 2024-05-08 09:47:56 +05:00
Ammar Ahmed
5a75ca73a9 mobile: fix editor scrolling on update 2024-05-08 09:47:56 +05:00
Abdullah Atta
626d393376 desktop: use yarn instead of npx for build scripts 2024-05-08 09:14:55 +05:00
Abdullah Atta
68d06ff3f8 core: fix legacy backup restoration 2024-05-07 15:57:22 +05:00
Abdullah Atta
79f20d100c core: fix File already exists error on duplicate downloads 2024-05-07 12:57:51 +05:00
Ammar Ahmed
9d5bec3bfb ci: upload sourcemaps 2024-05-07 11:49:58 +05:00
Abdullah Atta
ac96354b4c core: handle case when updating item while a push is ongoing 2024-05-06 22:04:04 +05:00
Abdullah Atta
63f44d6fbc core: set synced to false on removing content by note id 2024-05-06 15:45:45 +05:00
Abdullah Atta
f9bfa88c83 core: set synced to false on unlinking relations 2024-05-06 15:45:45 +05:00
Ammar Ahmed
ef192f3d22 mobile: release v3.0.2 2024-05-05 01:14:20 +05:00
Abdullah Atta
c9a8134265 core: fix notes & notebooks sorting 2024-05-05 00:00:49 +05:00
Ammar Ahmed
cd7a5d2ec0 mobile: fix error not rendered correctly 2024-05-04 23:52:47 +05:00
Abdullah Atta
b92bc03fa9 web: handle errors when saving attachment 2024-05-04 23:51:42 +05:00
Abdullah Atta
5f5e472800 web: bump version to 3.0.2 2024-05-04 23:35:49 +05:00
Ammar Ahmed
0951906cac mobile: do not check for .nnbackup file in zip 2024-05-04 23:29:59 +05:00
Abdullah Atta
6f0fe03760 core: do not throw if user not found during legacy backup 2024-05-04 23:28:37 +05:00
Muhammad Ali
159ce97e42 core: fix crash due to unsanitized sort options (#5324)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-05-04 23:26:58 +05:00
Ammar Ahmed
8a9848634c mobile: fix note not opening from notifications 2024-05-04 23:26:07 +05:00
Ammar Ahmed
282705a683 mobile: add logging 2024-05-04 23:11:30 +05:00
Ammar Ahmed
c91b706ed0 mobile: correctly show mime type 2024-05-04 23:11:30 +05:00
Ammar Ahmed
16afc07b2f mobile: fix all attachments show in audios 2024-05-04 23:11:30 +05:00
Ammar Ahmed
04f8be5a62 core: add missing audios attachments filter 2024-05-04 23:11:30 +05:00
Ammar Ahmed
0e8a5ca1fa editor: fix n.startsWith is not a function 2024-05-04 23:03:54 +05:00
Ammar Ahmed
1491bf771e Fix image and file uploads on mobile (#5304)
* mobile: fix image file formats support

* mobile: fix image and file uploads

---------

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-05-04 23:02:07 +05:00
Abdullah Atta
0f5ce465c3 web: add logging related to attachments 2024-05-04 22:51:01 +05:00
Abdullah Atta
647ff7ae0e web: prevent overwriting uploaded attachments 2024-05-04 22:51:01 +05:00
Abdullah Atta
45a552adef web: handle errors during uploaded file size check 2024-05-04 22:51:01 +05:00
Abdullah Atta
8e207d787d web: delete file before reuploading 2024-05-04 22:51:01 +05:00
Abdullah Atta
00754593ac web: fix attachment not getting marked as failed on error 2024-05-04 22:51:01 +05:00
Abdullah Atta
b22770f1ae core: do not reset attachment dateUploaded when marking as failed 2024-05-04 22:51:01 +05:00
Abdullah Atta
523da4e91f core: only delete attachment file (not the metadata) on sync conflict 2024-05-04 22:51:01 +05:00
Abdullah Atta
de6c8154a3 web: fix password recovery not working 2024-05-04 22:50:44 +05:00
Abdullah Atta
9ef32e8081 web: do not hide attachment manager for logged out user 2024-05-01 20:14:20 +05:00
Abdullah Atta
5f53593a26 web: do not throw if backup doesn't contain .nnbackup file 2024-05-01 20:12:23 +05:00
Abdullah Atta
1f404944d9 web: re-enable service worker 2024-05-01 16:38:09 +05:00
Ammar Ahmed
23dca36475 mobile: release v3.0.1 2024-05-01 16:34:01 +05:00
Ammar Ahmed
3dd8e20e9a mobile: add logging when loading attachments 2024-05-01 16:29:37 +05:00
Abdullah Atta
8f412f9487 editor: fix padding of react node views on mobile 2024-05-01 16:29:12 +05:00
Abdullah Atta
bf790219db web: bump version to 3.0.1 2024-05-01 16:16:08 +05:00
Ammar Ahmed
c6af310b2d mobile: add notebook search 2024-05-01 16:10:26 +05:00
Abdullah Atta
8d89b5dca9 web: bring back notebook search in move note dialog 2024-05-01 16:10:26 +05:00
Abdullah Atta
fa35244607 editor: update test snapshosts 2024-05-01 16:09:06 +05:00
Abdullah Atta
d96815fa36 ci: remove beta publish workflows 2024-05-01 15:23:43 +05:00
Abdullah Atta
4a9f5b1d8b core: remove .only from trash tests 2024-05-01 15:19:57 +05:00
Ammar Ahmed
43fac79011 mobile: fix moving notebook on ios 2024-05-01 15:18:55 +05:00
Ammar Ahmed
f2c62c8d8b mobile: fix moving notebook on ios 2024-05-01 15:18:55 +05:00
Ammar Ahmed
70c9dd0083 mobile: reduce font size in notebook header 2024-05-01 15:15:37 +05:00
Ammar Ahmed
ae6d8c5f2b mobile: hide breadcrumbs in top level notebooks 2024-05-01 15:15:37 +05:00
Abdullah Atta
38e2250fd1 web: log attachment on failure 2024-05-01 14:55:49 +05:00
Ammar Ahmed
17e2c5a8a2 desktop: fix app icon size for macos 2024-05-01 14:53:32 +05:00
Ammar Ahmed
2e226f0f72 mobile: fix cannot read property 'sortDirection' of undefined 2024-05-01 13:34:48 +05:00
Ammar Ahmed
167c54578b mobile: fix scrolling broken in reminder -> repeat -> month 2024-05-01 13:34:37 +05:00
Abdullah Atta
fb656af5ca web: sort tags by title in editor header 2024-05-01 13:34:20 +05:00
Ammar Ahmed
ddfab272d2 common: sort tags by title in resolved items 2024-05-01 13:34:20 +05:00
Ammar Ahmed
28958311c1 mobile: fix share extention crash on sharing from some apps 2024-05-01 13:34:06 +05:00
Ammar Ahmed
f1b8275b4b mobile: add support for setting note title form quick note widget 2024-05-01 13:34:06 +05:00
Ammar Ahmed
c8c75d40dd mobile: fix theme search returns empty results 2024-05-01 13:33:50 +05:00
Ammar Ahmed
bace90808d mobile: fix footer size for notebook screen 2024-05-01 13:33:31 +05:00
Ammar Ahmed
fe7c7e8db4 mobile: fix notebook-sheet position 2024-05-01 13:32:02 +05:00
Ammar Ahmed
519a569366 mobile: fix notebook sheet flicker 2024-05-01 13:32:02 +05:00
Ammar Ahmed
89d602a40f mobile: do not lock app when requesting permission 2024-05-01 13:31:38 +05:00
Abdullah Atta
a8db65b5af editor: fix performance issues due to using portals for react node views 2024-05-01 13:29:55 +05:00
Ammar Ahmed
716239abfd mobile: fix selection header 2024-05-01 13:21:31 +05:00
Ammar Ahmed
62dd5a5416 mobile: fix vault unlock with biometrics 2024-05-01 13:19:00 +05:00
Ammar Ahmed
96558c5df5 mobile: fix search bar position on ios 2024-05-01 13:18:21 +05:00
Ammar Ahmed
45997dd2e4 editor: fix scroll into view should bring heading to top 2024-05-01 13:16:54 +05:00
Ammar Ahmed
bde190f01c mobile: render settings icon if no profile pic is set 2024-05-01 13:15:24 +05:00
Ammar Ahmed
042c97688f mobile: add quick theme switch button 2024-05-01 13:15:24 +05:00
Ammar Ahmed
95b5114070 mobile: fix confusing settings/sync button 2024-05-01 13:15:24 +05:00
Ammar Ahmed
20742e2a1c mobile: fix back navigation from editor (#4867)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-05-01 13:13:44 +05:00
Ammar Ahmed
1e3149284e mobile: report error when migration fails due to backup error 2024-05-01 13:01:41 +05:00
Ammar Ahmed
d77c09f1fd mobile: fix opening internal link in readonly mode 2024-05-01 13:00:58 +05:00
Ammar Ahmed
aa602ea8e9 mobile: fix position of context menu 2024-05-01 12:59:50 +05:00
Ammar Ahmed
8ab6b6dc31 mobile: skip checking encrypted backup if user is logged out 2024-05-01 12:57:42 +05:00
Ammar Ahmed
404d136a73 mobile: fix sidebar order not loading on app launch 2024-05-01 12:50:13 +05:00
Abdullah Atta
cf9aeecd7e editor: fix placeholder showing always in first line of editor 2024-05-01 12:37:48 +05:00
Abdullah Atta
1adda96ce2 editor: fix many papercut ux issues in search replace 2024-05-01 12:36:00 +05:00
Abdullah Atta
6dd6d818c9 editor: add support for multi line search/replace 2024-05-01 12:36:00 +05:00
Abdullah Atta
c7e83961d4 web: show all notes if query is empty in note linking dialog 2024-05-01 12:35:38 +05:00
Abdullah Atta
03428e5848 core: allow full FTS5 search syntax 2024-05-01 12:35:38 +05:00
Abdullah Atta
d0fceed56d web: improve app performance by reducing react rerenders 2024-05-01 12:34:43 +05:00
Abdullah Atta
b8c68b5163 web: fix middle pane size increasing on exiting focus mode 2024-04-30 21:52:25 +05:00
Abdullah Atta
2496004896 web: fix position of image preview toolbar 2024-04-30 21:51:57 +05:00
Abdullah Atta
16ec35de25 web: do not change properties top on web app 2024-04-30 21:51:57 +05:00
Abdullah Atta
d248b8e2b8 web: properties & toc side bars must be below the titlebar 2024-04-30 21:51:57 +05:00
Abdullah Atta
641e7484e3 desktop: add custom dns toggle in settings 2024-04-30 21:51:35 +05:00
Abdullah Atta
a46409ff44 core: add more fts trigger tests 2024-04-30 21:51:17 +05:00
Abdullah Atta
5dbabc2706 core: fix database disk image is malformed error on updating deleted content 2024-04-30 21:51:17 +05:00
Abdullah Atta
2d4d656dd9 web: canceling backup on password change should abort 2024-04-30 21:50:48 +05:00
Abdullah Atta
ab51b22e4a web: do not ask for password when downloading backup during recovery 2024-04-30 21:50:32 +05:00
Abdullah Atta
781d09daca web: fix navigation menu resize issues 2024-04-30 21:50:21 +05:00
Abdullah Atta
f21533e5e4 core: fix sql collection only getting first batch on iteration 2024-04-30 21:50:00 +05:00
Abdullah Atta
2ea9b04863 web: fix toggles in properties not working 2024-04-30 21:49:37 +05:00
Abdullah Atta
675d517cbc web: reduce zoom factor step to 0.1 2024-04-30 21:49:13 +05:00
Abdullah Atta
c336c57bda web: disable pwa 2024-04-30 21:48:28 +05:00
Abdullah Atta
7b5fa09c71 web: fix all users are premium 2024-04-30 13:15:54 +05:00
Ammar Ahmed
d85974ed7a mobile: fix editor crash in v3 2024-04-30 12:09:15 +05:00
Ammar Ahmed
9598ea7542 mobile: fix wrong version in update sheet 2024-04-30 10:10:17 +05:00
Ammar Ahmed
42048ad53a mobile: fix multi-selection ux 2024-04-30 00:42:27 +05:00
Ammar Ahmed
1fbd2efb92 mobile: fix crash during backup 2024-04-30 00:17:25 +05:00
Ammar Ahmed
fe91ef79a0 global: update logo 2024-04-29 13:24:54 +05:00
Abdullah Atta
583d585b2f desktop: re-enable arm64 builds 2024-04-29 11:53:09 +05:00
Abdullah Atta
94b28f86ca desktop: remove channel when publishing 2024-04-29 11:38:31 +05:00
Abdullah Atta
f927f05f86 desktop: fix spawn EINVAL error on build 2024-04-29 11:15:43 +05:00
Abdullah Atta
bde8dea048 global: update package lockfiles 2024-04-29 11:15:21 +05:00
Abdullah Atta
511bea9b23 editor: update snapshots 2024-04-28 10:54:10 +05:00
Abdullah Atta
eda95530de desktop: set generateUpdatesFilesForAllChannels to false 2024-04-26 15:59:42 +05:00
Abdullah Atta
5173decaa5 crypto: upgrade sodium-native to 4.1.1 2024-04-26 15:48:42 +05:00
Abdullah Atta
00dcb55f6e ci: migrate to TrustedSigning from AzureCodeSigning 2024-04-26 15:44:29 +05:00
Abdullah Atta
6dbe1073cd ci: use AzureCodeSigning version 0.3.4 2024-04-26 15:30:53 +05:00
Abdullah Atta
9be3e4fcfa desktop: install node-gyp-build 2024-04-26 15:30:03 +05:00
Abdullah Atta
f9f0a866bd setup: fix infinite loop in postinstall 2024-04-26 15:18:49 +05:00
Abdullah Atta
daafeb2fa8 setup: retry postinstall commands 3 times 2024-04-26 15:17:49 +05:00
Abdullah Atta
a92b34b81c ci: back to node@20.x 2024-04-26 15:14:25 +05:00
Abdullah Atta
e29b6838b3 ci: use node@18.x 2024-04-26 14:53:23 +05:00
Ammar Ahmed
16ff7c92e4 mobile: update android version 2024-04-26 14:46:56 +05:00
Abdullah Atta
07e1ea6edf ci: use yarn instead of npx when executing electron-builder 2024-04-26 14:41:28 +05:00
Abdullah Atta
b26ede95fb desktop: use prebuilds on linux-arm 2024-04-26 14:01:32 +05:00
Abdullah Atta
971793a3ad desktop: use net.fetch for handling protocol requests 2024-04-26 14:01:32 +05:00
Abdullah Atta
6a2b5e1499 desktop: upgrade electron to 29 2024-04-26 14:01:32 +05:00
Abdullah Atta
d18b775d06 web: use same logic for uploading attachment on desktop & web 2024-04-26 14:01:32 +05:00
Abdullah Atta
d6cc899d95 web: close note after removing attachment permanently 2024-04-26 14:01:32 +05:00
Abdullah Atta
ea10058276 web: fix linked notes not showing in attachment context menu 2024-04-26 14:01:32 +05:00
Abdullah Atta
568c88be3e fs: fix out of bounds error when reading chunks 2024-04-26 14:01:32 +05:00
Abdullah Atta
f047552f18 core: fix attachment not getting detached from note's content 2024-04-26 14:01:32 +05:00
Ammar Ahmed
9a58430b5d mobile: release v3.0.0 2024-04-26 11:29:59 +05:00
Ammar Ahmed
205885edf5 mobile: disable app lock when picking file to restore backup 2024-04-26 11:29:59 +05:00
Abdullah Atta
8ffb49b700 desktop: fix Unexpected token 'with' on build 2024-04-26 10:30:44 +05:00
Abdullah Atta
25b18a2edd ci: pin node version to 20.2.0 2024-04-26 10:24:54 +05:00
Abdullah Atta
cfea85c8fb web: add v3 feature highlights 2024-04-26 09:35:06 +05:00
Abdullah Atta
82fc6d31c3 web: fix new notes not getting assigned to default notebook 2024-04-26 09:15:35 +05:00
Abdullah Atta
fb2de1753e web: fix full screen test 2024-04-26 09:06:00 +05:00
Ammar Ahmed
ceb5ce5cdd mobile: add quick settings tile on android 2024-04-26 08:49:38 +05:00
Ammar Ahmed
98d443001c mobile: fix no placeholder text on share widget 2024-04-26 08:49:38 +05:00
Abdullah Atta
5115dee069 core: fix crash when converting html to text 2024-04-26 08:28:32 +05:00
Abdullah Atta
0dc29c7026 core: refactor reminder upcoming time sql 2024-04-26 07:49:47 +05:00
Muhammad Ali
a5eb5e9df3 core: fix incorrect upcoming time in weekly/monthly reminders (#4464) 2024-04-25 16:22:34 +05:00
Ammar Ahmed
b680575328 mobile: fix padding of internal editors on mobile 2024-04-25 15:50:17 +05:00
Ammar Ahmed
c94ef616a5 mobile: improve ui of side menu user section 2024-04-25 15:31:10 +05:00
Ammar Ahmed
3ae40c09fc mobile: fix db init in background failing 2024-04-25 15:31:10 +05:00
Ammar Ahmed
5de4e00ce2 mobile: enable background sync by default 2024-04-25 15:31:10 +05:00
Ammar Ahmed
99e4e61a55 mobile: fix notebook sheet does not remember last position 2024-04-25 15:31:10 +05:00
Abdullah Atta
1ad7bfb82f web: fix settings dialog side bar transparent from bottom 2024-04-25 14:43:21 +05:00
Abdullah Atta
30ef679432 web: fix empty editor after unlocking app 2024-04-25 13:40:11 +05:00
Abdullah Atta
bb97f5e757 clipper(web): update app logo 2024-04-25 13:00:29 +05:00
Abdullah Atta
6cd5a04470 desktop: update app logo 2024-04-25 13:00:17 +05:00
Abdullah Atta
0c32ebf163 web: update app logo 2024-04-25 13:00:07 +05:00
Abdullah Atta
e150e08f29 editor: fix crash on dragging a react node 2024-04-25 10:38:40 +05:00
Ammar Ahmed
bbd603f10c docs: update logo 2024-04-25 10:23:05 +05:00
Ammar Ahmed
677944cba9 docs: remove mentions of topic 2024-04-25 10:23:05 +05:00
Ammar Ahmed
dd2cee53d3 mobile: update app logo 2024-04-25 10:23:05 +05:00
Ammar Ahmed
60c8d5ac89 mobile: fix notes in notifications not launch on app start 2024-04-25 10:23:05 +05:00
Ammar Ahmed
59276de406 mobile: fix background sync 2024-04-25 10:23:05 +05:00
Abdullah Atta
67981be516 desktop: clonableCryptoKey is always true on desktop 2024-04-25 10:17:37 +05:00
Abdullah Atta
77dfcec08c web: fix attachments ui in properties 2024-04-24 11:45:15 +05:00
Abdullah Atta
c4360ab95f common: fix exports 2024-04-24 11:45:00 +05:00
Abdullah Atta
17c4b72381 web: minor ui fix 2024-04-23 09:35:01 +05:00
Ammar Ahmed
e91cc2bdca mobile: support pinned editor tabs 2024-04-22 15:55:10 +05:00
Abdullah Atta
811f2305ec core: disable transactions for now 2024-04-22 12:23:43 +05:00
Abdullah Atta
98a707b5f5 setup: remove --prefer-offline 2024-04-22 09:52:26 +05:00
Abdullah Atta
aa9b24fa10 core: do not run auto sync on changes in notehistory & sessioncontent collections 2024-04-22 09:37:54 +05:00
Abdullah Atta
0b3060e4c0 web: improve error reporting if a query fails 2024-04-22 09:14:10 +05:00
Ammar Ahmed
d523db84cc mobile: fix back gesture handler broken in editor 2024-04-22 08:38:32 +05:00
Abdullah Atta
6a6a9ae6b3 ci: install @zip.js/zip.js before running web tests 2024-04-20 19:48:18 +05:00
Abdullah Atta
b1bc28e069 ci: install dayjs before running web tests 2024-04-20 19:41:47 +05:00
Abdullah Atta
f95c068bc1 web: fix all tests 2024-04-20 16:42:11 +05:00
Abdullah Atta
0e4cd087d4 core: add tests for virtualized grouping 2024-04-20 16:42:11 +05:00
Ammar Ahmed
f217b8cfec mobile: add borders to dialogs & sheet 2024-04-19 12:26:25 +05:00
Ammar Ahmed
d3787efbf0 web: fix wording of trash interval 2024-04-19 12:26:09 +05:00
Ammar Ahmed
e279878b12 mobile: improve move note sheet ux 2024-04-19 12:00:13 +05:00
Ammar Ahmed
154a227be0 mobile: fix placeholder item height in compact mode 2024-04-19 10:32:53 +05:00
Ammar Ahmed
756259c907 mobile: minor improvement in list 2024-04-19 10:23:12 +05:00
Ammar Ahmed
453ed4820a mobile: remove beta tag from reminders 2024-04-19 10:23:12 +05:00
Ammar Ahmed
10ab612366 mobile: fix section header title for rtl lang 2024-04-19 10:23:12 +05:00
Ammar Ahmed
8dc4f7ed3d mobile: remove v3 beta notice on login page 2024-04-19 10:23:12 +05:00
Ammar Ahmed
a73538bafc mobile: use border color for borders 2024-04-19 10:23:12 +05:00
Abdullah Atta
014887c7dc core: fix virtualized grouping showing duplicate groups 2024-04-19 10:14:22 +05:00
Abdullah Atta
246e49bd8c web: remove beta tag 2024-04-18 15:48:47 +05:00
Ammar Ahmed
146ac12935 mobile: fix search not update on changing item property 2024-04-18 14:48:37 +05:00
Abdullah Atta
4f5245adcf web: fix changing readonly doesn't update editor 2024-04-18 13:26:59 +05:00
Abdullah Atta
0bc0cc5729 Merge branch 'v3-beta' 2024-04-18 13:19:33 +05:00
Licaon_Kter
c54759e766 Remove gradle property unused in Java 17
Signed-off-by: Licaon_Kter <licaon-kter@users.noreply.github.com>
2024-03-22 23:17:45 +05:00
Ammar Ahmed
f15446b1d6 mobile: bump version code 2024-03-21 11:45:03 +05:00
448 changed files with 73032 additions and 5762 deletions

View File

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

View File

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

View File

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

View File

@@ -98,7 +98,10 @@ jobs:
api-private-key: ${{ secrets.API_KEY }}
- name: Upload Notesnook.ipa to Github
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: Notesnook.ipa
path: Notesnook.ipa
name: Notesnook.zip
path: |
Notesnook.ipa
apps/mobile/native/ios/**/*.map
packages/editor-mobile/sourcemaps/*.map

View File

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

View File

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

View File

@@ -1,32 +0,0 @@
name: Publish @notesnook/web v3 Beta
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Build
run: npm run build:web
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,23 +1,23 @@
{
"name": "@notesnook/desktop",
"version": "3.0.14-beta",
"version": "3.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.0.14-beta",
"version": "3.0.3",
"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.4.0",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.4",
"icojs": "^0.17.1",
"sodium-native": "^4.0.6",
"sodium-native": "^4.1.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
@@ -26,12 +26,13 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^28.2.1",
"electron-builder": "^24.9.1",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.0",
"prebuildify": "^6.0.0",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",
"tree-kill": "^1.2.2",
"undici": "^6.6.1"
"undici": "^6.14.1"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
@@ -69,9 +70,9 @@
}
},
"node_modules/@electron/asar": {
"version": "3.2.8",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.8.tgz",
"integrity": "sha512-cmskk5M06ewHMZAplSiF4AlME3IrnnZhKnWbtwKVLRkdJkKyUVjMLhDIiPIx/+6zQWVlKX/LtmK9xDme7540Sg==",
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.9.tgz",
"integrity": "sha512-Vu2P3X2gcZ3MY9W7yH72X9+AMXwUQZEJBrsPIbX0JsdllLtoh62/Q8Wg370/DawIEVKOyfD6KtTLo645ezqxUA==",
"dev": true,
"dependencies": {
"commander": "^5.0.0",
@@ -170,9 +171,9 @@
}
},
"node_modules/@electron/notarize": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.1.0.tgz",
"integrity": "sha512-Q02xem1D0sg4v437xHgmBLxI2iz/fc0D4K7fiVWHa/AnW8o7D751xyKNXgziA6HrTOme9ul1JfWN5ark8WH1xA==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz",
"integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==",
"dev": true,
"dependencies": {
"debug": "^4.1.1",
@@ -232,9 +233,9 @@
}
},
"node_modules/@electron/universal": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.4.1.tgz",
"integrity": "sha512-lE/U3UNw1YHuowNbTmKNs9UlS3En3cPgwM5MI+agIgr/B1hSze9NdOP0qn7boZaI9Lph8IDv3/24g9IxnJP7aQ==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz",
"integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==",
"dev": true,
"dependencies": {
"@electron/asar": "^3.2.1",
@@ -654,13 +655,100 @@
"node": ">=12"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
"integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==",
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"engines": {
"node": ">=14"
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@malept/cross-spawn-promise": {
@@ -719,6 +807,16 @@
"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",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@@ -968,26 +1066,25 @@
"dev": true
},
"node_modules/app-builder-lib": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.9.1.tgz",
"integrity": "sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==",
"version": "24.13.3",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz",
"integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==",
"dev": true,
"dependencies": {
"@develar/schema-utils": "~2.6.5",
"@electron/notarize": "2.1.0",
"@electron/notarize": "2.2.1",
"@electron/osx-sign": "1.0.5",
"@electron/universal": "1.4.1",
"@electron/universal": "1.5.1",
"@malept/flatpak-bundler": "^0.4.0",
"@types/fs-extra": "9.0.13",
"7zip-bin": "~5.2.0",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"builder-util": "24.13.1",
"builder-util-runtime": "9.2.4",
"chromium-pickle-js": "^0.2.0",
"debug": "^4.3.4",
"ejs": "^3.1.8",
"electron-publish": "24.8.1",
"electron-publish": "24.13.1",
"form-data": "^4.0.0",
"fs-extra": "^10.1.0",
"hosted-git-info": "^4.1.0",
@@ -1004,12 +1101,16 @@
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"dmg-builder": "24.13.3",
"electron-builder-squirrel-windows": "24.13.3"
}
},
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"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",
@@ -1098,9 +1199,9 @@
]
},
"node_modules/better-sqlite3-multiple-ciphers": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-9.4.0.tgz",
"integrity": "sha512-uQoBdShnpCdAPUmCPuLSrHtfSHAxjIFQvLnMbXHsvbWkfBP3sPfnnIaUTz7KHYRqgWNLlcQfs+9F9uqvMqau6Q==",
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/better-sqlite3-multiple-ciphers/-/better-sqlite3-multiple-ciphers-9.5.0.tgz",
"integrity": "sha512-6A9xBRvssONJW07Lur6pmkaUwlCGz1bnjFmXrrAR3qXETUBk92OI4C/+LhMOatn5wQ8AQzLScFZC/CKUpxkTaw==",
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.5.0",
@@ -1233,16 +1334,16 @@
"dev": true
},
"node_modules/builder-util": {
"version": "24.8.1",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.8.1.tgz",
"integrity": "sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==",
"version": "24.13.1",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz",
"integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==",
"dev": true,
"dependencies": {
"@types/debug": "^4.1.6",
"7zip-bin": "~5.2.0",
"app-builder-bin": "4.0.0",
"bluebird-lst": "^1.0.9",
"builder-util-runtime": "9.2.3",
"builder-util-runtime": "9.2.4",
"chalk": "^4.1.2",
"cross-spawn": "^7.0.3",
"debug": "^4.3.4",
@@ -1269,9 +1370,9 @@
}
},
"node_modules/builder-util/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"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",
@@ -1475,13 +1576,59 @@
"dev": true
},
"node_modules/config-file-ts": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.4.tgz",
"integrity": "sha512-cKSW0BfrSaAUnxpgvpXPLaaW/umg4bqg4k3GO1JqlRfpx+d5W0GDXznCMkWotJQek5Mmz1MJVChQnz3IVaeMZQ==",
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz",
"integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==",
"dev": true,
"dependencies": {
"glob": "^7.1.6",
"typescript": "^4.0.2"
"glob": "^10.3.10",
"typescript": "^5.3.3"
}
},
"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==",
"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"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"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==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"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==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/core-util-is": {
@@ -1670,14 +1817,14 @@
}
},
"node_modules/dmg-builder": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.9.1.tgz",
"integrity": "sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==",
"version": "24.13.3",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz",
"integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==",
"dev": true,
"dependencies": {
"app-builder-lib": "24.9.1",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"app-builder-lib": "24.13.3",
"builder-util": "24.13.1",
"builder-util-runtime": "9.2.4",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.2",
"js-yaml": "^4.1.0"
@@ -1687,9 +1834,9 @@
}
},
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"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",
@@ -1724,16 +1871,31 @@
"node": ">=8"
}
},
"node_modules/dotenv": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/dotenv-expand": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
"dev": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/ejs": {
"version": "3.1.9",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true,
"dependencies": {
"jake": "^10.8.5"
@@ -1746,14 +1908,14 @@
}
},
"node_modules/electron": {
"version": "28.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-28.2.1.tgz",
"integrity": "sha512-wlzXf+OvOiVlBf9dcSeMMf7Q+N6DG+wtgFbMK0sA/JpIJcdosRbLMQwLg/LTwNVKIbmayqFLDp4FmmFkEMhbYA==",
"version": "29.3.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-29.3.1.tgz",
"integrity": "sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^18.11.18",
"@types/node": "^20.9.0",
"extract-zip": "^2.0.1"
},
"bin": {
@@ -1764,16 +1926,16 @@
}
},
"node_modules/electron-builder": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.9.1.tgz",
"integrity": "sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==",
"version": "24.13.3",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz",
"integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==",
"dev": true,
"dependencies": {
"app-builder-lib": "24.9.1",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"app-builder-lib": "24.13.3",
"builder-util": "24.13.1",
"builder-util-runtime": "9.2.4",
"chalk": "^4.1.2",
"dmg-builder": "24.9.1",
"dmg-builder": "24.13.3",
"fs-extra": "^10.1.0",
"is-ci": "^3.0.0",
"lazy-val": "^1.0.5",
@@ -1790,9 +1952,9 @@
}
},
"node_modules/electron-builder/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"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",
@@ -1803,14 +1965,14 @@
}
},
"node_modules/electron-publish": {
"version": "24.8.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.8.1.tgz",
"integrity": "sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==",
"version": "24.13.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz",
"integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==",
"dev": true,
"dependencies": {
"@types/fs-extra": "^9.0.11",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"builder-util": "24.13.1",
"builder-util-runtime": "9.2.4",
"chalk": "^4.1.2",
"fs-extra": "^10.1.0",
"lazy-val": "^1.0.5",
@@ -1818,9 +1980,9 @@
}
},
"node_modules/electron-publish/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"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",
@@ -1858,6 +2020,15 @@
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "20.12.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -1952,15 +2123,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/execspawn": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/execspawn/-/execspawn-1.0.1.tgz",
"integrity": "sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg==",
"dev": true,
"dependencies": {
"util-extend": "^1.0.1"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -2061,6 +2223,22 @@
"node": ">=8"
}
},
"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==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
},
"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",
@@ -2571,12 +2749,12 @@
}
},
"node_modules/isbinaryfile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.0.tgz",
"integrity": "sha512-UDdnyGvMajJUWCkib7Cei/dvyJrrvo4FIrsvSFWdPpXSUorzXrDJ0S+X5Q4ZlasfPjca4yqCNNsjbCeiy8FFeg==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.2.tgz",
"integrity": "sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==",
"dev": true,
"engines": {
"node": ">= 14.0.0"
"node": ">= 18.0.0"
},
"funding": {
"url": "https://github.com/sponsors/gjtorikian/"
@@ -2588,6 +2766,24 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"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==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jake": {
"version": "10.8.7",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
@@ -2977,6 +3173,31 @@
"node": ">=8"
}
},
"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==",
"dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"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==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/peek-readable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
@@ -3054,12 +3275,11 @@
}
},
"node_modules/prebuildify": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.0.tgz",
"integrity": "sha512-DEvK4C3tcimIp7Pzqbs036n9i6CTKGp1XVEpMnr4wV3enKU5sBogPP+lP3KZw7993i42bXnsd5eIxAXQ566Cqw==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
"dev": true,
"dependencies": {
"execspawn": "^1.0.1",
"minimist": "^1.2.5",
"mkdirp-classic": "^0.5.3",
"node-abi": "^3.3.0",
@@ -3154,15 +3374,6 @@
"node": ">=12.0.0"
}
},
"node_modules/read-config-file/node_modules/dotenv": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -3238,21 +3449,6 @@
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/roarr": {
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
@@ -3390,6 +3586,18 @@
"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",
@@ -3470,12 +3678,12 @@
}
},
"node_modules/sodium-native": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.6.tgz",
"integrity": "sha512-uYsyycwcz9kYDwpXxJmL2YZosynsxcP6RPySbARVJdC9uNDa2CMjzJ7/WsMMvThKgvAYsBWdZc7L/WSVj9lTcA==",
"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.6.0"
"node-gyp-build": "^4.8.0"
}
},
"node_modules/source-map": {
@@ -3534,6 +3742,21 @@
"node": ">=8"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -3545,6 +3768,19 @@
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
@@ -3594,9 +3830,9 @@
}
},
"node_modules/tar": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dev": true,
"dependencies": {
"chownr": "^2.0.0",
@@ -3657,15 +3893,12 @@
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="
},
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"dev": true,
"dependencies": {
"rimraf": "^3.0.0"
},
"engines": {
"node": ">=8.17.0"
"node": ">=14.14"
}
},
"node_modules/tmp-promise": {
@@ -3754,30 +3987,33 @@
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.6.1.tgz",
"integrity": "sha512-J0GaEp0ztu/grIE2Uq57AbK6TRb+bWbOlxu0POCzhFKA6LKbwSAev+hDQaQcgUUA9CPs8Ky+cauzTHnQrtAQEA==",
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.14.1.tgz",
"integrity": "sha512-mAel3i4BsYhkeVPXeIPXVGPJKeBzqCieZYoFsbWfUzd68JmHByhc1Plit5WlylxXFaGpgkZB8mExlxnt+Q1p7A==",
"dev": true,
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=18.0"
"node": ">=18.17"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
@@ -3806,12 +4042,6 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/util-extend": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
"integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==",
"dev": true
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
@@ -3857,6 +4087,24 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View File

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

View File

@@ -1,60 +0,0 @@
diff --git a/node_modules/prebuildify/index.js b/node_modules/prebuildify/index.js
index ca9829a..7729aaf 100644
--- a/node_modules/prebuildify/index.js
+++ b/node_modules/prebuildify/index.js
@@ -1,5 +1,4 @@
var proc = require('child_process')
-var execspawn = require('execspawn')
var os = require('os')
var path = require('path')
var fs = require('fs')
@@ -179,11 +178,12 @@ function copySharedLibs (builds, folder, opts, cb) {
function run (cmd, opts, cb) {
if (!cmd) return cb()
- var child = execspawn(cmd, {
+ var child = proc.spawn(cmd, [], {
cwd: opts.cwd,
env: opts.env,
stdio: 'inherit',
- shell: opts.shell
+ shell: opts.shell || true,
+ windowsHide: true
})
child.on('exit', function (code) {
@@ -230,6 +230,8 @@ function build (target, runtime, opts, cb) {
var child = proc.spawn(opts.nodeGyp, args, {
cwd: opts.cwd,
env: opts.env,
+ shell: opts.shell,
+ windowsHide: true,
stdio: opts.quiet ? 'ignore' : 'inherit'
})
@@ -266,7 +268,11 @@ function strip (file, opts, cb) {
if (!opts.strip || (platform !== 'darwin' && platform !== 'linux')) return cb()
var args = platform === 'darwin' ? [file, '-Sx'] : [file, '--strip-all']
- var child = proc.spawn(opts.stripBin, args, { stdio: 'ignore' })
+ var child = proc.spawn(opts.stripBin, args, {
+ stdio: 'ignore',
+ shell: opts.shell,
+ windowsHide: true
+ })
child.on('exit', function (code) {
if (code) return cb(spawnError(opts.stripBin, code))
@@ -300,7 +306,11 @@ function npmbin (name) {
}
function shell () {
- return os.platform() === 'android' ? 'sh' : undefined
+ switch (os.platform()) {
+ case 'win32': return true
+ case 'android': return 'sh'
+ default: return undefined
+ }
}
function resolveTargets (targets, all, napi, electronCompat) {

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
const t = initTRPC.create();
@@ -55,6 +56,15 @@ export const osIntegrationRouter = t.router({
config.zoomFactor = factor;
}),
customDns: t.procedure.query(() => config.customDns),
setCustomDns: t.procedure
.input(z.boolean().optional())
.mutation(({ input: customDns }) => {
if (customDns) enableCustomDns();
else disableCustomDns();
config.customDns = !!customDns;
}),
proxyRules: t.procedure.query(() => config.proxyRules),
setProxyRules: t.procedure
.input(z.string().optional())

View File

@@ -35,6 +35,7 @@ import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -151,14 +152,8 @@ async function createWindow() {
app.once("ready", async () => {
console.info("App ready. Opening window.");
app.configureHostResolver({
secureDnsServers: [
"https://mozilla.cloudflare-dns.com/dns-query",
"https://dns.quad9.net/dns-query"
],
enableBuiltInResolver: true,
secureDnsMode: "automatic"
});
if (config.customDns) enableCustomDns();
else disableCustomDns();
if (!isDevelopment()) registerProtocol();
await createWindow();

View File

@@ -43,6 +43,7 @@ export const config = {
theme: nativeTheme.themeSource,
automaticUpdates: true,
proxyRules: "",
customDns: true,
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
windowControlsIconColor:

View File

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

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 { protocol } from "electron";
import { net, protocol } from "electron";
import { isDevelopment } from "./index";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
@@ -62,26 +62,7 @@ function registerProtocol() {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
if (request.headers.has("X-Content-Length")) {
request.headers.set(
"Content-Length",
request.headers.get("X-Content-Length") || "0"
);
request.headers.delete("X-Content-Length");
}
const headers = Object.fromEntries(request.headers.entries());
return await fetch(request.url, {
signal: request.signal,
mode: request.mode,
headers,
method: request.method,
body: request.body,
credentials: request.credentials,
referrer: (request as any).referrer,
duplex: "half",
redirect: "manual"
});
return net.fetch(request, { bypassCustomProtocolHandlers: true });
}
});
console.info(`${SCHEME} protocol inteception "successful"`);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

@@ -218,12 +218,7 @@ export default async function downloadAttachment(
}
if (options.base64 || options.text) {
console.log(
"starting decrypt base64 file...",
options.base64,
options.text,
attachment.hash
);
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
return await db.attachments.read(
attachment.hash,
options.base64 ? "base64" : "text"
@@ -292,7 +287,7 @@ export default async function downloadAttachment(
return fileUri;
} catch (e) {
console.log("download attachment error: ", e);
DatabaseLogger.error(e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)

View File

@@ -22,21 +22,24 @@ 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 { db } from "../database";
import { DatabaseLogger, db } from "../database";
import { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
export async function downloadFile(filename, data, cancelToken) {
if (!data) return false;
if (!data) {
DatabaseLogger.log(`Error downloading file: ${filename}, reason: No data`);
return false;
}
console.log("Downloading", filename);
DatabaseLogger.log(`Downloading ${filename}`);
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
console.log("Exists already", filename);
DatabaseLogger.log(`File Exists already: ${filename}`);
return true;
}
@@ -44,13 +47,24 @@ export async function downloadFile(filename, data, cancelToken) {
method: "GET",
headers
});
if (!res.ok)
if (!res.ok) {
DatabaseLogger.log(
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
);
throw new Error(`${res.status}: Unable to resolve download url`);
}
const downloadUrl = await res.text();
if (!downloadUrl) throw new Error("Unable to resolve download url");
if (!downloadUrl) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: Unable to resolve download url`
);
throw new Error("Unable to resolve download url");
}
let totalSize = 0;
console.log("Download starting");
DatabaseLogger.log(`Download starting: ${filename}`);
let request = RNFetchBlob.config({
path: path,
IOSBackgroundTask: true
@@ -61,13 +75,15 @@ export async function downloadFile(filename, data, cancelToken) {
.getState()
.setProgress(0, total, filename, recieved, "download");
totalSize = total;
console.log("downloading: ", recieved, total);
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
DatabaseLogger.log(`Download cancelled: ${filename}`);
};
let response = await request;
await fileCheck(response, totalSize);
let status = response.info().status;
@@ -91,7 +107,10 @@ export async function downloadFile(filename, data, cancelToken) {
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log("Download file error:", e, url, headers);
DatabaseLogger.error(e, {
url,
headers
});
return false;
}
}
@@ -100,18 +119,17 @@ 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) {
return 0;
DatabaseLogger.error(e);
return -1;
}
}
@@ -125,7 +143,9 @@ export async function checkAttachment(hash) {
try {
const size = await getUploadedFileSize(hash);
if (size <= 0) return { failed: "File length is 0." };
if (size === -1) return { success: true };
if (size === 0) return { failed: "File length is 0." };
} catch (e) {
return { failed: e?.message };
}

View File

@@ -21,12 +21,12 @@ import Sodium from "@ammarahmed/react-native-sodium";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { IOS_APPGROUPID } from "../../utils/constants";
import { db } from "../database";
import { DatabaseLogger, db } from "../database";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
console.log("Read encrypted file...");
DatabaseLogger.log("Read encrypted file...");
let path = `${cacheDir}/${filename}`;
try {
@@ -34,8 +34,6 @@ export async function readEncrypted(filename, key, cipherData) {
return false;
}
const attachment = await db.attachments.attachment(filename);
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{
@@ -45,12 +43,13 @@ export async function readEncrypted(filename, key, cipherData) {
},
cipherData.outputType === "base64" ? "base64" : "text"
);
console.log("file decrypted...", attachment?.mimeType);
DatabaseLogger.log("File decrypted...");
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log("readEncrypted", e);
DatabaseLogger.error(e);
return false;
}
}
@@ -128,7 +127,7 @@ export async function clearFileStorage() {
export async function createCacheDir() {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
console.log("Cache directory created");
DatabaseLogger.log("Cache directory created");
}
}
@@ -188,6 +187,9 @@ export async function exists(filename) {
);
if (stat.size !== expectedFileSize) {
DatabaseLogger.log(
`File size mismatch: ${filename}, expected: ${expectedFileSize}, actual: ${stat.size}`
);
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);

View File

@@ -25,6 +25,7 @@ import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
import { getUploadedFileSize } from "./download";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
@@ -33,6 +34,18 @@ export async function uploadFile(filename, data, cancelToken) {
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
const uploadedFileSize = await getUploadedFileSize(filename);
if (uploadedFileSize === -1) {
DatabaseLogger.log("Upload verification failed.");
return false;
}
const isUploaded = uploadedFileSize !== 0;
if (isUploaded) {
DatabaseLogger.log(`File ${filename} is already uploaded.`);
return true;
}
let res = await fetch(url, {
method: "PUT",
headers
@@ -50,7 +63,15 @@ export async function uploadFile(filename, data, cancelToken) {
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
if (!exists && Platform.OS === "ios") {
uploadFilePath = appGroupPath;
exists = await RNFetchBlob.fs.exists(uploadFilePath);
}
if (!exists) {
throw new Error(
`Trying to upload file at path ${uploadFilePath} that doest not exist.`
);
}
DatabaseLogger.info(`Starting upload: ${filename}`);
let request = RNFetchBlob.config({

View File

@@ -20,15 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Linking, View } from "react-native";
//import SettingsBackupAndRestore from '../../screens/settings/backup-restore';
import { eSendEvent, presentSheet } from "../../services/event-manager";
import Sync from "../../services/sync";
import { useThemeColors } from "@notesnook/theme";
import { eCloseAnnouncementDialog, eCloseSheet } from "../../utils/events";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { eCloseAnnouncementDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { PricingPlans } from "../premium/pricing-plans";
import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
import { allowedOnPlatform, getStyle } from "./functions";
@@ -56,13 +54,6 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
/>
)
});
} else if (item.type === "force-sync") {
eSendEvent(eCloseSheet);
await sleep(300);
Progress.present();
Sync.run("global", true, true, () => {
eSendEvent(eCloseSheet);
});
}
};
return (

View File

@@ -56,6 +56,7 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../screens/editor/tiptap/utils";
import SheetProvider from "../sheet-provider";
const Actions = ({
attachment,
@@ -218,10 +219,11 @@ const Actions = ({
}}
>
<Dialog context={contextId} />
<SheetProvider context={contextId} />
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.secondary.background,
borderBottomColor: colors.primary.border,
marginBottom: notes && notes.length > 0 ? 0 : 12
}}
>
@@ -238,36 +240,20 @@ const Actions = ({
style={{
flexDirection: "row",
marginBottom: 10,
paddingHorizontal: 12
paddingHorizontal: 12,
marginTop: 6,
gap: 10
}}
>
<Paragraph
size={SIZE.xs}
style={{
marginRight: 10
}}
color={colors.secondary.paragraph}
>
{attachment.type}
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{attachment.mimeType}
</Paragraph>
<Paragraph
style={{
marginRight: 10
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{formatBytes(attachment.size)}
</Paragraph>
{notes.length ? (
<Paragraph
style={{
marginRight: 10
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
<Paragraph size={SIZE.xs} color={colors.secondary.paragraph}>
{notes.length} note
{notes.length > 1 ? "s" : ""}
</Paragraph>
@@ -295,7 +281,7 @@ const Actions = ({
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.secondary.background,
borderBottomColor: colors.primary.border,
marginBottom: 12,
paddingVertical: 12
}}
@@ -366,7 +352,7 @@ const Actions = ({
{failed ? (
<Notice
type="alert"
text={`File check failed with error: ${attachment.failed} Try reuploading the file to fix the issue.`}
text={`File check failed: ${failed} Try reuploading the file to fix the issue.`}
size="small"
/>
) : null}

View File

@@ -177,7 +177,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
case "audio":
items = note
? db.attachments.ofNote(note.id, "audio")
: db.attachments.all;
: db.attachments.audios;
break;
case "documents":
items = note

View File

@@ -67,7 +67,7 @@ export const Login = ({ changeMode }) => {
Progress.present();
setTimeout(() => {
if (!useUserStore.getState().syncing) {
Sync.run("global", false, true);
Sync.run("global", false, "full");
}
}, 5000);
});
@@ -296,11 +296,6 @@ export const Login = ({ changeMode }) => {
</Paragraph>
</TouchableOpacity>
) : null}
<Notice
type="alert"
text="After logging in from V3 beta, your account data will be migrated and you won't be able to use V2 clients to sync your notes."
/>
</View>
</View>
</View>

View File

@@ -112,7 +112,7 @@ export const SessionExpired = () => {
if (!res) throw new Error("no token found");
if (db.tokenManager._isTokenExpired(res))
throw new Error("token expired");
Sync.run("global", false, true, async (complete) => {
Sync.run("global", false, "full", async (complete) => {
if (!complete) {
let user = await db.user.getUser();
if (!user) return;

View File

@@ -21,8 +21,10 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useKeyboard from "../../hooks/use-keyboard";
import { useThemeColors } from "@notesnook/theme";
export const Container = ({ children }: PropsWithChildren) => {
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const keyboard = useKeyboard();
const [height, setHeight] = useState(0);
@@ -43,7 +45,8 @@ export const Container = ({ children }: PropsWithChildren) => {
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
height: height || "100%",
width: "100%"
width: "100%",
backgroundColor: colors.primary.background
}}
onLayout={(event) => {
const height = event.nativeEvent.layout.height;

View File

@@ -45,8 +45,8 @@ const DialogButtons = ({
{
backgroundColor: colors.secondary.background,
height: 60,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 5,
borderBottomLeftRadius: 5,
paddingHorizontal: 12,
borderTopWidth: 0.7,
borderTopColor: getColorLinearShade(

View File

@@ -36,6 +36,7 @@ import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
import { useCallback } from "react";
import { Button } from "../ui/button";
import { getContainerBorder } from "../../utils/colors";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
@@ -121,7 +122,8 @@ export const Dialog = ({ context = "global" }) => {
maxHeight: 450,
borderRadius: 5,
backgroundColor: colors.primary.background,
paddingTop: 12
paddingTop: 12,
...getContainerBorder(colors.primary.border, 0.5)
};
return visible ? (

View File

@@ -24,7 +24,7 @@ import {
setAppLockVerificationCipher,
validateAppLockPassword
} from "../../../common/database/encryption";
import BiometicService from "../../../services/biometrics";
import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
@@ -323,7 +323,7 @@ export const AppLockPassword = () => {
SettingsService.setProperty("appLockHasPasswordSecurity", false);
if (
!(await BiometicService.isBiometryAvailable()) ||
!(await BiometricService.isBiometryAvailable()) ||
SettingsService.getProperty("biometricsAuthEnabled") === false
) {
SettingsService.setProperty("appLockEnabled", false);

View File

@@ -17,8 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
import { ImagePickerResponse } from "react-native-image-picker";
import {
Image,
Platform,
ScrollView,
TouchableOpacity,
View
} from "react-native";
import { Image as ImageType } from "react-native-image-crop-picker";
import { useThemeColors } from "../../../../../../packages/theme/dist";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
@@ -32,7 +38,7 @@ export default function AttachImage({
onAttach,
close
}: {
response: ImagePickerResponse;
response: ImageType[];
onAttach: ({ compress }: { compress: boolean }) => void;
close: ((ctx?: string | undefined) => void) | undefined;
}) {
@@ -58,14 +64,17 @@ export default function AttachImage({
}}
>
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
Attaching {response.assets?.length} image(s):
Attaching {response?.length} image(s):
</Paragraph>
<ScrollView horizontal>
{response.assets?.map((item) => (
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
{response?.map?.((item) => (
<TouchableOpacity key={item.filename} activeOpacity={0.9}>
<Image
source={{
uri: item.uri
uri:
Platform.OS === "ios"
? item.sourceURL || item.path
: item.path
}}
style={{
width: 100,
@@ -142,7 +151,7 @@ export default function AttachImage({
<Button
title={`${
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
(response?.length || 0) > 1 ? "Attach Images" : "Attach Image"
}`}
type="accent"
width="100%"
@@ -156,10 +165,16 @@ export default function AttachImage({
);
}
AttachImage.present = (response: ImagePickerResponse) => {
return new Promise((resolve) => {
AttachImage.present = (response: ImageType[], context?: string) => {
return new Promise<
| {
compress: boolean;
}
| undefined
>((resolve) => {
let resolved = false;
presentSheet({
context: context,
component: (ref, close, update) => (
<AttachImage
response={response}

View File

@@ -128,7 +128,7 @@ export class VaultDialog extends Component {
: this.state.copyNote
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.goToEditor
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
? "Unlock note to open it in editor."
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
}

View File

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

View File

@@ -71,7 +71,7 @@ export const RightMenus = ({
testID={notesnook.ids.default.addBtn}
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
iconSize={SIZE.xl}
type="shade"
type="accent"
hitSlop={{
top: 10,
right: 10,
@@ -82,7 +82,7 @@ export const RightMenus = ({
marginLeft: 10,
width: 32,
height: 32,
borderRadius: 5,
borderRadius: 100,
paddingHorizontal: 0,
borderWidth: 1,
borderColor: colors.primary.accent

View File

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

View File

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

View File

@@ -95,7 +95,7 @@ export const NotebookHeader = ({
style={{
flexShrink: 1
}}
size={SIZE.xxl}
size={SIZE.lg}
>
{notebook.title}
</Heading>
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
width: 40,
height: 40
}}
type={isPinnedToMenu ? "secondary" : "secondary"}
type="transparent"
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
@@ -123,7 +123,7 @@ export const NotebookHeader = ({
onPress={onEditNotebook}
tooltipText="Edit this notebook"
name="pencil"
type="secondary"
type="transparent"
color={colors.primary.icon}
style={{
width: 40,
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
</View>
{notebook.description ? (
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
{notebook.description}
</Paragraph>
) : null}

View File

@@ -99,7 +99,6 @@ export const SectionHeader = React.memo<
color={color || colors.primary.accent}
size={SIZE.sm}
style={{
minWidth: 60,
alignSelf: "center",
textAlignVertical: "center"
}}

View File

@@ -28,13 +28,12 @@ import {
hideSheet,
presentSheet
} from "../../../services/event-manager";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
export const openNote = async (
item: Note,
@@ -47,21 +46,8 @@ export const openNote = async (
if (!isTrash) {
note = (await db.notes.note(item.id)) as Note;
}
if (useSelectionStore.getState().selectionMode === item.type) {
const {
selectedItemsList,
selectionMode,
clearSelection,
setSelectedItem
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
} else {
clearSelection();
}
return;
}
if (selectItem(item)) return;
if (note.conflicted) {
eSendEvent(eShowMergeDialog, note);

View File

@@ -27,18 +27,12 @@ import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
const isTrash = item.type === "trash";
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (selectItem(item)) return;
if (isTrash) {
presentDialog({

View File

@@ -22,7 +22,6 @@ import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import ReminderSheet from "../../sheets/reminder";
@@ -30,7 +29,7 @@ import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
const ReminderItem = React.memo(
({
@@ -44,18 +43,7 @@ const ReminderItem = React.memo(
}) => {
const { colors } = useThemeColors();
const openReminder = () => {
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (selectItem(item)) return;
ReminderSheet.present(item, undefined, isSheet);
};

View File

@@ -26,6 +26,23 @@ import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
import { Item, TrashItem } from "@notesnook/core";
export function selectItem(item: Item) {
if (useSelectionStore.getState().selectionMode === item.type) {
const { selectionMode, clearSelection, setSelectedItem } =
useSelectionStore.getState();
if (selectionMode === item.type) {
setSelectedItem(item.id);
}
if (useSelectionStore.getState().selectedItemsList.length === 0) {
clearSelection();
}
return true;
}
return false;
}
type SelectionWrapperProps = PropsWithChildren<{
item: Item;
onPress: () => void;

View File

@@ -23,13 +23,12 @@ import React from "react";
import { View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
const TagItem = React.memo(
({
@@ -43,18 +42,7 @@ const TagItem = React.memo(
}) => {
const { colors } = useThemeColors();
const onPress = () => {
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (selectItem(item)) return;
TaggedNotes.navigate(item, true);
};

View File

@@ -80,7 +80,7 @@ export default function List(props: ListProps) {
const groupOptions = useGroupOptions(groupType);
const _onRefresh = async () => {
Sync.run("global", false, true, () => {
Sync.run("global", false, "full", () => {
props.onRefresh?.();
});
};
@@ -192,7 +192,9 @@ export default function List(props: ListProps) {
/>
) : null
}
ListFooterComponent={<Footer />}
ListFooterComponent={
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (

View File

@@ -48,6 +48,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import ReminderItem from "../list-items/reminder";
import TagItem from "../list-items/tag";
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
type ListItemWrapperProps<TItem = Item> = {
group?: GroupingKey;
@@ -75,6 +76,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
const refreshTimeout = useRef<NodeJS.Timeout>();
const currentItemId = useRef<string>();
const locked = useRef(false);
const compactMode = useIsCompactModeEnabled(props.dataType as ItemType);
const refreshItem = useCallback((resolvedItem: any) => {
if (!resolvedItem || !resolvedItem.data) {
@@ -117,14 +119,24 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
}
currentItemId.current = resolvedItem.item.id;
setItem(resolvedItem.item);
}
if (resolvedItem?.group) {
setGroupHeader(resolvedItem.group);
} else {
setGroupHeader(undefined);
}
}, []);
if (previousIndex.current !== index) {
previousIndex.current = index;
const resolvedItem = items?.cacheItem(index);
refreshItem(resolvedItem);
if (resolvedItem) {
refreshItem(resolvedItem);
} else {
setItem(undefined);
setGroupHeader(undefined);
}
}
useEffect(() => {
@@ -132,12 +144,15 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
try {
clearTimeout(refreshTimeout.current);
const idx = index;
refreshTimeout.current = setTimeout(async () => {
if (idx !== previousIndex.current) return;
const resolvedItem = await items?.item(idx, resolveItems);
if (idx !== previousIndex.current) return;
refreshItem(resolvedItem);
}, 100);
refreshTimeout.current = setTimeout(
async () => {
if (idx !== previousIndex.current) return;
const resolvedItem = await items?.item(idx, resolveItems);
if (idx !== previousIndex.current) return;
refreshItem(resolvedItem);
},
items?.cacheItem(index) ? 100 : 0
);
} catch (e) {
console.log("Error", e);
}
@@ -148,7 +163,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
return (
<View
style={{
height: 120,
height: compactMode ? 50 : 120,
width: "100%"
}}
/>

View File

@@ -313,7 +313,7 @@ const MergeConflicts = () => {
height: height / 2 - (50 + insets.top / 2),
backgroundColor: colors.primary.background,
borderBottomWidth: 1,
borderBottomColor: colors.secondary.background
borderBottomColor: colors.primary.border
}}
>
<ReadonlyEditor

View File

@@ -48,7 +48,7 @@ export const Synced = ({ item, close }) => {
paddingTop: 10,
marginTop: 10,
borderTopWidth: 1,
borderTopColor: colors.secondary.background
borderTopColor: colors.primary.border
}}
>
<Icon

View File

@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import {
BackHandler,
NativeEventSubscription,
Platform,
View
} from "react-native";
import Animated, { FadeInUp } from "react-native-reanimated";
import Menu from "react-native-reanimated-material-menu/src/Menu";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastManager } from "../../services/event-manager";
@@ -42,9 +42,10 @@ import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
import { MoveNotebookSheet } from "../sheets/move-notebook";
export const SelectionHeader = React.memo(
({
@@ -58,6 +59,8 @@ export const SelectionHeader = React.memo(
type?: ItemType;
renderedInRoute?: string;
}) => {
const menuRef = useRef<Menu>(null);
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const { colors } = useThemeColors();
const selectionMode = useSelectionStore((state) => state.selectionMode);
const selectedItemsList = useSelectionStore(
@@ -132,8 +135,7 @@ export const SelectionHeader = React.memo(
}, [clearSelection, selectionMode]);
return selectionMode !== type || focusedRouteId !== id ? null : (
<Animated.View
entering={FadeInUp}
<View
style={{
width: "100%",
height: Platform.OS === "android" ? 50 + insets.top : 50,
@@ -143,8 +145,7 @@ export const SelectionHeader = React.memo(
alignItems: "center",
flexDirection: "row",
zIndex: 999,
paddingHorizontal: 12,
marginVertical: 10
paddingHorizontal: 12
}}
>
<View
@@ -164,18 +165,15 @@ export const SelectionHeader = React.memo(
borderRadius: 100,
marginRight: 10
}}
type="secondary"
onPress={() => {
clearSelection();
}}
size={SIZE.xl}
color={colors.primary.icon}
name="close"
/>
<View
style={{
backgroundColor: colors.secondary.background,
height: 40,
borderRadius: 100,
paddingHorizontal: 16,
@@ -184,11 +182,12 @@ export const SelectionHeader = React.memo(
alignItems: "center"
}}
>
<Heading size={SIZE.md} color={colors.primary.accent}>
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
{selectedItemsList.length}
</Heading>
</View>
</View>
<View
style={{
flexDirection: "row",
@@ -211,161 +210,142 @@ export const SelectionHeader = React.memo(
allSelected ? colors.primary.accent : colors.primary.paragraph
}
name="select-all"
size={SIZE.xl}
/>
{renderedInRoute === "Notebooks" ? (
<IconButton
{selectedItemsList.length ? (
<Menu
ref={menuRef}
animationDuration={200}
style={{
marginLeft: 10
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -20
}}
onPress={async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
onRequestClose={() => {
menuRef.current?.hide();
}}
tooltipText="Move notebooks"
tooltipPosition={1}
name="arrow-right-bold-box-outline"
size={SIZE.xl}
/>
) : null}
{type !== "note" ? null : (
<>
<IconButton
onPress={async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
}}
style={{
marginLeft: 10
}}
color={colors.primary.icon}
tooltipText="Manage tags"
tooltipPosition={4}
name="pound"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
MoveNoteSheet.present();
}}
style={{
marginLeft: 10
}}
tooltipText="Add to notebooks"
tooltipPosition={4}
color={colors.primary.paragraph}
name="plus"
size={SIZE.xl}
/>
</>
)}
{renderedInRoute === "Notebook" ? (
<IconButton
onPress={async () => {
if (selectedItemsList.length > 0) {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
updateNotebook(id);
Navigation.queueRoutesForUpdate();
clearSelection();
anchor={
<IconButton
onPress={() => {
menuRef.current?.show();
}}
name="dots-vertical"
color={colors.primary.paragraph}
/>
}
>
{[
{
title:
selectedItemsList.length > 1
? "Move notebooks"
: "Move notebook",
onPress: async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
},
visible: renderedInRoute === "Notebooks",
icon: "arrow-right-bold-box-outline"
},
{
title: "Manage tags",
onPress: async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
},
visible: type === "note",
icon: "pound"
},
{
title: "Export",
onPress: async () => {
await sleep(100);
ExportNotesSheet.present(selectedItemsList);
},
visible: type === "note",
icon: "export"
},
{
title: "Link notebook",
onPress: async () => {
await sleep(100);
MoveNoteSheet.present();
},
visible: type === "note",
icon: "plus"
},
{
title: "Unlink notebook",
onPress: async () => {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
updateNotebook(id);
Navigation.queueRoutesForUpdate();
clearSelection();
},
visible: renderedInRoute === "Notebook",
icon: "minus"
},
{
title: "Unfavorite",
onPress: addToFavorite,
visible: focusedRouteId === "Favorites",
icon: "star-off"
},
{
title: `Move to trash`,
onPress: async () => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
},
visible: type !== "trash",
icon: "delete"
},
{
title: `Restore`,
onPress: restoreItem,
visible: type === "trash",
icon: "delete-restore"
},
{
title: `Delete`,
onPress: deleteItem,
visible: type === "trash",
icon: "delete"
}
}}
style={{
marginLeft: 10
}}
tooltipText={`Remove from Notebook`}
tooltipPosition={4}
testID="select-minus"
color={colors.primary.paragraph}
name="minus"
size={SIZE.xl}
/>
) : null}
{focusedRouteId === "Favorites" ? (
<IconButton
onPress={addToFavorite}
style={{
marginLeft: 10
}}
tooltipText="Remove from favorites"
tooltipPosition={4}
color={colors.primary.paragraph}
name="star-off"
size={SIZE.xl}
/>
) : null}
{type === "trash" ? null : (
<IconButton
style={{
marginLeft: 10
}}
onPress={() => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
}}
tooltipText="Move to trash"
tooltipPosition={1}
color={colors.primary.paragraph}
name="delete"
size={SIZE.xl}
/>
)}
{type === "trash" ? (
<>
<IconButton
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
onPress={restoreItem}
name="delete-restore"
tooltipText="Restore"
tooltipPosition={4}
size={SIZE.xl - 3}
/>
<IconButton
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
onPress={deleteItem}
tooltipText="Delete"
tooltipPosition={4}
name="delete"
size={SIZE.xl - 3}
/>
</>
].map((item) =>
!item.visible ? null : (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
}}
type="plain"
buttonType={{
text: contextMenuColors.primary.paragraph
}}
icon={item.icon}
key={item.title}
title={item.title}
onPress={async () => {
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();
}}
/>
)
)}
</Menu>
) : null}
</View>
</Animated.View>
</View>
);
}
);

View File

@@ -45,14 +45,16 @@ export const AddNotebookSheet = ({
notebook,
parentNotebook,
close,
showMoveNotesOnComplete
showMoveNotesOnComplete,
defaultTitle
}: {
notebook?: Notebook;
parentNotebook?: Notebook;
close?: (didAddNotebook: boolean) => void;
showMoveNotesOnComplete: boolean;
defaultTitle?: string;
}) => {
const title = useRef(notebook?.title);
const title = useRef(notebook?.title || defaultTitle);
const description = useRef(notebook?.description);
const titleInput = useRef<TextInput>(null);
const descriptionInput = useRef<TextInput>(null);
@@ -139,7 +141,7 @@ export const AddNotebookSheet = ({
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : ""}
defaultValue={notebook ? notebook.title : title.current}
/>
<Input
@@ -174,7 +176,8 @@ AddNotebookSheet.present = (
parentNotebook?: Notebook,
context?: string,
onClose?: (didAddNotebook: boolean) => void,
showMoveNotesOnComplete = true
showMoveNotesOnComplete = true,
defaultTitle?: string
) => {
presentSheet({
context: context,
@@ -187,6 +190,7 @@ AddNotebookSheet.present = (
onClose?.(didAddNotebook);
}}
showMoveNotesOnComplete={showMoveNotesOnComplete || false}
defaultTitle={defaultTitle}
/>
)
});

View File

@@ -19,7 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useCallback, useEffect } from "react";
import React, {
RefObject,
useCallback,
useEffect,
useRef,
useState
} from "react";
import {
ActivityIndicator,
Keyboard,
@@ -47,6 +53,8 @@ import Paragraph from "../../ui/typography/paragraph";
import { NotebookItem } from "./notebook-item";
import { useNotebookItemSelectionStore } from "./store";
import { AddNotebookSheet } from "../add-notebook";
import Input from "../../ui/input";
import { presentDialog } from "../../dialog/functions";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -93,7 +101,11 @@ const MoveNoteSheet = ({
actionSheetRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
const [notebooks, loading] = useNotebooks();
const [rootNotebooks, loading] = useNotebooks();
const searchQuery = useRef("");
const searchTimer = useRef<NodeJS.Timeout>();
const [notebooks, setNotebooks] = useState(rootNotebooks);
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
@@ -103,6 +115,12 @@ const MoveNoteSheet = ({
(state) => state.multiSelect
);
useEffect(() => {
if (!loading) {
setNotebooks(rootNotebooks);
}
}, [loading, rootNotebooks]);
useEffect(() => {
const items = note ? [note.id] : selectedItemsList;
updateInitialSelectionState(items);
@@ -199,7 +217,6 @@ const MoveNoteSheet = ({
{hasSelected() ? (
<IconButton
name="restore"
type="secondaryAccented"
color={colors.primary.icon}
onPress={() => {
const items = note ? [note.id] : selectedItemsList;
@@ -239,33 +256,48 @@ const MoveNoteSheet = ({
style={{
width: "100%"
}}
keyboardShouldPersistTaps="handled"
ListHeaderComponent={
<View
style={{
paddingHorizontal: 12,
width: "100%"
width: "100%",
paddingTop: 12
}}
>
<Button
title="Add new notebook"
style={{
alignSelf: "flex-start",
paddingHorizontal: 12,
justifyContent: "space-between"
<Input
placeholder="Search notebooks...."
button={{
icon: "plus",
onPress: () => {
AddNotebookSheet.present(
undefined,
undefined,
"link-notebooks",
undefined,
false,
searchQuery.current
);
},
color: colors.primary.icon
}}
onPress={() => {
AddNotebookSheet.present(
undefined,
undefined,
"link-notebooks",
undefined,
false
);
onChangeText={(value) => {
searchQuery.current = value;
if (!searchQuery.current) {
setNotebooks(rootNotebooks);
return;
}
searchTimer.current = setTimeout(() => {
db.lookup
.notebooks(searchQuery.current)
.sorted()
.then((result) => {
if (searchQuery.current === value) {
setNotebooks(result);
}
});
}, 300);
}}
icon="plus"
iconPosition="right"
type="secondaryAccented"
width="100%"
/>
</View>
}

View File

@@ -20,7 +20,7 @@ import { Note } from "@notesnook/core";
import { EVENTS } from "@notesnook/core/dist/common";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { Platform, View } from "react-native";
import { View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
@@ -43,6 +43,7 @@ type TabItem = {
locked?: boolean;
noteLocked?: boolean;
readonly?: boolean;
pinned?: boolean;
};
const TabItemComponent = (props: {
@@ -140,24 +141,50 @@ const TabItemComponent = (props: {
</Paragraph>
</View>
<IconButton
name="close"
size={SIZE.lg}
color={colors.primary.icon}
onPress={() => {
const isLastTab = useTabStore.getState().tabs.length === 1;
useTabStore.getState().removeTab(props.tab.id);
// The last tab is not actually removed, it is just cleaned up.
if (isLastTab) {
editorController.current?.reset(props.tab.id, true, true);
props.close?.();
}
<View
style={{
flexDirection: "row",
gap: 5,
paddingRight: 12
}}
top={0}
left={0}
right={20}
bottom={0}
/>
>
<IconButton
name="pin"
size={SIZE.lg}
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
onPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
pinned: !props.tab.pinned,
previewTab: false
});
}}
top={0}
left={0}
right={20}
bottom={0}
/>
{!props.tab?.pinned ? (
<IconButton
name="close"
size={SIZE.lg}
color={colors.primary.icon}
onPress={() => {
const isLastTab = useTabStore.getState().tabs.length === 1;
useTabStore.getState().removeTab(props.tab.id);
// The last tab is not actually removed, it is just cleaned up.
if (isLastTab) {
editorController.current?.reset(props.tab.id, true, true);
props.close?.();
}
}}
top={0}
left={0}
right={20}
bottom={0}
/>
) : null}
</View>
</Pressable>
);
};
@@ -225,7 +252,16 @@ export default function EditorTabs({
/>
</View>
<FlatList windowSize={3} data={tabs} renderItem={renderTabItem} />
<FlatList
windowSize={3}
data={tabs.sort((t1, t2) => {
if (t1.pinned && t2.pinned) return 0;
if (t1.pinned) return -1;
if (t2.pinned) return 1;
return 0;
})}
renderItem={renderTabItem}
/>
</View>
);
}

View File

@@ -19,7 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { Fragment, useState } from "react";
import { ActivityIndicator, Platform, StyleSheet, View } from "react-native";
import {
ActivityIndicator,
Linking,
Platform,
StyleSheet,
View
} from "react-native";
import FileViewer from "react-native-file-viewer";
import Share from "react-native-share";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
@@ -29,14 +35,12 @@ import { requestInAppReview } from "../../../services/app-review";
import {
PresentSheetOptions,
ToastManager,
eSendEvent,
presentSheet
} from "../../../services/event-manager";
import Exporter from "../../../services/exporter";
import PremiumService from "../../../services/premium";
import { useUserStore } from "../../../stores/use-user-store";
import { getElevationStyle } from "../../../utils/elevation";
import { eCloseSheet } from "../../../utils/events";
import { SIZE, ph, pv } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import { Dialog } from "../../dialog";
@@ -65,6 +69,7 @@ const ExportNotesSheet = ({
filePath: string;
name: string;
type: string;
fileDir: string;
}
| undefined
>();
@@ -295,7 +300,9 @@ const ExportNotesSheet = ({
successfully as {result?.fileName}
</Paragraph>
<Button
title="Open"
title={
Platform.OS === "android" ? "Open file location" : "Open"
}
type="accent"
width={250}
fontSize={SIZE.md}
@@ -305,20 +312,23 @@ const ExportNotesSheet = ({
}}
onPress={async () => {
if (!result?.filePath) return;
eSendEvent(eCloseSheet);
await sleep(500);
FileViewer.open(result?.filePath, {
showOpenWithDialog: true,
showAppsSuggestions: true
}).catch((e) => {
console.log(e);
ToastManager.show({
heading: "Cannot open",
message: `No application found to open ${result.name} file.`,
type: "success",
context: "local"
if (Platform.OS === "android") {
Linking.openURL(result.fileDir).catch((e) => {
ToastManager.error(e as Error);
});
});
} else {
FileViewer.open(result?.filePath, {
showOpenWithDialog: true,
showAppsSuggestions: true
}).catch((e) => {
ToastManager.show({
heading: "Cannot open",
message: `No application found to open ${result.name} file.`,
type: "success",
context: "local"
});
});
}
}}
/>
<Button
@@ -331,10 +341,10 @@ const ExportNotesSheet = ({
borderRadius: 100
}}
onPress={async () => {
if (!result?.filePath) return;
if (!result) return;
if (Platform.OS === "ios") {
Share.open({
url: result.filePath
url: result?.fileDir + result.fileName
}).catch(console.log);
} else {
FileViewer.open(result.filePath, {
@@ -347,7 +357,7 @@ const ExportNotesSheet = ({
/>
<Button
title="Export in another format"
type="secondaryAccented"
type="inverted"
width={250}
fontSize={SIZE.md}
style={{

View File

@@ -141,7 +141,7 @@ Logged in: ${user ? "yes" : "no"}`,
defaultValue={title.current}
style={{
borderWidth: 1,
borderColor: colors.secondary.background,
borderColor: colors.primary.border,
borderRadius: 5,
padding: 12,
fontFamily: "OpenSans-Regular",
@@ -178,7 +178,7 @@ For example:
}}
style={{
borderWidth: 1,
borderColor: colors.secondary.background,
borderColor: colors.primary.border,
borderRadius: 5,
padding: 12,
fontFamily: "OpenSans-Regular",

View File

@@ -35,7 +35,10 @@ import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
import { editorController } from "../../../screens/editor/tiptap/utils";
import {
EditorEvents,
editorController
} from "../../../screens/editor/tiptap/utils";
const ListNoteItem = ({
id,
@@ -190,13 +193,13 @@ export default function LinkNote(props: {
}
: undefined
);
editorController.current.commands.createInternalLink(
{
editorController.current?.postMessage(EditorEvents.resolve, {
data: {
href: link,
title: selectedNote.title
},
props.resolverId
);
resolverId: props.resolverId
});
};
const onSelectNote = async (note: Note) => {
@@ -219,7 +222,7 @@ export default function LinkNote(props: {
<View
style={{
paddingHorizontal: 12,
minHeight: 400,
minHeight: "100%",
maxHeight: "100%"
}}
>
@@ -311,6 +314,7 @@ export default function LinkNote(props: {
style={{
marginTop: 10
}}
keyboardShouldPersistTaps="handled"
windowSize={3}
keyExtractor={(item) => item.id}
data={nodes}
@@ -324,6 +328,7 @@ export default function LinkNote(props: {
onSelectNote={onSelectNote}
/>
)}
keyboardShouldPersistTaps="handled"
style={{
marginTop: 10
}}

View File

@@ -96,9 +96,12 @@ export default function Migrate() {
});
setLoading(true);
await sleep(1);
const { error } = await BackupService.run(false, "local");
const { error, report } = await BackupService.run(false, "local");
if (error) {
ToastManager.error(error, "Backup failed");
if (report) {
reportError(error);
}
setLoading(false);
return;
}
@@ -159,13 +162,14 @@ export default function Migrate() {
}}
>
Migrating database{progress ? `(${progress?.collection})` : null}
{progress ? `(${progress.current}/${progress.total}) ` : null}...
please wait
{progress ? `(${progress.current}/${progress.total}) ` : null}
... please wait
</Paragraph>
<View
style={{
width: 200
width: 200,
alignSelf: "center"
}}
>
<ProgressBarComponent

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import create from "zustand";
@@ -72,6 +72,20 @@ export const MoveNotebookSheet = ({
}) => {
const [notebooks] = useNotebooks();
const { colors } = useThemeColors();
const [moveToTop, setMoveToTop] = useState(false);
useEffect(() => {
(async () => {
for (const notebook of selectedNotebooks) {
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
setMoveToTop(true);
return;
}
}
})();
}, [selectedNotebooks]);
const renderItem = useCallback(
({ index }: { index: number }) => {
return (
@@ -93,6 +107,7 @@ export const MoveNotebookSheet = ({
: selectedNotebooks[0].title
} to ${selectedNotebook.title}?`,
positiveText: "Move",
context: "move-notebook",
positivePress: async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
@@ -157,36 +172,38 @@ export const MoveNotebookSheet = ({
paddingHorizontal: 12
}}
>
<Button
title="Move to top"
style={{
alignSelf: "flex-start",
width: "100%",
justifyContent: "space-between"
}}
icon="arrow-up-bold"
iconPosition="right"
type="secondaryAccented"
onPress={async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
await db.relations.unlink(
{
type: "notebook",
id: parent
},
notebook
);
eSendEvent(eOnNotebookUpdated, parent);
eSendEvent(eOnNotebookUpdated, notebook.id);
{moveToTop ? (
<Button
title="Move to top"
style={{
alignSelf: "flex-start",
width: "100%",
justifyContent: "space-between"
}}
icon="arrow-up-bold"
iconPosition="right"
type="secondaryAccented"
onPress={async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
await db.relations.unlink(
{
type: "notebook",
id: parent
},
notebook
);
eSendEvent(eOnNotebookUpdated, parent);
eSendEvent(eOnNotebookUpdated, notebook.id);
}
}
}
useNotebookStore.getState().refresh();
close?.();
}}
/>
useNotebookStore.getState().refresh();
close?.();
}}
/>
) : null}
</View>
}
ListEmptyComponent={

View File

@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core/dist/types";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
import React, { RefObject, useEffect, useRef, useState } from "react";
import { ActivityIndicator, TextInput, View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import { db } from "../../../common/database";
@@ -35,6 +35,7 @@ import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
@@ -50,39 +51,60 @@ export const MoveNotes = ({
}) => {
const { colors } = useThemeColors();
const currentNotebook = notebook;
const inputRef = useRef<TextInput>(null);
const [loading, setLoading] = useState(false);
const selectionCount = useItemSelectionStore(
(state) =>
Object.keys(state.selection).filter(
(k) =>
state.initialState?.[k] !== "selected" &&
state.selection[k] !== "deselected"
state.selection?.[k] === "selected" ||
state.selection[k] === "deselected"
)?.length > 0
);
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const loadNotes = React.useCallback(
async (query?: string) => {
setLoading(true);
const notes = query
? db.lookup.notes(query).sorted()
: db.notes?.all.sorted(db.settings.getGroupOptions("notes") as any);
notes
.then(async (notes) => {
await notes.item(0);
setNotes(notes);
setLoading(false);
})
.catch(() => {
setLoading(false);
});
db.relations
.from(currentNotebook, "note")
.get()
.then((existingNotes) => {
const selection: { [name: string]: any } = {};
existingNotes.forEach((rel) => {
selection[rel.toId] = "selected";
});
useItemSelectionStore.setState({
selection: selection,
initialState: selection
});
});
},
[currentNotebook]
);
useEffect(() => {
db.notes?.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
setNotes(notes);
});
db.relations
.from(currentNotebook, "note")
.get()
.then((existingNotes) => {
const selection: { [name: string]: any } = {};
existingNotes.forEach((rel) => {
selection[rel.toId] = "selected";
});
useItemSelectionStore.setState({
selection: selection,
initialState: selection
});
});
loadNotes();
return () => {
useItemSelectionStore.getState().reset();
};
}, [currentNotebook]);
}, [currentNotebook, loadNotes]);
const renderItem = React.useCallback(
({ index }: { item: boolean; index: number }) => {
@@ -96,9 +118,7 @@ export const MoveNotes = ({
style={{
paddingHorizontal: 12,
maxHeight: "100%",
height: notes?.placeholders.length
? 50 * (notes?.placeholders.length + 2)
: 300
height: "100%"
}}
>
<Dialog context="local" />
@@ -109,6 +129,22 @@ export const MoveNotes = ({
/>
<Seperator />
<Input
button={{
icon: "magnify",
color: colors.primary.accent,
size: SIZE.lg,
onPress: () => {}
}}
testID="search-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
loadNotes(v && v.trim() === "" ? undefined : v.trim());
}}
placeholder="Search a note"
/>
<FlashList
ListEmptyComponent={
<View
@@ -118,13 +154,17 @@ export const MoveNotes = ({
alignItems: "center"
}}
>
<Paragraph color={colors.secondary.paragraph}>
No notes to show
</Paragraph>
{loading ? (
<ActivityIndicator size="large" color={colors.primary.accent} />
) : (
<Paragraph color={colors.secondary.paragraph}>
No notes to show
</Paragraph>
)}
</View>
}
estimatedItemSize={50}
data={notes?.placeholders}
data={loading ? [] : notes?.placeholders}
renderItem={renderItem}
/>
@@ -135,6 +175,12 @@ export const MoveNotes = ({
currentNotebook.id,
...useItemSelectionStore.getState().getSelectedItemIds()
);
await db.notes?.removeFromNotebook(
currentNotebook.id,
...useItemSelectionStore.getState().getDeselectedItemIds()
);
updateNotebook(currentNotebook.id);
Navigation.queueRoutesForUpdate();
fwdRef?.current?.hide();
@@ -161,11 +207,12 @@ const SelectableNoteItem = React.memo(
const selected = useItemSelectionStore((state) =>
item?.id ? state.selection[item.id] === "selected" : false
);
const exists = useItemSelectionStore((state) =>
item?.id ? state.initialState[item.id] === "selected" : false
);
return exists ? null : (
return (
<Pressable
testID="listitem.select"
onPress={() => {
@@ -205,8 +252,14 @@ const SelectableNoteItem = React.memo(
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
type="selected"
color={selected ? colors.selected.icon : colors.primary.icon}
type="plain"
color={
selected
? colors.selected.icon
: exists && !selected
? colors.static.red
: colors.primary.icon
}
/>
<View

View File

@@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { Platform, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { getVersion } from "react-native-device-info";
import { features } from "../../../features";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { useThemeColors } from "@notesnook/theme";
import { eCloseSheet } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
@@ -36,7 +37,13 @@ export type FeatureType = {
platform?: "ios" | "android";
};
const NewFeature = ({ features }: { features: FeatureType[] }) => {
const NewFeature = ({
features,
version
}: {
features: FeatureType[];
version?: string | null;
}) => {
const { colors } = useThemeColors();
return (
@@ -44,30 +51,33 @@ const NewFeature = ({ features }: { features: FeatureType[] }) => {
style={{
alignItems: "center",
paddingHorizontal: 12,
paddingTop: 12
paddingTop: 12,
maxHeight: "100%"
}}
>
<Heading color={colors.secondary.heading} size={SIZE.md}>
New Version Highlights 🎉
{!version ? "New version " : `v${version} `}Highlights 🎉
</Heading>
<Seperator />
{features.map((item) => (
<View
key={item.title}
style={{
backgroundColor: colors.secondary.background,
padding: 12,
borderRadius: 10,
width: "100%",
marginBottom: 10
}}
>
<Heading size={SIZE.lg - 2}>{item.title}</Heading>
<Paragraph>{item.body}</Paragraph>
</View>
))}
<ScrollView>
{features.map((item) => (
<View
key={item.title}
style={{
backgroundColor: colors.secondary.background,
padding: 12,
borderRadius: 10,
width: "100%",
marginBottom: 10
}}
>
<Heading size={SIZE.lg - 2}>{item.title}</Heading>
<Paragraph selectable>{item.body}</Paragraph>
</View>
))}
</ScrollView>
<Seperator />
<Button
@@ -108,7 +118,13 @@ NewFeature.present = () => {
);
if (_features.length === 0) return;
presentSheet({
component: <NewFeature features={features} />
component: (
<NewFeature
features={features}
version={SettingsService.getProperty("version")}
/>
),
disableClosing: true
});
return true;
};

View File

@@ -19,13 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { RefreshControl, View, useWindowDimensions } from "react-native";
import {
Platform,
RefreshControl,
View,
useWindowDimensions
} from "react-native";
import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Config from "react-native-config";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import create from "zustand";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { MMKV } from "../../../common/database/mmkv";
import { useNotebook } from "../../../hooks/use-notebook";
import NotebookScreen from "../../../screens/notebook";
@@ -43,9 +49,8 @@ import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import Sort from "../sort";
import { MoveNotebookSheet } from "../move-notebook";
import { db } from "../../../common/database";
import Sort from "../sort";
const useItemSelectionStore = createItemSelectionStore(true, false);
@@ -63,7 +68,8 @@ class NotebookSheetConfig {
}
static get(item: ConfigItem) {
return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0;
const value = MMKV.getInt(NotebookSheetConfig.makeId(item));
return typeof value === "number" ? value : 0;
}
static set(item: ConfigItem, index = 0) {
@@ -126,11 +132,6 @@ export const NotebookSheet = () => {
if (!focusedRouteId) return;
const nextRoot = await findRootNotebookId(focusedRouteId);
if (nextRoot !== currentItem.current) {
console.log(
"NotebookSheet.useEffect.canShow",
"Root changed to",
nextRoot
);
useItemSelectionStore.setState({
enabled: false,
selection: {}
@@ -139,17 +140,14 @@ export const NotebookSheet = () => {
currentItem.current = nextRoot;
const snapPoint = NotebookSheetConfig.get({
type: "notebook",
id: nextRoot as string
id: focusedRouteId as string
});
if (ref.current?.isOpen()) {
ref.current?.snapToIndex(snapPoint);
} else {
setTimeout(() => {
ref.current?.show(snapPoint);
}, 150);
ref.current?.show(snapPoint);
}
console.log("NotebookSheet.useEffect.didShow", focusedRouteId);
setRoot(nextRoot);
onRequestUpdate();
});
@@ -162,7 +160,7 @@ export const NotebookSheet = () => {
ref.current?.hide();
}
}
}, [canShow, onRequestUpdate, focusedRouteId]);
}, [canShow, focusedRouteId]);
return (
<ActionSheet
@@ -198,7 +196,11 @@ export const NotebookSheet = () => {
backgroundColor: colors.secondary.background
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
snapPoints={
Config.isTesting === "true"
? [100]
: [Platform.OS === "android" ? 15 : 10, 100]
}
initialSnapIndex={1}
backgroundInteractionEnabled
gestureEnabled
@@ -237,7 +239,7 @@ export const NotebookSheet = () => {
>
<Icon
name="notebook-plus"
color={colors.primary.accent}
color={colors.primary.icon}
size={SIZE.xxl}
/>
</View>

View File

@@ -159,7 +159,7 @@ export default function ReminderNotify({
? 160 * references?.placeholders?.length
: 500,
borderTopWidth: 1,
borderTopColor: colors.secondary.background,
borderTopColor: colors.primary.border,
marginTop: 5,
paddingTop: 5
}}

View File

@@ -18,7 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useRef, useState } from "react";
import { Platform, TextInput, View } from "react-native";
import {
Platform,
TextInput,
View,
ScrollView as RNScrollView
} from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
@@ -364,7 +369,7 @@ export default function ReminderSheet({
))}
</View>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
{recurringMode === RecurringModes.Daily ||
recurringMode === RecurringModes.Year
? null
@@ -425,7 +430,7 @@ export default function ReminderSheet({
}}
/>
))}
</ScrollView>
</RNScrollView>
</View>
) : null}

View File

@@ -48,6 +48,7 @@ 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);
@@ -246,7 +247,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
await db.backup.import(backup, password, key);
});
await db.initCollections();
initialize();
refreshAllStores();
ToastManager.show({
heading: "Backup restored successfully.",
@@ -285,9 +285,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
throw new Error("Backup file is invalid");
}
// if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
// throw new Error("Backup file is invalid");
// }
await db.transaction(async () => {
let password;
@@ -384,10 +384,20 @@ const RestoreDataComponent = ({ close, setRestoring, 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);
@@ -399,6 +409,11 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}
} catch (e) {
console.log("error", e.stack);
setTimeout(() => {
useUserStore.setState({
disableAppLockRequests: false
});
}, 1000);
setRestoring(false);
backupError(e);
}
@@ -415,7 +430,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
borderRadius: 0,
flexDirection: "row",
borderBottomWidth: 0.5,
borderBottomColor: colors.secondary.background
borderBottomColor: colors.primary.border
}}
>
<View

View File

@@ -50,7 +50,7 @@ const Sort = ({ type, screen }) => {
const setOrderBy = async () => {
let _groupOptions = {
...groupOptions,
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
@@ -85,22 +85,22 @@ const Sort = ({ type, screen }) => {
<Button
title={
groupOptions.sortDirection === "asc"
? groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
groupOptions?.sortDirection === "asc"
? groupOptions?.groupBy === "abc" ||
groupOptions?.sortBy === "title"
? "A - Z"
: groupOptions.sortBy === "dueDate"
: groupOptions?.sortBy === "dueDate"
? "Earliest first"
: "Old - New"
: groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
: groupOptions?.groupBy === "abc" ||
groupOptions?.sortBy === "title"
? "Z - A"
: groupOptions.sortBy === "dueDate"
: groupOptions?.sortBy === "dueDate"
? "Latest first"
: "New - Old"
}
icon={
groupOptions.sortDirection === "asc"
groupOptions?.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
@@ -127,14 +127,14 @@ const Sort = ({ type, screen }) => {
justifyContent: "flex-start",
flexWrap: "wrap",
borderBottomWidth: isTopicSheet ? 0 : 1,
borderBottomColor: colors.secondary.background,
borderBottomColor: colors.primary.border,
marginBottom: 12,
paddingHorizontal: 12,
paddingBottom: 12,
alignItems: "center"
}}
>
{groupOptions.groupBy === "abc" ? (
{groupOptions?.groupBy === "abc" ? (
<Button
type="secondary"
title="Title"
@@ -154,18 +154,18 @@ const Sort = ({ type, screen }) => {
item === "dateEdited") ? null : (
<Button
key={item}
type={groupOptions.sortBy === item ? "selected" : "plain"}
type={groupOptions?.sortBy === item ? "selected" : "plain"}
title={SORT[item]}
height={40}
iconPosition="left"
icon={groupOptions.sortBy === item ? "check" : null}
icon={groupOptions?.sortBy === item ? "check" : null}
style={{
marginRight: 10,
paddingHorizontal: 8
}}
buttonType={{
text:
groupOptions.sortBy === item
groupOptions?.sortBy === item
? colors.primary.accent
: colors.secondary.paragraph
}}
@@ -213,11 +213,11 @@ const Sort = ({ type, screen }) => {
key={item}
testID={"btn-" + item}
type={
groupOptions.groupBy === GROUP[item] ? "selected" : "plain"
groupOptions?.groupBy === GROUP[item] ? "selected" : "plain"
}
buttonType={{
text:
groupOptions.groupBy === GROUP[item]
groupOptions?.groupBy === GROUP[item]
? colors.primary.accent
: colors.secondary.paragraph
}}
@@ -229,13 +229,13 @@ const Sort = ({ type, screen }) => {
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "asc";
_groupOptions.sortDirection = "desc";
}
updateGroupOptions(_groupOptions);
}}
height={40}
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
icon={groupOptions?.groupBy === GROUP[item] ? "check" : null}
title={
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
}

View File

@@ -170,6 +170,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
lineHeight: 20,
marginTop: 10
}}
selectable
>
{version.body}
</Paragraph>
@@ -181,6 +182,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>

View File

@@ -21,15 +21,11 @@ import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import { View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
@@ -59,26 +55,6 @@ export const SideMenu = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const BottomItemsList = [
{
name: isDark ? "Day" : "Night",
icon: "theme-light-dark",
func: () => {
useThemeStore.getState().setColorScheme();
},
switch: true,
on: !!isDark,
close: false
},
{
name: "Settings",
icon: "cog-outline",
close: true,
func: () => {
Navigation.navigate("Settings");
}
}
];
const pro = {
name: "Notesnook Pro",
@@ -121,7 +97,7 @@ export const SideMenu = React.memo(
<PinnedSection />
</>
),
[]
[order, hiddensItems]
);
return !isAppLoading && introCompleted ? (
@@ -155,26 +131,6 @@ export const SideMenu = React.memo(
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
<MenuItem testID={pro.name} key={pro.name} item={pro} index={0} />
) : null}
{BottomItemsList.slice(DDS.isLargeTablet() ? 0 : 1, 3).map(
(item, index) => (
<MenuItem
testID={
item.name == "Night mode"
? notesnook.ids.menu.nightmode
: item.name
}
key={item.name}
item={item}
index={index}
rightBtn={
DDS.isLargeTablet() || item.name === "Notesnook Pro"
? undefined
: BottomItemsList[0]
}
/>
)
)}
</View>
{dragging ? (
@@ -206,14 +162,7 @@ export const SideMenu = React.memo(
/>
</View>
) : (
<View
style={{
width: "100%",
paddingHorizontal: 0
}}
>
<UserStatus />
</View>
<UserStatus />
)}
</View>
</View>

View File

@@ -25,17 +25,20 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useSyncProgress from "../../hooks/use-sync-progress";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { eOpenLoginDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph";
export const UserStatus = () => {
const { colors } = useThemeColors();
const { colors, isDark } = useThemeColors();
const user = useUserStore((state) => state.user);
const syncing = useUserStore((state) => state.syncing);
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
@@ -53,7 +56,8 @@ export const UserStatus = () => {
alignSelf: "center",
paddingBottom: Platform.OS === "ios" ? insets.bottom / 2 : null,
borderTopWidth: 1,
borderTopColor: colors.secondary.background
borderTopColor: colors.primary.border,
backgroundColor: colors.primary.background
}}
>
<View
@@ -64,13 +68,9 @@ export const UserStatus = () => {
}}
>
<Pressable
onPress={async () => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
onPress={() => {
Navigation.navigate("Settings");
tabBarRef.current.closeDrawer();
}}
type="plain"
style={{
@@ -78,7 +78,8 @@ export const UserStatus = () => {
justifyContent: "flex-start",
padding: 12,
borderRadius: 0,
alignItems: "center"
alignItems: "center",
gap: 10
}}
>
{userProfile?.profilePicture ? (
@@ -89,11 +90,19 @@ export const UserStatus = () => {
style={{
width: 35,
height: 35,
borderRadius: 100,
marginRight: 10
borderRadius: 100
}}
/>
) : null}
) : (
<Icon
name="cog-outline"
size={SIZE.lg - 2}
color={colors.secondary.icon}
style={{
paddingLeft: 8
}}
/>
)}
<View
style={{
@@ -102,22 +111,12 @@ export const UserStatus = () => {
}}
>
<Paragraph
style={{
flexWrap: "wrap"
}}
numberOfLines={1}
size={SIZE.sm}
color={colors.primary.heading}
>
{!user
? "Login to sync your notes."
: lastSyncStatus === SyncStatus.Failed
? "Sync failed, tap to retry"
: syncing
? `Syncing your notes${
progress ? ` (${progress.current})` : ""
}`
: !userProfile?.fullName
? "Tap to sync"
{!user || !userProfile?.fullName
? "Settings"
: userProfile.fullName}
</Paragraph>
@@ -132,16 +131,20 @@ export const UserStatus = () => {
"Not logged in"
) : lastSynced && lastSynced !== "Never" ? (
<>
{lastSyncStatus === SyncStatus.Failed
{syncing
? `Syncing ${progress ? `(${progress.current})` : ""}`
: lastSyncStatus === SyncStatus.Failed
? "Sync failed"
: "Synced"}{" "}
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
{!syncing ? (
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
) : null}
{isOffline ? " (offline)" : ""}
</>
) : (
@@ -149,7 +152,7 @@ export const UserStatus = () => {
)}{" "}
<Icon
name="checkbox-blank-circle"
size={11}
size={9}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
@@ -162,25 +165,80 @@ export const UserStatus = () => {
</Paragraph>
</View>
{user ? (
syncing ? (
<ActivityIndicator color={colors.primary.accent} size={SIZE.xl} />
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
name="sync"
size={SIZE.lg}
/>
)
) : null}
<View
style={{
flexDirection: "row",
gap: 0
}}
>
<IconButton
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
useThemeStore.getState().setColorScheme();
}}
name="theme-light-dark"
color={isDark ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
style={{
borderRadius: 100,
width: 40,
height: 40
}}
/>
<Pressable
style={{
borderRadius: 100,
width: 40,
height: 40
}}
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
}}
>
{user ? (
syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.xl}
/>
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.icon}
name="sync"
size={SIZE.lg}
/>
)
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
size={SIZE.lg}
name="login"
/>
)}
</Pressable>
</View>
</Pressable>
</View>
</View>

View File

@@ -59,7 +59,7 @@ export interface TabsRef {
lock: () => boolean;
openDrawer: (animated?: boolean) => void;
closeDrawer: (animated?: boolean) => void;
page: number;
page: () => number;
setScrollEnabled: () => true;
isDrawerOpen: () => boolean;
node: RefObject<Animated.View>;
@@ -121,7 +121,12 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
} else {
if (prevWidths.current?.sidebar !== widths.sidebar) {
translateX.value =
!appState || appState?.movedAway ? widths.sidebar : editorPosition;
!appState || (appState && appState?.movedAway)
? widths.sidebar
: editorPosition;
if (translateX.value === editorPosition) {
onChangeTab?.({ i: 2, from: 1 });
}
}
}
isLoaded.current = true;
@@ -234,7 +239,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
onDrawerStateChange(false);
isDrawerOpen.value = false;
},
page: currentTab.value,
page: () => currentTab.value,
setScrollEnabled: () => true,
node: node
}),

View File

@@ -28,6 +28,7 @@ import { Toast } from "../../toast";
import { useAppState } from "../../../hooks/use-app-state";
import SettingsService from "../../../services/settings";
import { useUserStore } from "../../../stores/use-user-store";
import { getContainerBorder } from "../../../utils/colors";
/**
*
@@ -73,9 +74,17 @@ const SheetWrapper = ({
borderTopLeftRadius: 15,
alignSelf: "center",
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0
borderBottomLeftRadius: 0,
...getContainerBorder(colors.primary.border, 0.5),
borderBottomWidth: 0
};
}, [colors.primary.background, largeTablet, smallTablet, width]);
}, [
colors.primary.background,
colors.primary.border,
largeTablet,
smallTablet,
width
]);
const _onOpen = () => {
if (lockEvents.current) return;

View File

@@ -85,8 +85,8 @@ import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { updateStatusBarColor } from "../utils/colors";
import { BETA } from "../utils/constants";
import {
eClearEditor,
eCloseSheet,
eEditorReset,
eLoginSessionExpired,
eOnLoadNote,
eOpenAnnouncementDialog,
@@ -201,11 +201,13 @@ const onRequestPartialSync = async (
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
);
if (full || force) {
await Sync.run("global", force, full, undefined, lastSyncTime);
} else {
await Sync.run("global", false, false, undefined, lastSyncTime);
}
await Sync.run(
"global",
force,
full ? "full" : "send",
undefined,
lastSyncTime
);
};
const onLogout = async (reason: string) => {
@@ -234,7 +236,7 @@ async function checkForShareExtensionLaunchedInBackground() {
if (notesAddedFromIntent || shareExtensionOpened) {
const id = useTabStore.getState().getCurrentNoteId();
const note = id && (await db.notes.note(id));
eSendEvent("webview_reset");
eSendEvent(eEditorReset);
if (note) setTimeout(() => eSendEvent("loadingNote", note), 1);
MMKV.removeItem("shareExtensionOpened");
}
@@ -531,13 +533,15 @@ export const useAppEvents = () => {
}
//@ts-ignore
globalThis["IS_SHARE_EXTENSION"] = false;
} else {
const id = useTabStore.getState().getCurrentNoteId();
const note = id ? await db.notes.note(id) : undefined;
const locked = note && (await db.vaults.itemExists(note));
if (locked && SettingsService.canLockAppInBackground()) {
eSendEvent(eClearEditor);
if (
SettingsService.getBackgroundEnterTime() + 60 * 1000 * 10 <
Date.now()
) {
// Reset the editor if the app has been in background for more than 10 minutes.
eSendEvent(eEditorReset);
}
} else {
await saveEditorState();
if (
SettingsService.canLockAppInBackground() &&
@@ -679,6 +683,9 @@ export const useAppEvents = () => {
if (db.isInitialized) {
Notifications.setupReminders(true);
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}
useSettingStore.getState().setAppLoading(false);
DatabaseLogger.info("Database initialized");
}

View File

@@ -71,12 +71,10 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (!isValidIdOrIndex(idOrIndex)) return;
console.log("useDBItem.onUpdateItem", idOrIndex, type);
if (items && typeof idOrIndex === "number") {
items.item(idOrIndex).then((item) => {
setItem(item.item);
itemIdRef.current = item.item.id;
itemIdRef.current = item.item?.id;
});
} else {
if (!(db as any)[type + "s"][type]) {

View File

@@ -36,7 +36,6 @@ export const useNotebook = (
const onRequestUpdate = React.useCallback(() => {
if (!item?.id) return;
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
const selector = db.relations.from(
{
@@ -59,7 +58,6 @@ export const useNotebook = (
useEffect(() => {
if (nestedNotebooks) {
console.log("useNotebook.useEffect.onRequestUpdate");
onRequestUpdate();
}
}, [item?.id, onRequestUpdate, nestedNotebooks]);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect } from "react";
import BiometicService from "../services/biometrics";
import BiometricService from "../services/biometrics";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { db } from "../common/database";
@@ -39,8 +39,8 @@ export const useVaultStatus = () => {
const checkVaultStatus = useCallback(() => {
db.vault?.exists().then(async (exists) => {
const available = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const available = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
if (
VaultStatusCache.exists === exists &&
VaultStatusCache.biometryEnrolled === fingerprint &&

View File

@@ -64,6 +64,8 @@ import { useSettingStore } from "../stores/use-setting-store";
import {
eClearEditor,
eCloseFullscreenEditor,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eUnlockNote
@@ -164,9 +166,7 @@ const _TabsHolder = () => {
_deviceMode === "smallTablet"
? dimensions.width -
valueLimiter(dimensions.width * 0.4, 300, 450)
: dimensions.width > 1100
? dimensions.width * 0.55
: dimensions.width * 0.5,
: dimensions.width * 0.48,
zIndex: null,
paddingHorizontal: 0
}
@@ -257,9 +257,7 @@ const _TabsHolder = () => {
position: "relative",
width:
current === "tablet"
? size.width > 1100
? size.width * 0.55
: size.width * 0.5
? size.width * 0.48
: current === "smallTablet"
? size.width - valueLimiter(size.width * 0.4, 300, 450)
: size.width,
@@ -371,15 +369,9 @@ const _TabsHolder = () => {
editor: dimensions.width - valueLimiter(dimensions.width * 0.4, 300, 450)
},
tablet: {
sidebar:
dimensions.width > 1100
? dimensions.width * 0.15
: dimensions.width * 0.2,
sidebar: dimensions.width * 0.22,
list: dimensions.width * 0.3,
editor:
dimensions.width > 1100
? dimensions.width * 0.55
: dimensions.width * 0.5
editor: dimensions.width * 0.48
}
};
@@ -515,11 +507,13 @@ const _TabsHolder = () => {
};
export const TabHolder = React.memo(_TabsHolder, () => true);
const onChangeTab = async (obj) => {
if (obj.i === 2) {
const onChangeTab = async (event) => {
if (event.i === 2) {
editorState().movedAway = false;
editorState().isFocused = true;
activateKeepAwake();
eSendEvent(eOnEnterEditor);
if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, {
newNote: true
@@ -529,17 +523,14 @@ const onChangeTab = async (obj) => {
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
) {
eSendEvent(eUnlockNote);
} else {
editorState().currentlyEditing = true;
eSendEvent(eOnLoadNote);
}
}
} else {
if (obj.from === 2) {
if (event.from === 2) {
deactivateKeepAwake();
editorState().movedAway = true;
editorState().isFocused = false;
eSendEvent(eClearEditor, "removeHandler");
eSendEvent(eOnExitEditor);
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {

View File

@@ -32,13 +32,14 @@ import WebView from "react-native-webview";
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import BiometicService from "../../services/biometrics";
import BiometricService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
eSubscribeEvent
} from "../../services/event-manager";
import {
eEditorReset,
eOnLoadNote,
eUnlockNote,
eUnlockWithBiometrics,
@@ -51,7 +52,13 @@ import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { syncTabs, useTabStore } from "./tiptap/use-tab-store";
import { editorController, editorState } from "./tiptap/utils";
import {
editorController,
editorState,
openInternalLink,
randId
} from "./tiptap/utils";
import { tabBarRef } from "../../utils/global-refs";
const style: ViewStyle = {
height: "100%",
@@ -61,7 +68,10 @@ const style: ViewStyle = {
backgroundColor: "transparent"
};
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
if (request.url.includes("https")) {
if (request.url.includes("nn://")) {
openInternalLink(request.url);
return false;
} else if (request.url.includes("https")) {
if (Platform.OS === "ios" && !request.isTopFrame) return true;
openLinkInBrowser(request.url);
return false;
@@ -95,24 +105,20 @@ const Editor = React.memo(
noToolbar,
noHeader
});
const renderKey = useRef(`editor-0` + editorId);
const renderKey = useRef(randId("editor-id") + editorId);
useImperativeHandle(ref, () => ({
get: () => editor
}));
useLockedNoteHandler();
const onError = useCallback(() => {
renderKey.current =
renderKey.current === `editor-0`
? `editor-1` + editorId
: `editor-0` + editorId;
renderKey.current = randId("editor-id") + editorId;
editor.state.current.ready = false;
editor.setLoading(true);
}, [editor, editorId]);
useEffect(() => {
const sub = [eSubscribeEvent("webview_reset", onError)];
const sub = [eSubscribeEvent(eEditorReset, onError)];
return () => {
sub.forEach((s) => s?.unsubscribe());
};
@@ -139,6 +145,7 @@ const Editor = React.memo(
nestedScrollEnabled
onError={onError}
injectedJavaScriptBeforeContentLoaded={`
globalThis.__DEV__ = ${__DEV__}
globalThis.readonly=${readonly};
globalThis.noToolbar=${noToolbar};
globalThis.noHeader=${noHeader};
@@ -201,8 +208,8 @@ const useLockedNoteHandler = () => {
useEffect(() => {
(async () => {
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
@@ -216,9 +223,9 @@ const useLockedNoteHandler = () => {
try {
if (!tabRef.current?.noteLocked || !tabRef.current) return;
console.log("Trying to unlock with biometrics...");
const credentials = await BiometicService.getCredentials(
const credentials = await BiometricService.getCredentials(
"Unlock note",
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
"Unlock note to open it in editor."
);
if (credentials && credentials?.password && tabRef.current.noteId) {
@@ -226,6 +233,7 @@ const useLockedNoteHandler = () => {
tabRef.current.noteId,
credentials?.password
);
eSendEvent(eOnLoadNote, {
item: note
});
@@ -262,7 +270,7 @@ const useLockedNoteHandler = () => {
try {
const unlocked = await db.vault.unlock(password);
if (!unlocked) throw new Error("Incorrect vault password");
await BiometicService.storeCredentials(password);
await BiometricService.storeCredentials(password);
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Biometric unlocking enabled!",
@@ -271,8 +279,8 @@ const useLockedNoteHandler = () => {
context: "global"
});
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
@@ -331,7 +339,7 @@ const useLockedNoteHandler = () => {
}),
eSubscribeEvent(eUnlockWithPassword, onSubmit)
];
if (tabRef.current?.locked) {
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
unlock();
}
return () => {

View File

@@ -48,5 +48,6 @@ export const EventTypes = {
unlockWithBiometrics: "editor-events:unlock-biometrics",
disableReadonlyMode: "editor-events:disable-readonly-mode",
readonlyEditorLoaded: "readonlyEditorLoaded",
error: "editorError"
error: "editorError",
dbLogger: "editor-events:dbLogger"
};

View File

@@ -21,12 +21,11 @@ import Sodium from "@ammarahmed/react-native-sodium";
import { isImage } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import {
ImagePickerResponse,
launchCamera,
launchImageLibrary
} from "react-native-image-picker";
import DocumentPicker, {
DocumentPickerOptions,
DocumentPickerResponse
} from "react-native-document-picker";
import { Image, openCamera, openPicker } from "react-native-image-crop-picker";
import { DatabaseLogger, db } from "../../../common/database";
import filesystem from "../../../common/filesystem";
import { compressToFile } from "../../../common/filesystem/compress";
@@ -42,8 +41,9 @@ 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) => {
const showEncryptionSheet = (file: DocumentPickerResponse) => {
presentSheet({
title: "Encrypting attachment",
paragraph: `Please wait while we encrypt ${file.name} file for upload`,
@@ -51,24 +51,25 @@ const showEncryptionSheet = (file) => {
});
};
const santizeUri = (uri) => {
const santizeUri = (uri: string) => {
uri = decodeURI(uri);
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
return uri;
};
/**
* @param {{
* noteId: string,
* tabId: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* }} fileOptions
*/
const file = async (fileOptions) => {
type PickerOptions = {
noteId?: string;
tabId?: number;
type: "image" | "camera" | "file";
reupload: boolean;
hash?: string;
context?: string;
outputType?: "base64" | "url" | "cache";
};
const file = async (fileOptions: PickerOptions) => {
try {
const options = {
const options: DocumentPickerOptions<"ios"> = {
mode: "import",
allowMultiSelection: false
};
@@ -87,11 +88,11 @@ const file = async (fileOptions) => {
file = file[0];
let uri = Platform.OS === "ios" ? file.fileCopyUri : file.uri;
let uri = Platform.OS === "ios" ? file.fileCopyUri || file.uri : file.uri;
if (file.size > FILE_SIZE_LIMIT) {
if ((file.size || 0) > FILE_SIZE_LIMIT) {
ToastManager.show({
title: "File too large",
heading: "File too large",
message: "The maximum allowed size per file is 500 MB",
type: "error"
});
@@ -108,29 +109,40 @@ const file = async (fileOptions) => {
return;
}
console.log("file uri: ", uri);
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
showEncryptionSheet(file);
const hash = await Sodium.hashFile({
uri: uri,
type: "url"
});
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
if (
!(await attachFile(
uri,
hash,
file.type || "application/octet-stream",
file.name,
fileOptions
))
)
return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
if (!fileOptions.tabId) return;
if (
useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId
fileOptions.tabId &&
fileOptions.noteId &&
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
fileOptions.noteId
) {
if (isImage(file.type)) {
if (isImage(file.type || "application/octet-stream")) {
editorController.current?.commands.insertImage(
{
hash: hash,
filename: file.name,
mime: file.type,
size: file.size,
dataurl: await db.attachments.read(hash, "base64"),
title: file.name
mime: file.type || "application/octet-stream",
size: file.size || 0,
dataurl: (await db.attachments.read(hash, "base64")) as string,
type: "image"
},
fileOptions.tabId
);
@@ -139,8 +151,9 @@ const file = async (fileOptions) => {
{
hash: hash,
filename: file.name,
mime: file.type,
size: file.size
mime: file.type || "application/octet-stream",
size: file.size || 0,
type: "file"
},
fileOptions.tabId
);
@@ -151,39 +164,72 @@ const file = async (fileOptions) => {
eSendEvent(eCloseSheet);
}, 1000);
} catch (e) {
eSendEvent(eCloseSheet);
ToastManager.show({
heading: e.message,
heading: (e as Error).message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
});
console.log("attachment error: ", e);
DatabaseLogger.error(e);
}
};
/**
* @param {{
* noteId: string,
* tabId: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* }} options
*/
const camera = async (options) => {
const camera = async (options: PickerOptions) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchCamera(
{
includeBase64: true,
mediaType: "photo"
},
(response) => handleImageResponse(response, options)
);
openCamera({
mediaType: "photo",
includeBase64: true,
cropping: false,
multiple: true,
maxFiles: 10,
writeTempFile: true
})
.then((response) => {
handleImageResponse(
Array.isArray(response) ? response : [response],
options
);
})
.catch((e) => {
console.log("camera error: ", e);
});
} catch (e) {
ToastManager.show({
heading: e.message,
heading: (e as Error).message,
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};
const gallery = async (options: PickerOptions) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
openPicker({
includeBase64: true,
mediaType: "photo",
maxFiles: 10,
cropping: false,
multiple: true
})
.then((response) =>
handleImageResponse(
Array.isArray(response) ? response : [response],
options
)
)
.catch((e) => {
console.log("gallery error: ", e);
});
} catch (e) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
ToastManager.show({
heading: (e as Error).message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
@@ -192,53 +238,9 @@ const camera = async (options) => {
}
};
const gallery = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchImageLibrary(
{
includeBase64: true,
mediaType: "photo",
selectionLimit: 10
},
(response) => handleImageResponse(response, options)
);
} catch (e) {
ToastManager.show({
heading: e.message,
message: "You need internet access to attach a file",
type: "error",
context: "global"
});
console.log("attachment error:", e);
}
};
/**
*
* @typedef {{
* noteId?: string,
* tabId?: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* context?: string
* }} ImagePickerOptions
*
* @param {{
* noteId?: string,
* tabId?: string,
* type: "image" | "camera" | "file"
* reupload: boolean
* hash?: string
* context?: string
* }} options
* @returns
*/
const pick = async (options) => {
const pick = async (options: PickerOptions) => {
if (!PremiumService.get()) {
let user = await db.user.getUser();
const user = await db.user.getUser();
if (editorState().isFocused) {
editorState().isFocused = true;
}
@@ -259,58 +261,55 @@ const pick = async (options) => {
file(options);
}
};
/**
*
* @param {ImagePickerResponse} response
* @param {ImagePickerOptions} options
* @returns
*/
const handleImageResponse = async (response, options) => {
if (
response.didCancel ||
response.errorMessage ||
!response.assets ||
response.assets?.length === 0
) {
return;
}
const result = await AttachImage.present(response);
const handleImageResponse = async (
response: Image[],
options: PickerOptions
) => {
console.log(response, "result-file-picker");
const result = await AttachImage.present(response, options.context);
if (!result) return;
const compress = result.compress;
for (let image of response.assets) {
const isPng = /(png)/g.test(image.type);
const isJpeg = /(jpeg|jpg)/g.test(image.type);
for (const image of response) {
const isPng = /(png)/g.test(image.mime);
const isJpeg = /(jpeg|jpg)/g.test(image.mime);
if (compress && (isPng || isJpeg)) {
image.uri = await compressToFile(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
image.path = await compressToFile(
Platform.OS === "ios" ? "file://" + image.path : image.path,
isPng ? "PNG" : "JPEG"
);
const stat = await RNFetchBlob.fs.stat(image.uri.replace("file://", ""));
image.fileSize = stat.size;
const stat = await RNFetchBlob.fs.stat(image.path.replace("file://", ""));
image.size = stat.size;
image.path =
Platform.OS === "ios" ? image.path.replace("file://", "") : image.path;
}
if (image.fileSize > IMAGE_SIZE_LIMIT) {
if (image.size > IMAGE_SIZE_LIMIT) {
ToastManager.show({
title: "File too large",
heading: "File too large",
message: "The maximum allowed size per image is 50 MB",
type: "error"
});
return;
}
let b64 = `data:${image.type};base64, ` + image.base64;
const uri = decodeURI(image.uri);
const b64 = `data:${image.mime};base64, ` + image.data;
const uri = decodeURI(image.path);
const hash = await Sodium.hashFile({
uri: uri,
type: "url"
});
let fileName = image.originalFileName || image.fileName;
const fileName = image.sourceURL
? basename(image.sourceURL)
: image.filename || "image";
console.log("attaching image...", fileName);
console.log("attaching file...");
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
if (!(await attachFile(uri, hash, image.mime, fileName, options))) return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
console.log("attaching image to note...");
@@ -322,11 +321,13 @@ const handleImageResponse = async (response, options) => {
editorController.current?.commands.insertImage(
{
hash: hash,
mime: image.type,
title: fileName,
mime: image.mime,
type: "image",
dataurl: b64,
size: image.fileSize,
filename: fileName
size: image.size,
filename: fileName as string,
width: image.width,
height: image.height
},
options.tabId
);
@@ -343,10 +344,16 @@ const handleImageResponse = async (response, options) => {
* @param {ImagePickerOptions} options
* @returns
*/
export async function attachFile(uri, hash, type, filename, options) {
export async function attachFile(
uri: string,
hash: string,
type: string,
filename: string,
options: PickerOptions
) {
try {
let exists = await db.attachments.exists(hash);
let encryptionInfo;
const exists = await db.attachments.exists(hash);
let encryptionInfo: any;
if (options?.hash && options.hash !== hash) {
ToastManager.show({
heading: "Please select the same file for reuploading",
@@ -358,26 +365,36 @@ export async function attachFile(uri, hash, type, filename, options) {
}
if (!options.reupload && exists) {
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
options.reupload = (await filesystem.getUploadedFileSize(hash)) === 0;
}
if (options.reupload) {
DatabaseLogger.log(`Deleting file before reupload. ${hash}`);
const deleted = await db.fs().deleteFile(hash, false);
if (!deleted)
throw new Error(`Failed to delete file before reupload. ${hash}`);
}
if (!exists || options?.reupload) {
let key = await db.attachments.generateKey();
const key = await db.attachments.generateKey();
encryptionInfo = await Sodium.encryptFile(key, {
uri: uri,
type: options.type || "url",
type: options.outputType || "url",
hash: hash
});
} as any);
encryptionInfo.mimeType = type;
encryptionInfo.filename = filename;
encryptionInfo.alg = "xcha-stream";
encryptionInfo.size = encryptionInfo.length;
encryptionInfo.key = key;
if (options?.reupload && exists) await db.attachments.reset(hash);
if (options?.reupload && exists) {
const attachment = await db.attachments.attachment(hash);
if (attachment) await db.attachments.reset(attachment?.id);
}
} else {
encryptionInfo = { hash: hash };
}
await db.attachments.add(encryptionInfo, options.noteId);
await db.attachments.add(encryptionInfo);
return true;
} catch (e) {
DatabaseLogger.error(e);

View File

@@ -37,6 +37,7 @@ export type EditorState = {
isAwaitingResult: boolean;
scrollPosition: number;
overlay?: boolean;
initialLoadCalled?: boolean;
};
export type Settings = {
@@ -74,6 +75,8 @@ export type EditorMessage<T> = {
type: string;
noteId: string;
tabId: number;
resolverId?: string;
hasTimeout?: boolean;
};
export type SavePayload = {
@@ -84,6 +87,7 @@ export type SavePayload = {
sessionHistoryId?: number;
ignoreEdit: boolean;
tabId: number;
pendingChanges?: boolean;
};
export type AppState = {

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-case-declarations */
/* eslint-disable @typescript-eslint/no-var-requires */
import { parseInternalLink } from "@notesnook/core";
import { ItemReference } from "@notesnook/core/dist/types";
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
@@ -27,7 +26,6 @@ import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef } from "react";
import {
BackHandler,
InteractionManager,
Keyboard,
KeyboardEventListener,
NativeEventSubscription,
@@ -37,6 +35,7 @@ import { WebViewMessageEvent } from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import EditorTabs from "../../../components/sheets/editor-tabs";
import { Issue } from "../../../components/sheets/github/issue";
import LinkNote from "../../../components/sheets/link-note";
import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list";
@@ -60,6 +59,8 @@ import {
eClearEditor,
eCloseFullscreenEditor,
eEditorTabFocused,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eOpenLoginDialog,
@@ -74,8 +75,7 @@ import { useDragState } from "../../settings/editor/state";
import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { EditorEvents, editorState } from "./utils";
import { Issue } from "../../../components/sheets/github/issue";
import { EditorEvents, editorState, openInternalLink } from "./utils";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -269,22 +269,20 @@ export const useEditorEvents = (
}, [editor, deviceMode, fullscreen]);
const onHardwareBackPress = useCallback(() => {
if (editorState().currentlyEditing) {
console.log(tabBarRef.current?.page());
if (tabBarRef.current?.page() === 2) {
onBackPress();
return true;
}
}, [onBackPress]);
const onLoadNote = useCallback(async () => {
console.log("Loading...");
InteractionManager.runAfterInteractions(() => {
if (!DDS.isTab) {
handleBack.current = BackHandler.addEventListener(
"hardwareBackPress",
onHardwareBackPress
);
}
});
const onEnterEditor = useCallback(async () => {
if (!DDS.isTab) {
handleBack.current = BackHandler.addEventListener(
"hardwareBackPress",
onHardwareBackPress
);
}
}, [onHardwareBackPress]);
const onClearEditorSessionRequest = useCallback(
@@ -331,24 +329,39 @@ export const useEditorEvents = (
}, [fullscreen, onHardwareBackPress]);
useEffect(() => {
eSubscribeEvent(eOnLoadNote + editor.editorId, onLoadNote);
const onExitEditor = () => {
if (handleBack.current) {
handleBack.current.remove();
}
};
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
eSubscribeEvent(eOnExitEditor, onExitEditor);
eSubscribeEvent(
eClearEditor + editor.editorId,
onClearEditorSessionRequest
);
return () => {
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
eUnSubscribeEvent(eOnLoadNote, onLoadNote);
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
eUnSubscribeEvent(eOnExitEditor, onExitEditor);
};
}, [editor.editorId, onClearEditorSessionRequest, onLoadNote]);
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
const onMessage = useCallback(
async (event: WebViewMessageEvent) => {
const data = event.nativeEvent.data;
const editorMessage = JSON.parse(data) as EditorMessage<any>;
if (editorMessage.hasTimeout && editorMessage.resolverId) {
editor.postMessage(EditorEvents.resolve, {
data: true,
resolverId: editorMessage.resolverId
});
}
if (editorMessage.type === EventTypes.load) {
console.log("Editor loaded");
DatabaseLogger.log("Editor is ready");
editor.onLoad();
return;
}
@@ -376,21 +389,36 @@ export const useEditorEvents = (
content: editorMessage.value.html as string,
noteId: noteId,
tabId: editorMessage.tabId,
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit,
pendingChanges: editorMessage.value?.pendingChanges
});
break;
case EventTypes.title:
DatabaseLogger.log("EventTypes.title");
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string,
title: editorMessage.value?.title as string,
noteId: noteId,
tabId: editorMessage.tabId,
ignoreEdit: false
ignoreEdit: false,
pendingChanges: editorMessage.value?.pendingChanges
});
break;
case EventTypes.logger:
logger.info("[WEBVIEW LOG]", editorMessage.value);
logger.info("[EDITOR LOG]", editorMessage.value);
break;
case EventTypes.dbLogger:
if (editorMessage.value.error) {
DatabaseLogger.error(
editorMessage.value.error,
editorMessage.value.error,
{
message: "[EDITOR_ERROR]" + editorMessage.value.message
}
);
} else {
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
}
break;
case EventTypes.contentchange:
editor.onContentChanged(editorMessage.noteId);
@@ -443,7 +471,7 @@ export const useEditorEvents = (
break;
case EventTypes.filepicker:
editorState().isAwaitingResult = true;
const { pick } = require("./picker.js").default;
const { pick } = require("./picker").default;
pick({
type: editorMessage.value,
noteId: noteId,
@@ -462,12 +490,10 @@ export const useEditorEvents = (
case EventTypes.getAttachmentData: {
const attachment = (editorMessage.value as any)
.attachment as Attachment;
?.attachment as Attachment;
console.log(
"Getting attachment data:",
attachment.hash,
attachment.type
DatabaseLogger.log(
`Getting attachment data: ${attachment?.hash} ${attachment?.type}`
);
downloadAttachment(attachment.hash, true, {
base64: attachment.type === "image",
@@ -480,17 +506,17 @@ export const useEditorEvents = (
console.log(
"Got attachment data:",
!!data,
(editorMessage.value as any).resolverId
editorMessage.resolverId
);
editor.postMessage(EditorEvents.attachmentData, {
resolverId: (editorMessage.value as any).resolverId,
editor.postMessage(EditorEvents.resolve, {
resolverId: editorMessage.resolverId,
data
});
})
.catch(() => {
console.log("Error downloading attachment data");
editor.postMessage(EditorEvents.attachmentData, {
resolverId: (editorMessage.value as any).resolverId,
.catch((e) => {
DatabaseLogger.error(e);
editor.postMessage(EditorEvents.resolve, {
resolverId: editorMessage.resolverId,
data: undefined
});
});
@@ -519,27 +545,7 @@ export const useEditorEvents = (
break;
case EventTypes.link:
if (editorMessage.value.startsWith("nn://")) {
const data = parseInternalLink(editorMessage.value);
if (!data?.id) break;
if (
data.id ===
useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {
if (!data.params?.blockId) return;
editor.commands.scrollIntoViewById(data.params.blockId);
}, 150);
}
return;
}
eSendEvent(eOnLoadNote, {
item: await db.notes.note(data?.id),
blockId: data.params?.blockId
});
openInternalLink(editorMessage.value);
console.log(
"Opening note from internal link:",
editorMessage.value
@@ -637,7 +643,7 @@ export const useEditorEvents = (
case EventTypes.createInternalLink: {
LinkNote.present(
editorMessage.value.attributes,
editorMessage.value.resolverId
editorMessage.resolverId as string
);
break;
}
@@ -662,10 +668,8 @@ export const useEditorEvents = (
.updateTab(useTabStore.getState().currentTab, {
readonly: false
});
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: "Readonly mode disabled.",
type: "success"
setTimeout(() => {
Navigation.queueRoutesForUpdate();
});
}
break;

View File

@@ -39,6 +39,7 @@ import { DatabaseLogger, db } from "../../../common/database";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
@@ -46,16 +47,20 @@ import {
import Navigation from "../../../services/navigation";
import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useTagStore } from "../../../stores/use-tag-store";
import {
eEditorReset,
eEditorTabFocused,
eOnLoadNote,
eShowMergeDialog,
eUpdateNoteInEditor
} from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { unlockVault } from "../../../utils/unlock-vault";
import { onNoteCreated } from "../../notes/common";
import Commands from "./commands";
import { EventTypes } from "./editor-events";
import { SessionHistory } from "./session-history";
import { EditorState, SavePayload } from "./types";
import { syncTabs, useTabStore } from "./use-tab-store";
@@ -68,6 +73,7 @@ import {
isEditorLoaded,
post
} from "./utils";
import { sleep } from "../../../utils/time";
type NoteWithContent = Note & {
content?: NoteContent<false>;
@@ -145,8 +151,9 @@ export const useEditor = (
useEffect(() => {
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
console.log("Editot tab focus changed", lastTabFocused.current, tabId);
if (lastTabFocused.current !== tabId) lock.current = false;
lastTabFocused.current = tabId as number;
console.log(tabId);
});
return () => {
event?.unsubscribe();
@@ -165,10 +172,11 @@ export const useEditor = (
useEffect(() => {
if (loading) {
overlay(true);
state.current.ready = false;
setLoading(false);
}
}, [loading]);
}, [loading, overlay]);
const withTimer = useCallback(
(id: string, fn: () => void, duration: number) => {
@@ -216,7 +224,8 @@ export const useEditor = (
type,
ignoreEdit,
sessionHistoryId: currentSessionHistoryId,
tabId
tabId,
pendingChanges
}: SavePayload) => {
if (currentNotes.current[id as string]?.readonly || readonly) return;
try {
@@ -277,12 +286,26 @@ export const useEditor = (
);
}, 50);
const saveTimer = setTimeout(() => {
DatabaseLogger.log(`Note save timeout: ${id}...`);
ToastManager.error(
new Error(
"Copy your changes and restart the app to avoid data loss. If the issue persists, please report to us at support@streetwriters.co."
),
"Saving note is taking too long",
"global",
15000
);
}, 30 * 1000);
if (!locked) {
DatabaseLogger.log(`Saving note: ${id}...`);
id = await db.notes?.add({ ...noteData });
saved = true;
DatabaseLogger.log(`Note saved: ${id}...`);
clearTimeout(saveTimer);
if (!note && id) {
editorSessionHistory.newSession(id);
if (id) {
@@ -316,9 +339,22 @@ export const useEditor = (
Notifications.pinNote(id as string);
}
} else {
if (!db.vault.unlocked) {
if (pendingChanges) await sleep(3000);
const unlocked = await unlockVault({
title: "Unlock vault to save note",
paragraph: `This note is locked, unlock to save ${
pendingChanges ? "some pending" : ""
} changes`,
context: "global"
});
if (!unlocked)
throw new Error("Could not save note, vault is locked");
}
noteData.contentId = note?.contentId;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await db.vault?.save(noteData as any);
clearTimeout(saveTimer);
}
if (id && useTabStore.getState().getTabForNote(id) === tabId) {
@@ -341,6 +377,16 @@ export const useEditor = (
}
}
if (
id &&
id === useTabStore.getState().getCurrentNoteId() &&
pendingChanges
) {
postMessage(EditorEvents.title, title || note?.title, tabId);
postMessage(EditorEvents.html, data, tabId);
currentNotes.current[id] = note;
}
saveCount.current++;
return id;
} catch (e) {
@@ -415,7 +461,11 @@ export const useEditor = (
commands.focus(tabId);
});
} else {
if (!event.item) return;
if (!event.item) {
overlay(false);
return;
}
console.log("LOADING NOTE", event.item.id);
const item = event.item;
const currentTab = useTabStore
@@ -443,7 +493,6 @@ export const useEditor = (
useTabStore.getState().focusTab(tabId);
setTimeout(() => {
if (blockIdRef.current) {
console.log("scrolling to block", blockIdRef.current);
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
@@ -502,6 +551,7 @@ export const useEditor = (
loadingState.current === currentContents.current[item.id]?.data
) {
// If note is already loading, return.
console.log("Note is already loading...");
return;
}
@@ -535,11 +585,8 @@ export const useEditor = (
10000
);
console.log("blockId", blockIdRef.current);
setTimeout(() => {
if (blockIdRef.current) {
console.log("scrolling to block", blockIdRef.current);
commands.scrollIntoViewById(blockIdRef.current);
blockIdRef.current = undefined;
}
@@ -573,124 +620,131 @@ export const useEditor = (
data: Note | ContentItem | TrashItem | DeletedItem,
isLocal?: boolean
) => {
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
if (!data) return;
try {
await (async () => {
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
if (!data) return;
if (isDeleted(data) || isTrashItem(data)) {
const tabId = useTabStore.getState().getTabForNote(data.id);
if (tabId !== undefined) {
console.log("Removing tab");
await commands.clearContent(tabId);
useTabStore.getState().removeTab(tabId);
}
return;
}
const noteId =
(data as ContentItem).type === "tiptap"
? (data as ContentItem).noteId
: data.id;
if (!useTabStore.getState().hasTabForNote(noteId)) return;
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
const tab = useTabStore.getState().getTab(tabId);
const note = data.type === "note" ? data : await db.notes?.note(noteId);
lock.current = true;
// Handle this case where note was locked on another device and synced.
const locked = await db.vaults.itemExists(
currentNotes.current[noteId] as ItemReference
);
if (note) {
if (!locked && tab?.noteLocked) {
// Note lock removed.
if (tab.locked) {
if (useTabStore.getState().currentTab === tabId) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
} else {
useTabStore.getState().updateTab(tabId, {
locked: false,
noteLocked: false
});
commands.setLoading(true, tabId);
if (isDeleted(data) || isTrashItem(data)) {
const tabId = useTabStore.getState().getTabForNote(data.id);
if (tabId !== undefined) {
console.log("Removing tab");
await commands.clearContent(tabId);
useTabStore.getState().removeTab(tabId);
}
}
} else if (!tab?.noteLocked && locked) {
// Note lock added.
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
});
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
commands.setLoading(true, tabId);
}
}
if (currentNotes.current[noteId]?.title !== note.title) {
postMessage(EditorEvents.title, note.title, tabId);
}
commands.setTags(note);
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
commands.setStatus(
getFormattedDate(note.dateEdited, "date-time"),
"Saved",
tabId as number
);
}
console.log("readonly state changed...", note.readonly);
useTabStore.getState().updateTab(tabId, {
readonly: note.readonly
});
}
if (data.type === "tiptap" && note && !isLocal) {
if (lastContentChangeTime.current[noteId] >= data.dateEdited) {
lock.current = false;
return;
}
if (locked && isEncryptedContent(data)) {
const decryptedContent = await db.vault?.decryptContent(data, noteId);
if (!decryptedContent) {
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
});
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
commands.setLoading(true, tabId);
}
} else {
await postMessage(
EditorEvents.updatehtml,
decryptedContent.data,
tabId
);
currentContents.current[note.id] = decryptedContent;
}
} else {
const _nextContent = data.data;
if (_nextContent === currentContents.current?.data) {
lock.current = false;
return;
}
lastContentChangeTime.current[note.id] = note.dateEdited;
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
if (!isEncryptedContent(data)) {
currentContents.current[note.id] = data as UnencryptedContentItem;
const noteId =
(data as ContentItem).type === "tiptap"
? (data as ContentItem).noteId
: data.id;
if (!useTabStore.getState().hasTabForNote(noteId)) return;
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
const tab = useTabStore.getState().getTab(tabId);
const note =
data.type === "note" ? data : await db.notes?.note(noteId);
lock.current = true;
// Handle this case where note was locked on another device and synced.
const locked = await db.vaults.itemExists(
currentNotes.current[noteId] as ItemReference
);
if (note) {
if (!locked && tab?.noteLocked) {
// Note lock removed.
if (tab.locked) {
if (useTabStore.getState().currentTab === tabId) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
} else {
useTabStore.getState().updateTab(tabId, {
locked: false,
noteLocked: false
});
commands.setLoading(true, tabId);
}
}
} else if (!tab?.noteLocked && locked) {
// Note lock added.
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
});
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
commands.setLoading(true, tabId);
}
}
if (currentNotes.current[noteId]?.title !== note.title) {
postMessage(EditorEvents.title, note.title, tabId);
}
commands.setTags(note);
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
commands.setStatus(
getFormattedDate(note.dateEdited, "date-time"),
"Saved",
tabId as number
);
}
console.log("readonly state changed...", note.readonly);
useTabStore.getState().updateTab(tabId, {
readonly: note.readonly
});
}
}
if (data.type === "tiptap" && note && !isLocal) {
if (lastContentChangeTime.current[noteId] >= data.dateEdited) {
return;
}
if (locked && isEncryptedContent(data)) {
const decryptedContent = await db.vault?.decryptContent(data);
if (!decryptedContent) {
useTabStore.getState().updateTab(tabId, {
locked: true,
noteLocked: true
});
if (useTabStore.getState().currentTab !== tabId) {
commands.clearContent(tabId);
commands.setLoading(true, tabId);
}
} else {
await postMessage(
EditorEvents.updatehtml,
decryptedContent.data,
tabId
);
currentContents.current[note.id] = decryptedContent;
}
} else {
const _nextContent = data.data;
if (_nextContent === currentContents.current?.data) {
return;
}
lastContentChangeTime.current[note.id] = note.dateEdited;
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
if (!isEncryptedContent(data)) {
currentContents.current[note.id] =
data as UnencryptedContentItem;
}
}
}
})();
} catch (e) {
DatabaseLogger.error(e as Error, "Error when applying sync changes");
} finally {
lock.current = false;
}
lock.current = false;
},
[postMessage, commands]
);
@@ -713,7 +767,8 @@ export const useEditor = (
type,
ignoreEdit,
noteId,
tabId
tabId,
pendingChanges
}: {
noteId?: string;
title?: string;
@@ -721,18 +776,29 @@ export const useEditor = (
type: string;
ignoreEdit: boolean;
tabId: number;
pendingChanges?: boolean;
}) => {
DatabaseLogger.log(`Saving content...`);
DatabaseLogger.log(
`saveContent... title: ${!!title}, content: ${!!content}, noteId: ${noteId}`
);
if (
lock.current ||
(currentLoadingNoteId.current &&
currentLoadingNoteId.current === noteId)
) {
DatabaseLogger.log(`Skipped saving conent:
DatabaseLogger.log(`Skipped saving content:
lock.current: ${lock.current}
currentLoadingNoteId.current: ${currentLoadingNoteId.current}
`);
if (lock.current) {
setTimeout(() => {
if (lock.current) {
DatabaseLogger.warn("Editor force removed lock after 5 seconds");
lock.current = false;
}
}, 5000);
}
return;
}
@@ -740,7 +806,7 @@ export const useEditor = (
lastContentChangeTime.current[noteId] = Date.now();
}
if (type === EditorEvents.content && noteId) {
if (type === EventTypes.content && noteId) {
currentContents.current[noteId as string] = {
data: content,
type: "tiptap",
@@ -755,7 +821,8 @@ export const useEditor = (
id: noteId,
ignoreEdit,
sessionHistoryId: noteId ? editorSessionHistory.get(noteId) : undefined,
tabId: tabId
tabId: tabId,
pendingChanges
};
withTimer(
noteId || "newnote",
@@ -767,7 +834,16 @@ export const useEditor = (
onChange(params.data);
return;
}
saveNote(params);
if (useSettingStore.getState().isAppLoading) {
const sub = useSettingStore.subscribe((state) => {
if (!state.isAppLoading) {
saveNote(params);
sub();
}
});
} else {
saveNote(params);
}
},
ignoreEdit ? 0 : 150
);
@@ -810,7 +886,7 @@ export const useEditor = (
useTabStore.getState().currentTab
))
) {
eSendEvent("webview_reset", "onReady");
eSendEvent(eEditorReset, "onReady");
return false;
} else {
syncTabs();
@@ -820,7 +896,6 @@ export const useEditor = (
}, [isDefaultEditor, restoreEditorState]);
const onLoad = useCallback(async () => {
if (currentNotes.current) overlay(true);
setTimeout(() => {
postMessage(EditorEvents.theme, theme);
});
@@ -835,12 +910,22 @@ export const useEditor = (
const noteId = useTabStore.getState().getCurrentNoteId();
if (!noteId) {
overlay(false);
loadNote({ newNote: true });
if (tabBarRef.current?.page === 1) {
if (tabBarRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
} else if (state.current?.initialLoadCalled) {
const note = currentNotes.current[noteId];
if (note) {
loadNote({
item: note
});
}
}
if (!state.current?.initialLoadCalled) {
state.current.initialLoadCalled = true;
}
overlay(false);
}, [
postMessage,
theme,

View File

@@ -76,6 +76,7 @@ export type TabItem = {
readonly?: boolean;
locked?: boolean;
noteLocked?: boolean;
pinned?: boolean;
};
const history = new History();

View File

@@ -22,10 +22,15 @@ import { TextInput } from "react-native";
import WebView from "react-native-webview";
import { MMKV } from "../../../common/database/mmkv";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { AppState, EditorState, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { parseInternalLink } from "@notesnook/core";
import { eOnLoadNote } from "../../../utils/events";
import { db } from "../../../common/database";
export const textInput = createRef<TextInput>();
export const editorController =
createRef<useEditorType>() as MutableRefObject<useEditorType>;
@@ -41,7 +46,7 @@ export function editorState() {
return editorController.current?.state.current || defaultState;
}
export const EditorEvents: { [name: string]: string } = {
export const EditorEvents = {
html: "native:html",
updatehtml: "native:updatehtml",
title: "native:title",
@@ -50,7 +55,8 @@ export const EditorEvents: { [name: string]: string } = {
logger: "native:logger",
status: "native:status",
keyboardShown: "native:keyboardShown",
attachmentData: "native:attachment-data"
attachmentData: "native:attachment-data",
resolve: "native:resolve"
};
export function randId(prefix: string) {
@@ -176,3 +182,27 @@ export function clearAppState() {
appState = undefined;
MMKV.removeItem("appState");
}
export async function openInternalLink(url: string) {
const data = parseInternalLink(url);
if (!data?.id) return false;
if (
data.id ===
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {
if (!data.params?.blockId) return;
editorController.current.commands.scrollIntoViewById(
data.params.blockId
);
}, 150);
}
return;
}
eSendEvent(eOnLoadNote, {
item: await db.notes.note(data?.id),
blockId: data.params?.blockId
});
}

View File

@@ -29,10 +29,10 @@ import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotes } from "../../stores/use-notes-store";
import { openEditor } from "../notes/common";
import LinkNote from "../../components/sheets/link-note";
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
const [notes, loading] = useNotes();
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
@@ -80,7 +80,9 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
loading: "Loading your notes"
}}
/>
<FloatingButton title="Create a new note" onPress={openEditor} />
{!notes || !notes.placeholders?.length ? null : (
<FloatingButton title="Create a new note" onPress={openEditor} />
)}
</DelayLayout>
</>
);

View File

@@ -16,40 +16,37 @@ 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 { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core/dist/types";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import SelectionHeader from "../../components/selection-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { eSendEvent, eSubscribeEvent } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import useNavigationStore, {
NotebookScreenParams
} from "../../stores/use-navigation-store";
import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { openEditor, setOnFirstSave } from "../notes/common";
import SelectionHeader from "../../components/selection-header";
import Paragraph from "../../components/ui/typography/paragraph";
import { View } from "react-native";
import { SIZE } from "../../utils/size";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import { resolveItems } from "@notesnook/common";
import { openEditor, setOnFirstSave } from "../notes/common";
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const params = useRef<NotebookScreenParams>(route?.params);
const [loading, setLoading] = useState(true);
const updateOnFocus = useRef(false);
const [breadcrumbs, setBreadcrumbs] = useState<
{
id: string;
@@ -59,11 +56,17 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
useNavigationFocus(navigation, {
onFocus: () => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
if (updateOnFocus.current) {
onRequestUpdate();
updateOnFocus.current = false;
} else {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
}
syncWithNavigation();
return false;
},
onBlur: () => {
updateOnFocus.current = false;
setOnFirstSave(null);
return false;
}
@@ -79,6 +82,15 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const onRequestUpdate = React.useCallback(
async (data?: NotebookScreenParams) => {
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
!data
) {
updateOnFocus.current = true;
return;
}
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
const currentNotebookRoot = await findRootNotebookId(
@@ -104,7 +116,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
if (notebook) {
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
setBreadcrumbs(breadcrumbs);
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
params.current.item = notebook;
const notes = await db.relations
.from(notebook, "note")
@@ -122,10 +134,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
);
useEffect(() => {
onRequestUpdate();
eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
onRequestUpdate(params.current);
const sub = eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
return () => {
eUnSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
sub?.unsubscribe();
};
}, [onRequestUpdate]);
@@ -167,7 +179,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
onPressDefaultRightButton={openEditor}
/>
{breadcrumbs ? (
{breadcrumbs && breadcrumbs.length > 0 ? (
<View
style={{
width: "100%",

View File

@@ -81,16 +81,21 @@ const NotesPage = ({
route.name === "ColoredNotes"
? (params.current?.item as Color)?.colorCode
: undefined;
const updateOnFocus = useRef(false);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
if (updateOnFocus.current) {
onRequestUpdate();
updateOnFocus.current = false;
} else {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
}
syncWithNavigation();
if (focusControl) return !prev.current;
return false;
},
onBlur: () => {
updateOnFocus.current = false;
setOnFirstSave(null);
return false;
},
@@ -99,7 +104,6 @@ const NotesPage = ({
const syncWithNavigation = React.useCallback(() => {
const { item } = params.current;
useNavigationStore
.getState()
.setFocusedRouteId(params?.current?.item?.id || route.name);
@@ -113,6 +117,14 @@ const NotesPage = ({
const onRequestUpdate = React.useCallback(
async (data?: NotesScreenParams) => {
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
!data
) {
updateOnFocus.current = false;
return;
}
const isNew = data && data?.item?.id !== params.current?.item?.id;
if (data) params.current = data;

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