Compare commits

...

271 Commits

Author SHA1 Message Date
Ammar Ahmed
977d6f9246 mobile: fix selection state causing delete to fail 2023-10-30 13:22:03 +05:00
Ammar Ahmed
d412679fd7 mobile: fix relogin in session expired mode 2023-10-30 11:56:52 +05:00
Ammar Ahmed
22c52d4432 core: reset lastSynced if new login 2023-10-30 11:55:43 +05:00
Ammar Ahmed
9e5bdf9e33 core: fix access token not saved 2023-10-30 11:55:14 +05:00
Abdullah Atta
7253eca5c0 core: do not allow saving empty token response 2023-10-30 10:55:39 +05:00
Abdullah Atta
b1f3dc29c5 core: use single sse command for logging the user out 2023-10-30 10:55:39 +05:00
Abdullah Atta
8e3df8d41b editor: disallow links in inline code 2023-10-30 10:41:04 +05:00
Ammar Ahmed
efad22c00a mobile: fix 24 hour reminder time 2023-10-30 10:26:15 +05:00
Ammar Ahmed
b7690f13b1 mobile: hide pinned notification subtitle 2023-10-30 09:58:52 +05:00
Ammar Ahmed
6e1739da2e mobile: fix note not added to default notebook in widget 2023-10-24 17:35:53 +05:00
Ammar Ahmed
77ab423041 mobile: fix keep in view in editor (#3612) 2023-10-24 16:54:00 +05:00
Ammar Ahmed
96a8e885d3 Fix session expiry dialog not presented when user encryption key is not found (#3625)
* core: fix session expired dialog not shown when
user encryption key does not
exist

* core: add watch command
2023-10-24 15:58:26 +05:00
Ammar Ahmed
2b917c7eca mobile: fix pin input hide behind keyboard on session expiry sheet (#3624) 2023-10-24 15:57:20 +05:00
Ammar Ahmed
942d2f7a4e mobile: fix system bar colors (#3611) 2023-10-24 12:44:41 +05:00
Ammar Ahmed
75142ff1b2 mobile: fix session id mismatch causing editor to get stuck.(#3613) 2023-10-24 09:18:55 +05:00
Abdullah Atta
74172dc3fa docs: update organizing notes using notebooks doc 2023-10-03 14:42:54 +05:00
Abdullah Atta
848fdda8d7 docs: update outdated documentation 2023-10-03 13:44:21 +05:00
Abdullah Atta
3895569a0c setup: make flatpak source generation more robust 2023-10-03 12:02:27 +05:00
Abdullah Atta
0aac29ca76 web: bump version to 2.6.7 2023-10-02 21:06:08 +05:00
Abdullah Atta
25f9eece8d web: remove blur from dialogs 2023-10-02 21:06:08 +05:00
Ammar Ahmed
0e22b8ab2c mobile: release v2.6.7 2023-10-02 19:01:16 +05:00
Abdullah Atta
577c2ce6cd core: auto create vault if non exists & user opens a locked note 2023-10-02 18:00:36 +05:00
Abdullah Atta
5b9cb8ca4b desktop: fix close to system tray only works once 2023-10-02 17:56:41 +05:00
Ammar Ahmed
2eda93c0c2 mobile: fix add to notebook sheet hangs with many notebooks (#3505)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-10-02 17:06:28 +05:00
Ammar Ahmed
d916adf2e3 mobile: fix dialog show above app lock 2023-10-02 17:05:41 +05:00
Abdullah Atta
1f065bcbb2 web: do not show save dialog when Ctrl+S is pressed 2023-10-02 16:37:09 +05:00
Abdullah Atta
3efdd9505e editor: do not open keyboard on task item drop 2023-10-02 16:22:20 +05:00
Abdullah Atta
ab3f5f6a6c editor: hide scrollbar in math nodes 2023-10-02 16:17:39 +05:00
Muhammad Ali
9027be7bbb web: add menu item to allow copying a note as text or markdown (#3193)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-10-02 16:09:35 +05:00
Abdullah Atta
7b08b5c035 core: fix vault key not getting synced 2023-10-02 16:07:52 +05:00
Abdullah Atta
4a557c67b7 core: fix require prismjs on desktop build 2023-10-02 15:28:47 +05:00
Abdullah Atta
a6ebe092e8 core: fix undefined is not an object when publishing empty note 2023-10-02 15:15:21 +05:00
Midhun Sudhir
9759e1b396 mobile: fix typo in pricing plans (#3477)
Signed-off-by: Midhun Sudhir <60651970+midhun3301@users.noreply.github.com>
2023-09-28 23:19:15 +05:00
Ammar Ahmed
b6147ca5a1 mobile: release v2.6.6 2023-09-25 13:26:40 +05:00
Ammar Ahmed
83887db77a mobile: pass e2e tests 2023-09-25 13:26:40 +05:00
Abdullah Atta
1facfbec0e web: bump version to 2.6.6 2023-09-25 12:38:49 +05:00
Abdullah Atta
d6608325b4 web: skip education plan in tests 2023-09-25 12:27:09 +05:00
Ammar Ahmed
439b090470 mobile: fix learn more button is empty 2023-09-25 12:23:37 +05:00
Ammar Ahmed
ed98319a21 mobile: fix topic sheet size 2023-09-25 12:23:37 +05:00
Ammar Ahmed
54f94f44c0 mobile: remove icon animation 2023-09-25 12:23:37 +05:00
Ammar Ahmed
57cf0e23cf core: fix sorting of debug logs 2023-09-25 12:23:37 +05:00
Abdullah Atta
9e66269aca core: fix core tests 2023-09-25 12:20:54 +05:00
Abdullah Atta
a4f50fa2c3 web: fix backup tests 2023-09-25 11:47:29 +05:00
Abdullah Atta
7a0bafb02c editor: improve drag/drop support in task lists 2023-09-25 11:06:05 +05:00
Abdullah Atta
a24658ac0e core: fix "Cannot read property type of undefined" 2023-09-25 10:43:54 +05:00
Abdullah Atta
bb77385b66 editor: update tiptap to 2.1.11 2023-09-25 10:36:36 +05:00
Ammar Ahmed
e8b1bb649c editor: focus editor on press enter on mobile 2023-09-22 17:32:01 +05:00
Ammar Ahmed
023347da3f editor: fix link color in tool 2023-09-22 17:31:36 +05:00
Ammar Ahmed
f10146be03 mobile: fix image preview not loading image 2023-09-22 17:17:25 +05:00
Ammar Ahmed
7bac3c1326 mobile: fix selection is not reset when switching topics 2023-09-22 16:35:40 +05:00
Ammar Ahmed
528c0a10de mobile: skip clipping images from webpage 2023-09-22 10:32:52 +05:00
Ammar Ahmed
c1e1a8d1a9 clipper: support full page clips without styles and images 2023-09-22 10:32:52 +05:00
Ammar Ahmed
21720a510d editor: cache src of image when loading from network 2023-09-22 10:32:52 +05:00
Ammar Ahmed
a2d5e7f32f editor: layout images as block on mobile 2023-09-22 10:32:52 +05:00
Ammar Ahmed
629fb7b9cd mobile: patch lib to support asset reading 2023-09-22 10:32:52 +05:00
Ammar Ahmed
f6a4af8c00 clipper: convert relative urls to absolute 2023-09-22 10:32:52 +05:00
Ammar Ahmed
837e60316a mobile: fix loading clip on android 2023-09-22 10:32:52 +05:00
Ammar Ahmed
2592a90853 mobile: fix clipper build output path 2023-09-22 10:32:52 +05:00
ammarahm-ed
788429efb7 mobile: clip dynamic websites 2023-09-22 10:32:52 +05:00
Ammar Ahmed
674d8672f0 mobile: set title max length to 500 chars 2023-09-21 15:29:47 +05:00
ammarahm-ed
8d371fce79 mobile: multi-line title 2023-09-21 15:29:47 +05:00
Muhammad Ali
f0f391ec73 editors: add support for write protecting a specific task list (#3285)
* editor: added readonly attr for tasklist

* Update packages/editor/src/extensions/task-list/task-list.ts

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>

* editor: fixed tasklist title input

* editor: allow write protecting nested task lists

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-09-21 14:52:55 +05:00
Muhammad Ali
abc3b5569c core: fix upcoming reminder time calculation (#3341)
* core: added missing equality sign

* core: added test
2023-09-21 14:04:20 +05:00
Muhammad Ali
c42dc0f12e editor: add drag/drop support for attachments (#3325)
* editor: added drag for attachments

* editor: refactor attachment dragging check

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-09-21 13:59:41 +05:00
luzpaz
7ec14f2eff mobile: fix typos in apps/mobile subdirectory
Found via `codespell -q 3 -S "*.patch"`
2023-09-21 13:51:28 +05:00
ammarahm-ed
31d4e88efd mobile: check if file exists based on file size 2023-09-21 13:43:16 +05:00
Abdullah Atta
da06184d51 core: increment progress even if file exists 2023-09-21 13:43:16 +05:00
Abdullah Atta
08296793f1 web: improve attachment progress events & ui 2023-09-21 13:43:16 +05:00
Abdullah Atta
3b6fde2ea6 web: check file size when checking for file existence 2023-09-21 13:43:16 +05:00
Abdullah Atta
1f2948c9a5 core: check for file existence before calling downloadFile 2023-09-21 13:43:16 +05:00
ammarahm-ed
e62267042e mobile: add support for progress 2023-09-21 13:43:16 +05:00
ammarahm-ed
883fe48946 core: add download start & canceled events 2023-09-21 13:43:16 +05:00
Abdullah Atta
6ba18c5bd1 web: use new file upload/download events 2023-09-21 13:43:16 +05:00
Abdullah Atta
207347190d core: fix download/upload cancellation 2023-09-21 13:43:16 +05:00
Ammar Ahmed
0cf76e1f11 mobile: fix sorting notes in topics (#3348)
* mobile: fix sorting notes in topics

* mobile: fix list is not updated
2023-09-21 13:30:10 +05:00
ammarahm-ed
9d76311f82 mobile: support font scaling 2023-09-21 13:29:44 +05:00
ammarahm-ed
c327a292c7 mobile: show +, # and export icon in selection header 2023-09-21 13:24:25 +05:00
ammarahm-ed
5a965f6d52 mobile: simplify deleting topic 2023-09-21 13:24:25 +05:00
ammarahm-ed
a81c621d49 mobile: migrate to persisted app cache
We now use app files directory on android
& LibraryPath on iOS to store cache files
such as images etc so they don't get deleted
by the system
2023-09-21 13:19:46 +05:00
ammarahm-ed
2e7b5d0d08 mobile: fix attachments fail to encrypt from share extension 2023-09-21 13:19:46 +05:00
ammarahm-ed
6d324fe8da mobile: improve editor content loading 2023-09-21 13:10:55 +05:00
ammarahm-ed
6d2be3a083 mobile: clear download/upload progress on cancel token 2023-09-21 13:10:55 +05:00
ammarahm-ed
d252c6fc5c mobile: fix editor not reset session id 2023-09-21 13:10:55 +05:00
ammarahm-ed
30c5e87c67 mobile: support running pending sync 2023-09-21 13:06:38 +05:00
Abdullah Atta
2db819de2b web: add support for running pending sync 2023-09-21 13:06:38 +05:00
Muhammad Ali
bff5e64484 editor: fix plain text pasting on windows (#3402)
* editor: added check for empty <p/> with <br/>

* editor: improve paragraph empty check

* editor: add tests

* editor: fix CRLF line breaks when pasting text

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-09-21 11:58:55 +05:00
Abdullah Atta
3492bef2c7 crypto: fix nodejs native encryption causing file corruption 2023-09-21 11:30:47 +05:00
Abdullah Atta
6d9aa56084 web: fix loading indicator in setting buttons 2023-09-21 11:30:23 +05:00
Abdullah Atta
94c40b3444 desktop: fix jumplist item activation when app is running 2023-09-21 11:30:23 +05:00
Abdullah Atta
278c8b1115 web: use update status in settings about screen 2023-09-21 11:30:23 +05:00
Abdullah Atta
8e8581cce5 desktop: auto check for updates every 12 hours 2023-09-21 11:30:23 +05:00
Abdullah Atta
bd29f4e9e2 web: hide "You are on latest version" notice 2023-09-21 11:30:23 +05:00
Abdullah Atta
eb2b92d85c web: only show loading indicator on button that was pressed 2023-09-21 11:30:23 +05:00
Abdullah Atta
042ac0d338 desktop: fix auto updater events not sent to app 2023-09-21 11:30:23 +05:00
Abdullah Atta
a7011f0d6b global: update @trpc/* to 10.38.3 2023-09-21 11:30:23 +05:00
Abdullah Atta
22144ee18d web: fix backup validation check 2023-09-19 22:28:16 +05:00
Abdullah Atta
b85484fceb desktop: update lockfile 2023-09-15 17:13:47 +05:00
Abdullah Atta
b58956371f desktop: bump electron to v25.8.1 fixing CVE-2023-4863 2023-09-15 17:10:54 +05:00
Abdullah Atta
f487e9f7e2 global: update lockfiles 2023-09-15 15:06:27 +05:00
Abdullah Atta
f14efd3b1e web: fallback to blob download if service worker not found 2023-09-15 11:29:40 +05:00
Abdullah Atta
3496a42c19 web: bump to version 2.6.5 2023-09-15 10:24:15 +05:00
Abdullah Atta
0882c3ecf5 web: use single service worker for everything 2023-09-15 10:23:56 +05:00
ammarahm-ed
1f6d76bac7 mobile: update versionCode 2023-09-15 00:06:42 +05:00
ammarahm-ed
db7c3f4321 mobile: release v2.6.5 2023-09-14 23:45:44 +05:00
ammarahm-ed
9ea4651fb0 core: fix backup fail if item in null/undefined 2023-09-14 23:45:13 +05:00
Abdullah Atta
ed5a75447f web: register stream saver service worker on app start 2023-09-14 19:03:15 +05:00
ammarahm-ed
d08994a314 mobile: bump version code 2023-09-13 18:51:36 +05:00
ammarahm-ed
2bf6e7073c core: fix migration not running on colors 2023-09-13 18:21:55 +05:00
ammarahm-ed
7990458cf3 core: call migrateItem with backup key 2023-09-13 18:15:44 +05:00
ammarahm-ed
f3b13735f3 mobile: fix color of button in sheet 2023-09-13 18:14:03 +05:00
ammarahm-ed
ae680a32bc core: fix migration 2023-09-13 17:32:17 +05:00
ammarahm-ed
cbd0f18c6e mobile: release 2.6.4 2023-09-13 14:25:24 +05:00
ammarahm-ed
fd62b1b631 mobile: fix backup not created before migration 2023-09-13 13:50:05 +05:00
ammarahm-ed
2159f6720e mobile: fix delete notebook in bulk 2023-09-13 13:46:08 +05:00
ammarahm-ed
67c3a36464 editor: fix code-block ui 2023-09-13 13:44:53 +05:00
Abdullah Atta
652efc8624 ci: only get app store version if publishing to app store 2023-09-13 13:07:14 +05:00
Abdullah Atta
30da041ea3 web: ask for password if backup encryption if off 2023-09-13 12:48:58 +05:00
Abdullah Atta
e90c401503 web: bump version to 2.6.4 2023-09-13 12:47:10 +05:00
Abdullah Atta
83d919f06e web: fix login form shown for split second before login 2023-09-13 12:42:54 +05:00
Abdullah Atta
74fef0d2d9 core: run all items migrator before other migrators 2023-09-13 12:42:02 +05:00
Abdullah Atta
5906b24da1 core: add support for localOnly migrations 2023-09-13 12:40:30 +05:00
Abdullah Atta
8dd36f7adf core: set last synced to 0 right after login 2023-09-13 12:39:15 +05:00
ammarahm-ed
f2dc26dbff mobile: add support for nnbackupz format 2023-09-13 11:42:59 +05:00
ammarahm-ed
87915ecd00 core: handle null/undefined keys in backup 2023-09-13 11:41:49 +05:00
Abdullah Atta
3cf4e55621 core: restore colors from backups correctly 2023-09-13 10:45:40 +05:00
Abdullah Atta
4c7cd840df core: fix indexes getting replaced on backup restore 2023-09-13 10:23:44 +05:00
Abdullah Atta
bd238a6fe4 core: remove unnecessary exist check slowing reads 2023-09-13 10:23:44 +05:00
Abdullah Atta
beaace9b54 web: add support for streaming backup creation & restore 2023-09-13 10:23:44 +05:00
Abdullah Atta
c5352c2a73 core: add support for restoring new nnbackupz format 2023-09-13 10:23:44 +05:00
Abdullah Atta
dd6410c33e core: add support for streaming backups 2023-09-13 10:23:44 +05:00
Abdullah Atta
ac9dca8098 core: migrate db in a streaming way 2023-09-13 10:23:44 +05:00
Abdullah Atta
74ade6fecc core: make sure items sync after migration 2023-09-13 10:23:44 +05:00
Abdullah Atta
e1cfbb155d core: fix Cannot read properties of null (reading 'clear') 2023-09-13 10:23:44 +05:00
Abdullah Atta
030aba2ac3 core: add migration to remove item.remote 2023-09-13 10:23:44 +05:00
Abdullah Atta
9ab7e722e0 core: port fixes for sync issues from typescript branch 2023-09-13 10:23:44 +05:00
ammarahm-ed
14d6714209 theme: confirm before downloading code-block css 2023-09-11 12:51:01 +05:00
ammarahm-ed
e8e50bebcf theme: support code-block css in builder 2023-09-11 12:33:14 +05:00
ammarahm-ed
fa0610e392 mobile: release v2.6.3 2023-09-09 11:54:56 +05:00
Abdullah Atta
124489a5a5 web: bump to version 2.6.3 2023-09-09 11:06:56 +05:00
Abdullah Atta
22fcdc0fb1 desktop: bump to version 2.6.3 2023-09-09 11:06:20 +05:00
Abdullah Atta
1375300a91 desktop: downgrade electron to v25.8.0 2023-09-09 11:05:25 +05:00
Abdullah Atta
e6f4aea4d7 desktop: handle unhandled errors 2023-09-08 14:49:02 +05:00
Abdullah Atta
d72c4057ff theme: support ARGB values in background 2023-09-08 09:45:54 +05:00
Abdullah Atta
02d80ee4fa web: add separate plan for education discount 2023-09-07 18:39:21 +05:00
ammarahm-ed
2da3e9e0be mobile: release v2.6.2 2023-09-06 21:38:20 +05:00
ammarahm-ed
8333e7bdb3 mobile: set NSExtensionActivationDictionaryVersion to v2 2023-09-06 20:07:15 +05:00
ammarahm-ed
cef2472aae mobile: handle state when app entered background
to take some action like pick a file/image etc.
2023-09-06 20:06:46 +05:00
ammarahm-ed
a4946f7e89 mobile: hide caret when input 2fa code 2023-09-06 20:05:06 +05:00
ammarahm-ed
3e95e0feae mobile: fix db logger not logging 2023-09-06 20:04:46 +05:00
Abdullah Atta
b81843cdf7 core: sync settings before everything else 2023-09-06 19:31:06 +05:00
Abdullah Atta
eedd50fc64 web: bump version to 2.6.2 2023-09-06 17:06:02 +05:00
Abdullah Atta
1cb00bca0d core: fix collector tests 2023-09-06 17:00:04 +05:00
Abdullah Atta
8c233570a7 core: return default values for unset settings 2023-09-06 16:42:51 +05:00
Muhammad Ali
8a3c8b85bb web: fix autocomplete hint in signup form password fields (#3272) 2023-09-06 16:09:54 +05:00
Abdullah Atta
2ee8a807dc core: fix settings getting replaced on login 2023-09-06 15:56:20 +05:00
Abdullah Atta
ec85f748fa core: use serverLastSynced time from client 2023-09-06 15:43:20 +05:00
Abdullah Atta
051b7fcb8c core: notify autosync before setting remote= false 2023-09-06 15:28:56 +05:00
Abdullah Atta
9af88811d8 core: remove remote: true from settings after sync 2023-09-06 15:09:31 +05:00
Abdullah Atta
6c4672037c web: do not run sync on file picker closed 2023-09-06 14:49:56 +05:00
Abdullah Atta
d59e0d9ee2 core: sync attachments before anything else 2023-09-06 14:48:43 +05:00
Abdullah Atta
5a001a1457 core: use bulk io operations for logger storage 2023-09-06 12:30:39 +05:00
Abdullah Atta
44d6474b36 core: fix attachments not syncing 2023-09-06 12:14:03 +05:00
Abdullah Atta
0607e005f5 core: only sync syncable attachments 2023-09-06 12:04:58 +05:00
Abdullah Atta
308eb4b55f core: merge remote attachment if local attachment doesn't exist 2023-09-06 11:40:44 +05:00
Abdullah Atta
6abb6207c5 core: always keep remote attachment if it is newer 2023-09-06 11:16:53 +05:00
Abdullah Atta
89189d52ae web: add log on when sync is ignored 2023-09-06 10:28:31 +05:00
Abdullah Atta
e653ce69f8 core: push items serially tcp style 2023-09-06 10:22:53 +05:00
Abdullah Atta
9b89b328f6 core: send sync progress event after merge 2023-09-06 09:58:07 +05:00
Abdullah Atta
77ae71fab1 web: fix sync status progress 2023-09-06 09:40:24 +05:00
Abdullah Atta
7f7c1e6b93 core: use linkedom and node-fetch for tests 2023-09-06 09:40:24 +05:00
ammarahm-ed
30341bea1c mobile: run sync based on lastSyncTime 2023-09-06 09:27:02 +05:00
Abdullah Atta
82d44ea890 web: show sync status in place of placeholder 2023-09-05 20:15:34 +05:00
Abdullah Atta
5f0907168f core: always send sync request to client 2023-09-05 18:01:44 +05:00
Abdullah Atta
48061962b1 web: fix backup/export tests 2023-09-05 17:47:28 +05:00
Abdullah Atta
24141b66cf core: add log to debug relations removal issue 2023-09-05 17:47:11 +05:00
Abdullah Atta
41beee45ba core: migrate in bulk for more performance 2023-09-05 16:08:10 +05:00
Abdullah Atta
cb7c70b0d6 web: fix navigation menu ui 2023-09-05 15:47:39 +05:00
ammarahm-ed
2aa5a57557 mobile: use accentForeground on accent background 2023-09-05 15:40:25 +05:00
Abdullah Atta
17582bcefb web: fix navigation menu not taking up full space 2023-09-05 15:40:25 +05:00
Abdullah Atta
1f5a5afb10 theme: do not throw error if deprecated color is found 2023-09-05 15:40:25 +05:00
Abdullah Atta
d1c724563f web: fix themes selection not showing themes 2023-09-05 15:40:25 +05:00
Abdullah Atta
72aa5eccb7 server: fix themes pagination 2023-09-05 15:40:25 +05:00
Abdullah Atta
95e0c4b0d6 editor: reduce task list header padding 2023-09-05 15:40:25 +05:00
Abdullah Atta
9ec6ca0ded web: fix various colors 2023-09-05 15:40:25 +05:00
Abdullah Atta
85b7611214 theme: increase opacity of shade color 2023-09-05 15:40:25 +05:00
Abdullah Atta
d256d435e4 theme: mark shade and textSelection colors as deprecated 2023-09-05 15:40:25 +05:00
Abdullah Atta
577a976e01 theme: derive shade color from accent color 2023-09-05 15:40:25 +05:00
Abdullah Atta
07453834b0 docs: update list of colors in theme-engine docs 2023-09-05 15:40:25 +05:00
Abdullah Atta
59f6b5e411 theme: get rid of shade & textSelection colors 2023-09-05 15:40:25 +05:00
Abdullah Atta
ac9be8360a web: list item title should use heading color 2023-09-05 15:40:25 +05:00
Abdullah Atta
32d0b07f09 web: use accentForeground color instead of white 2023-09-05 15:40:25 +05:00
Abdullah Atta
7b57639076 theme: add new accentForeground color 2023-09-05 15:40:25 +05:00
Abdullah Atta
1fa174bab7 theme: fix input placeholder color 2023-09-05 15:40:25 +05:00
Abdullah Atta
6c52b5a77d editor: editor toolbar separators should use separator color 2023-09-05 15:40:25 +05:00
Abdullah Atta
d4a6157ed3 web: navigation menu separators should use separator color 2023-09-05 15:40:25 +05:00
Abdullah Atta
a86358bbcd web: borders between panes should use scoped border color 2023-09-05 15:40:25 +05:00
Abdullah Atta
20d0e6d047 web: fix menu borders should use border color 2023-09-05 15:40:25 +05:00
Muhammad Ali
51fc4bf3e4 core: add support for aligning images in PDF/HTML exports (#3197)
* web: added function for aligning images

* web: removed regex and added replaceAll in loop

* Update apps/web/src/common/export.ts

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>

* core: add support for image alignment in html/pdf export

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-09-05 15:39:59 +05:00
Abdullah Atta
61d0dafd21 desktop: update app-builder-lib patch 2023-09-04 16:01:04 +05:00
Abdullah Atta
89398cb5e5 misc: remove all mention of crypto-worker 2023-09-04 15:58:38 +05:00
Abdullah Atta
72ec4063de global: update lockfiles 2023-09-04 15:58:23 +05:00
Abdullah Atta
f8423eeea8 editor: update test snapshots 2023-09-04 15:51:47 +05:00
Abdullah Atta
d91203990e editor: respect text direction when exiting node using arrow keys 2023-09-04 15:51:47 +05:00
Abdullah Atta
fc84c1af51 editor: add keyboard specific android editor tests 2023-09-04 15:51:47 +05:00
Abdullah Atta
58de07073d editor: improve escaping from & deleting of nodes 2023-09-04 15:51:47 +05:00
Abdullah Atta
cafa5ff821 editor: fix blockquote padding & margin on mobile 2023-09-04 15:51:47 +05:00
Abdullah Atta
1859799ca3 editor: fix crash on adding a task list 2023-09-04 15:51:47 +05:00
Abdullah Atta
271aa11f74 editor: use list-keymap extension instead of own workarounds 2023-09-04 15:51:47 +05:00
Abdullah Atta
e42f1a65f5 editor: update tiptap packages to v2.1.7 2023-09-04 15:51:47 +05:00
Abdullah Atta
73f3a31614 editor: fix sub outline lists not getting outdented 2023-09-04 15:51:47 +05:00
Abdullah Atta
4d7792d06a editor: fix alignment of outline list indicator 2023-09-04 15:51:47 +05:00
ammarahm-ed
1a0f17c059 mobile: update relations store on open notification 2023-09-04 15:46:37 +05:00
ammarahm-ed
89d941401a mobile: fix beta tags ui 2023-09-04 15:46:23 +05:00
ammarahm-ed
a076ee640d mobile: save report issue information if app gets
restarted or accidentally closed
2023-09-04 15:45:30 +05:00
Abdullah Atta
8c1629f7dd web: ask for password when doing sensitive actions 2023-09-04 15:10:18 +05:00
ammarahm-ed
9a31c07ac5 mobile: add verification prompts 2023-09-04 15:10:18 +05:00
ammarahm-ed
11f39316b5 mobile: fix color scheme 2023-09-04 15:09:03 +05:00
ammarahm-ed
f157229e00 mobile: fix theme gets reset on opening share extension 2023-09-04 15:09:03 +05:00
ammarahm-ed
a0f25c4e6f mobile: fix list not updating on change date sort 2023-09-04 15:07:33 +05:00
Ammar Ahmed
003b3d2a84 Remove hover effect on mobile in editor (#3163)
* editor: remove background on hover on mobile

* editor: fix task item alignment and size on mobile
2023-09-04 15:06:41 +05:00
ammarahm-ed
1775e0bda1 mobile: fix flash on launch on ios 2023-09-04 14:37:24 +05:00
Ammar Ahmed
d5038ac631 mobile: fix quick notes from notifications (#3165)
- Add note to default notebook
- Respect paragraph spacing

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-09-04 14:36:55 +05:00
ammarahm-ed
7eab77ba01 mobile: fix share sheet sorting 2023-09-04 14:34:53 +05:00
ammarahm-ed
89413c56e2 mobile: auto update pinned note in notif 2023-09-04 14:33:39 +05:00
Muhammad Ali
4d89643203 web: fix multi-select context menu for list items (#3159) 2023-09-04 14:31:00 +05:00
ammarahm-ed
e119590249 mobile: prevent login/signup request when loading 2023-09-04 14:27:35 +05:00
ammarahm-ed
ace2dfac30 core: increase server timeout 2023-09-04 14:27:35 +05:00
Abdullah Atta
5843df0ce8 web: allow encrypted backups for basic users 2023-09-04 14:24:46 +05:00
Abdullah Atta
403ed8adb6 web: do not clear attachment upload status on complete 2023-09-04 14:24:24 +05:00
Abdullah Atta
1eb5c8bc46 core: do not trigger auto sync on settings merge 2023-09-04 14:24:24 +05:00
Abdullah Atta
46f7ec5e00 core: do not trigger autosync on attachment upload 2023-09-04 14:24:24 +05:00
Abdullah Atta
52adece147 web: upload arraybuffer on desktop builds for attachments 2023-09-04 14:24:24 +05:00
Abdullah Atta
de50be9d1b desktop: use net.fetch instead when handling protocol 2023-09-04 14:24:24 +05:00
Abdullah Atta
0f9e32b49f desktop: update electron to 26.1.0 2023-09-04 14:24:24 +05:00
Muhammad Ali
b4c15adf5d editor: fix formatting issues when pasting from ChatGPT (#3217)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-09-04 14:22:45 +05:00
Abdullah Atta
a3d3886c51 editor: refactor clipboard extension 2023-09-04 14:22:45 +05:00
Abdullah Atta
0cfa7ce774 editor: paste single line of code as inline code 2023-09-04 14:22:45 +05:00
Abdullah Atta
8299ea909e editor: improve code block detection when pasting from vscode 2023-09-04 14:22:45 +05:00
Abdullah Atta
8b056fe610 editor: preserve formatting when pasting text 2023-09-04 14:22:45 +05:00
Abdullah Atta
511c40db5c editor: do not replace newlines with br when pasting 2023-09-04 14:22:45 +05:00
Muhammad Ali
42d0d42188 web: scroll to top when navigating within settings (#3223) 2023-09-04 13:43:07 +05:00
Abdullah Atta
c80a998444 editor: fix image shuffling on real-time editor sync 2023-09-04 13:41:32 +05:00
ammarahm-ed
0a10df75d1 mobile: add encryptMulti 2023-09-04 13:16:22 +05:00
Abdullah Atta
40d755d191 core: fix all tests 2023-09-04 13:16:22 +05:00
Abdullah Atta
0409a610ab core: improve sync status updates 2023-09-04 13:16:22 +05:00
Abdullah Atta
aa9cd23cf3 core: add fix for localOnly items getting deleted on sync 2023-09-04 13:16:22 +05:00
Abdullah Atta
12d89114a4 core: fix tags & colors not resolving on sync 2023-09-04 13:16:22 +05:00
Abdullah Atta
d5b679187a core: send sync completed event udp style 2023-09-04 13:16:22 +05:00
ammarahm-ed
463ba3754a mobile: add encryptMulti function 2023-09-04 13:16:22 +05:00
Abdullah Atta
2fa8fd14e7 core: migrate to sync v2 for data uploads 2023-09-04 13:16:22 +05:00
Abdullah Atta
fac788d2a9 crypto: remove crypto-worker & simplify crypto interface 2023-09-04 13:16:22 +05:00
ammarahm-ed
6554f900d7 mobile: do not start sync if sync is running 2023-09-04 13:16:22 +05:00
Abdullah Atta
81d3ec83d9 core: fix realtime sync 2023-09-04 13:16:22 +05:00
Abdullah Atta
339c5a357b core: update note-storage mock for tests 2023-09-04 13:16:22 +05:00
ammarahm-ed
b484992037 core: remove console.log 2023-09-04 13:16:22 +05:00
ammarahm-ed
87e42e98c6 mobile: fix logout page ui 2023-09-04 13:16:22 +05:00
ammarahm-ed
51bef01609 mobile: fix logout loading ui 2023-09-04 13:16:22 +05:00
ammarahm-ed
6a49244614 core: fix item not loaded on sync 2023-09-04 13:16:22 +05:00
ammarahm-ed
dad2b61a74 mobile: fix crash when logging out user 2023-09-04 13:16:22 +05:00
ammarahm-ed
fabc595f3d mobile: fix storage 2023-09-04 13:16:22 +05:00
Abdullah Atta
a700787cf3 core: make sure all tests are passing 2023-09-04 13:16:22 +05:00
ammarahm-ed
fb26229715 mobile: update libraries 2023-09-04 13:16:22 +05:00
ammarahm-ed
c4f894f8d0 mobile: add async storage & encryption 2023-09-04 13:16:22 +05:00
Abdullah Atta
ddd1c19c64 core: improve merging performance 2023-09-04 13:16:22 +05:00
Abdullah Atta
79cb3bdba4 core: merge vault key on sync 2023-09-04 13:16:22 +05:00
Abdullah Atta
8447c2150b core: improve sync fetch performance 2023-09-04 13:16:22 +05:00
Abdullah Atta
bc465971be web: add support for bulk writes 2023-09-04 13:16:22 +05:00
Abdullah Atta
f8ce23aa11 crypto: add support for bulk decryption 2023-09-04 13:16:22 +05:00
Abdullah Atta
84004c4d73 global: update lockfiles 2023-08-25 14:30:41 +05:00
Muhammad Ali
2b6aa9b89b ui: allow changing menu button icon color (#3170) 2023-08-21 16:38:21 +05:00
ammarahm-ed
54b8a3e789 mobile: refactor to typescript 2023-08-16 10:14:39 +05:00
393 changed files with 13450 additions and 8494 deletions

View File

@@ -19,7 +19,6 @@ runs:
packages/crypto/package-lock.json
packages/sodium/package-lock.json
packages/clipper/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json

View File

@@ -97,6 +97,7 @@ jobs:
- name: Get App Store Version
id: appstore
uses: streetwriters/appstore-connect-app-version@develop
if: inputs.publish-apple
with:
app-id: ${{ steps.app_metadata.outputs.apple_app_id }}
key-id: ${{ secrets.api_key_id }}

View File

@@ -103,7 +103,7 @@ All commits must include valid scopes in the commit message. **Valid commit scop
**Packages:**
1. `crypto`: changes related to the cryptographic core (including `@notesnook/crypto-worker`)
1. `crypto`: changes related to the cryptographic core
2. `editor`: changes related to the editor (including `@notesnook/editor-mobile`)
3. `logger`: changes related to the logger
4. `theme`: changes related to the theme

View File

@@ -46,7 +46,6 @@ Notesnook is built using the following technologies:
| `@notesnook/editor-mobile` | [/packages/editor-mobile](/packages/editor-mobile) | A very thin wrapper around `@notesnook/editor` for mobile clients |
| `@notesnook/logger` | [/packages/logger](/packages/logger) | Simple & pluggable logger |
| `@notesnook/sodium` | [/packages/sodium](/packages/sodium) | Wrapper around libsodium to support Node.js & Browser |
| `@notesnook/crypto-worker` | [/packages/crypto-worker](/packages/crypto-worker) | Helpers to use `@notesnook/crypto` from a Worker |
| `@notesnook/streamable-fs` | [/packages/streamable-fs](/packages/streamable-fs) | Streaming interface around an IndexedDB based file system |
| `@notesnook/theme` | [/packages/theme](/packages/theme) | The core theme used in web & desktop clients |

View File

@@ -0,0 +1,3 @@
owner: streetwriters
repo: notesnook
provider: github

View File

@@ -19,9 +19,40 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-var */
import { BrowserWindow } from "electron";
import {
type FormData as FormDataType,
type Headers as HeadersType,
type Request as RequestType,
type Response as ResponseType
} from "undici";
declare global {
var window: BrowserWindow | null;
var RELEASE: boolean;
var MAC_APP_STORE: boolean;
// Re-export undici fetch function and various classes to global scope.
// These are classes and functions expected to be at global scope according to Node.js v18 API
// documentation.
// See: https://nodejs.org/dist/latest-v18.x/docs/api/globals.html
// eslint-disable-next-line no-var
export var {
FormData,
Headers,
Request,
Response,
fetch
}: typeof import("undici");
type FormData = FormDataType;
type Headers = HeadersType;
type Request = RequestType;
type Response = ResponseType;
}
// NOTE: the import in the global block above needs to be a var for this to work properly.
globalThis.fetch = fetch;
globalThis.FormData = FormData;
globalThis.Headers = Headers;
globalThis.Request = Request;
globalThis.Response = Response;

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.1",
"version": "2.6.7",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -12,25 +12,25 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.31.0",
"@trpc/server": "10.31.0",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.1",
"electron-updater": "6.1.4",
"icojs": "^0.17.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/node": "^18.15.0",
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "24.5.1",
"electron-builder": "24.4.0",
"electron": "25.8.1",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"node-fetch": "^3.3.1",
"tree-kill": "^1.2.2"
"tree-kill": "^1.2.2",
"undici": "^5.23.0"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
@@ -191,6 +191,7 @@
"allowNativeWayland": true
},
"extraResources": [
"app-update.yml",
"./assets/**"
],
"extraMetadata": {

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
index 3ba5d6a..cf8e147 100644
index fcb7f54..cb1c7f7 100644
--- a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
+++ b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
@@ -105,6 +105,7 @@ class LinuxTargetHelper {
@@ -116,6 +116,7 @@ class LinuxTargetHelper {
StartupWMClass: appInfo.productName,
...extra,
...targetSpecificOptions.desktop,
@@ -10,7 +10,7 @@ index 3ba5d6a..cf8e147 100644
};
const description = this.getDescription(targetSpecificOptions);
if (!(0, builder_util_1.isEmptyOrSpaces)(description)) {
@@ -148,6 +149,23 @@ class LinuxTargetHelper {
@@ -159,6 +160,23 @@ class LinuxTargetHelper {
data += `\n${name}=${desktopMeta[name]}`;
}
data += "\n";

View File

@@ -117,6 +117,14 @@ export const osIntegrationRouter = t.router({
writeFileSync(resolvedPath, data);
}),
resolvePath: t.procedure
.input(z.object({ filePath: z.string() }))
.query(({ input }) => {
const { filePath } = input;
if (!filePath) return;
return resolvePath(filePath);
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {

View File

@@ -27,19 +27,23 @@ export type CLIOptions = {
hidden: boolean;
};
export async function parseArguments(): Promise<CLIOptions> {
export async function parseArguments(argv: string[]): Promise<CLIOptions> {
const result: CLIOptions = {
note: false,
notebook: false,
reminder: false,
hidden: false
};
const { hidden } = await yargs(hideBin(process.argv))
const { hidden } = await yargs(hideBin(argv))
.boolean("hidden")
// have to account for this flag added on Windows when launching
// via Jumplist
.boolean("allow-file-access-from-files")
.command("new", "Create a new item", (yargs) => {
return yargs
.command("note", "Create a new note", {}, () => {
result.note = true;
console.log("HERE!");
})
.command("notebook", "Create a new notebook", {}, () => {
result.notebook = true;

View File

@@ -34,6 +34,7 @@ import { router, api } from "./api";
import { config } from "./utils/config";
import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -53,10 +54,17 @@ if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
process.on("uncaughtException", (error) => {
console.error("uncaughtException:", error);
});
process.on("unhandledRejection", (reason) => {
console.error("unhandledRejection:", reason);
});
app.commandLine.appendSwitch("lang", "en-US");
async function createWindow() {
const cliOptions = await parseArguments();
const cliOptions = await parseArguments(process.argv);
setTheme(getTheme());
const mainWindowState = new WindowState({});
@@ -83,6 +91,7 @@ async function createWindow() {
}
});
createIPCHandler({ router, windows: [mainWindow] });
globalThis.window = mainWindow;
mainWindowState.manage(mainWindow);
@@ -98,7 +107,6 @@ async function createWindow() {
await AssetManager.loadIcons();
setupDesktopIntegration();
createIPCHandler({ router, windows: [mainWindow] });
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
@@ -139,8 +147,12 @@ app.once("window-all-closed", () => {
}
});
app.on("second-instance", () => {
app.on("second-instance", async (_ev, argv) => {
if (!globalThis.window) return;
const cliOptions = await parseArguments(argv);
if (cliOptions.note) bridge.onCreateItem("note");
if (cliOptions.notebook) bridge.onCreateItem("notebook");
if (cliOptions.reminder) bridge.onCreateItem("reminder");
bringToFront();
});
@@ -181,7 +193,7 @@ function setupDesktopIntegration() {
app.on("before-quit", () => app.exit(0));
}
globalThis.window?.once("close", (e) => {
globalThis.window?.on("close", (e) => {
if (config.desktopSettings.closeToSystemTray) {
e.preventDefault();
if (process.platform == "darwin") {

View File

@@ -31,7 +31,6 @@ declare global {
}
process.once("loaded", async () => {
console.log("HELLO!");
const electronTRPC: RendererGlobalElectronTRPC = {
sendMessage: (operation) =>
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),

View File

@@ -17,16 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { protocol, ProtocolRequest } from "electron";
import { protocol, net } from "electron";
import { isDevelopment } from "./index";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
import { URL } from "url";
import fetch, { Response } from "node-fetch";
const BASE_PATH = isDevelopment() ? "../public" : "";
const HOSTNAME = `app.notesnook.com`;
const FILE_NOT_FOUND = -6;
const SCHEME = "https";
const extensionToMimeType: Record<string, string> = {
html: "text/html",
@@ -34,101 +32,42 @@ const extensionToMimeType: Record<string, string> = {
js: "application/javascript",
css: "text/css",
svg: "image/svg+xml",
png: "image/png"
png: "image/png",
jpg: "image/jpg",
ttf: "font/ttf",
woff: "font/woff",
woff2: "font/woff2"
};
function registerProtocol() {
const protocolInterceptionResult = protocol.interceptStreamProtocol(
SCHEME,
async (request, callback) => {
const url = new URL(request.url);
if (shouldInterceptRequest(url)) {
console.info("Intercepting request:", request.url);
const loadIndex = !extname(url.pathname);
const filePath = normalize(
`${__dirname}${
loadIndex
? `${BASE_PATH}/index.html`
: `${BASE_PATH}/${url.pathname}`
}`
);
if (!filePath) {
console.error("Local asset file not found at", filePath);
callback({ error: FILE_NOT_FOUND });
return;
}
const fileExtension = extname(filePath).replace(".", "");
const data = createReadStream(filePath);
callback({
data,
mimeType: extensionToMimeType[fileExtension]
});
} else {
let response: Response;
try {
const body = await getBody(request);
response = await fetch(request.url, {
...request,
body,
headers: {
...request.headers
// origin: `${PROTOCOL}://${HOSTNAME}/`
},
referrer: request.referrer,
redirect: "manual"
});
} catch (e) {
console.error(e);
console.error(`Error sending request to `, request.url, "Error: ", e);
callback({ statusCode: 400 });
return;
}
callback({
statusCode: response.status,
data: response.body || undefined,
headers: Object.fromEntries(response.headers.entries()),
mimeType: response.headers.get("Content-Type") || undefined
protocol.handle(SCHEME, async (request) => {
const url = new URL(request.url);
if (shouldInterceptRequest(url)) {
console.info("Intercepting request:", request.url);
const loadIndex = !extname(url.pathname);
const filePath = normalize(
`${__dirname}${
loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
}`
);
if (!filePath) {
console.error("Local asset file not found at", filePath);
return new Response(undefined, {
status: 404,
statusText: "FILE_NOT_FOUND"
});
}
const fileExtension = extname(filePath).replace(".", "");
return new Response(createReadStream(filePath), {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
return net.fetch(request, {
bypassCustomProtocolHandlers: true
});
}
);
console.info(
`${SCHEME} protocol inteception ${
protocolInterceptionResult ? "successful" : "failed"
}.`
);
// protocol.handle(SCHEME, (request) => {
// const url = new URL(request.url);
// if (shouldInterceptRequest(url)) {
// console.info("Intercepting request:", request.url);
// const loadIndex = !extname(url.pathname);
// const absoluteFilePath = normalize(
// `${__dirname}${
// loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
// }`
// );
// const filePath = getPath(absoluteFilePath);
// if (!filePath) {
// console.error("Local asset file not found at", filePath);
// return new Response(undefined, {
// status: 404,
// statusText: "FILE_NOT_FOUND"
// });
// }
// const fileExtension = extname(filePath).replace(".", "");
// const data = createReadStream(filePath);
// return new Response(data, {
// headers: { "Content-Type": extensionToMimeType[fileExtension] }
// });
// } else {
// return net.fetch(request);
// }
// });
// console.info(`${SCHEME} protocol inteception "successful"`);
});
console.info(`${SCHEME} protocol inteception "successful"`);
}
const bypassedRoutes: string[] = [];
@@ -139,22 +78,3 @@ function shouldInterceptRequest(url: URL) {
const PROTOCOL_URL = `${SCHEME}://${HOSTNAME}/`;
export { registerProtocol, PROTOCOL_URL };
async function getBody(request: ProtocolRequest) {
const session = globalThis?.window?.webContents?.session;
const blobParts = [];
if (!request.uploadData || !request.uploadData.length) return null;
for (const data of request.uploadData) {
if (data.bytes) {
blobParts.push(new Uint8Array(data.bytes));
} else if (session && data.blobUUID) {
const buffer = await session.getBlobData(data.blobUUID);
if (!buffer) continue;
blobParts.push(new Uint8Array(buffer));
}
}
const blob = new Blob(blobParts);
return blob;
}

View File

@@ -16,6 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "@azure/core-asynciterator-polyfill";
import SettingsService from "./services/settings";
import {
THEME_COMPATIBILITY_VERSION,
@@ -45,6 +46,7 @@ const App = () => {
if (appLockMode && appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
globalThis["IS_MAIN_APP_RUNNING"] = true;
init();
setTimeout(async () => {
SettingsService.onFirstLaunch();
@@ -92,6 +94,12 @@ const App = () => {
);
};
let currTheme =
useThemeStore.getState().colorScheme === "dark"
? SettingsService.getProperty("darkTheme")
: SettingsService.getProperty("lightTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element) => {
return function AppWithThemeProvider() {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
@@ -123,11 +131,13 @@ export const withTheme = (Element) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const nextTheme = colorScheme === "dark" ? darkTheme : lightTheme;
if (JSON.stringify(nextTheme) !== JSON.stringify(currTheme)) {
useThemeEngineStore
.getState()
.setTheme(colorScheme === "dark" ? darkTheme : lightTheme);
}, [colorScheme, darkTheme, lightTheme]);
currTheme = nextTheme;
}
return <Element />;
};

View File

@@ -126,6 +126,18 @@ export async function decrypt(password, data) {
return await Sodium.decrypt(password, _data);
}
export async function decryptMulti(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
data = data.map((d) => {
d.output = "plain";
return d;
});
return await Sodium.decryptMulti(password, data);
}
export function parseAlgorithm(alg) {
if (!alg) return {};
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
@@ -154,3 +166,24 @@ export async function encrypt(password, data) {
alg: getAlgorithm(7)
};
}
export async function encryptMulti(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let results = await Sodium.encryptMulti(
password,
data.map((item) => ({
type: "plain",
data: item
}))
);
return !results
? []
: results.map((result) => ({
...result,
alg: getAlgorithm(7)
}));
}

View File

@@ -17,18 +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 { database } from "@notesnook/common";
import { initalize, logger as dbLogger } from "@notesnook/core/dist/logger";
import { logger as dbLogger } from "@notesnook/core/dist/logger";
import { Platform } from "react-native";
import { MMKVLoader } from "react-native-mmkv-storage";
import filesystem from "../filesystem";
import * as Gzip from "react-native-gzip";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import Storage, { KV } from "./storage";
import * as Gzip from "react-native-gzip";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
import filesystem from "../filesystem";
import "./logger";
import Storage from "./storage";
database.host(
__DEV__
@@ -38,11 +34,11 @@ database.host(
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.8.101:5264",
// AUTH_HOST: "http://192.168.8.101:8264",
// SSE_HOST: "http://192.168.8.101:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.8.101:9264",
// ISSUES_HOST: "http://192.168.8.101:2624"
// API_HOST: "http://192.168.43.108:5264",
// AUTH_HOST: "http://192.168.43.108:8264",
// SSE_HOST: "http://192.168.43.108:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.108:9264",
// ISSUES_HOST: "http://192.168.43.108:2624"
}
: {
API_HOST: "https://api.notesnook.com",
@@ -63,7 +59,5 @@ database.setup(
}
);
initalize(new KV(LoggerStorage), true);
export const db = database;
export const DatabaseLogger = dbLogger;

View File

@@ -0,0 +1,29 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { MMKVLoader } from "react-native-mmkv-storage";
import { initalize } from "@notesnook/core/dist/logger";
import { KV } from "./storage";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
initalize(new KV(LoggerStorage));
export {};

View File

@@ -18,12 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage";
import { ProcessingModes, MMKVLoader } from "react-native-mmkv-storage";
export const MMKV = new MMKVStorage.Loader()
export const MMKV = new MMKVLoader()
.setProcessingMode(
Platform.OS === "ios"
? ProcessingModes.MULTI_PROCESS
: ProcessingModes.SINGLE_PROCESS
)
.disableIndexing()
.initialize();

View File

@@ -27,11 +27,16 @@ import {
getCryptoKey,
getRandomBytes,
hash,
removeCryptoKey
removeCryptoKey,
decryptMulti,
encryptMulti
} from "./encryption";
import { MMKV } from "./mmkv";
export class KV {
/**
* @type {typeof MMKV}
*/
storage = null;
constructor(storage) {
this.storage = storage;
@@ -53,6 +58,7 @@ export class KV {
key,
typeof data === "string" ? data : JSON.stringify(data)
);
return true;
}
@@ -60,27 +66,32 @@ export class KV {
if (keys.length <= 0) {
return [];
} else {
let data = await this.storage.getMultipleItemsAsync(keys.slice());
return data.map(([key, value]) => {
let obj;
try {
obj = JSON.parse(value);
} catch (e) {
obj = value;
}
return [key, obj];
});
try {
let data = await this.storage.getMultipleItemsAsync(
keys.slice(),
"string"
);
return data.map(([key, value]) => {
let obj;
try {
obj = JSON.parse(value);
} catch (e) {
obj = value;
}
return [key, obj];
});
} catch (e) {
console.log(e);
}
}
}
async remove(key) {
return await this.storage.removeItem(key);
return this.storage.removeItem(key);
}
async clear() {
return await this.storage.clearStore();
return this.storage.clearStore();
}
async getAllKeys() {
@@ -96,6 +107,10 @@ export class KV {
);
return keys;
}
async writeMulti(items) {
return this.storage.setMultipleItemsAsync(items, "object");
}
}
const DefaultStorage = new KV(MMKV);
@@ -129,8 +144,10 @@ export default {
remove: (key) => DefaultStorage.remove(key),
clear: () => DefaultStorage.clear(),
getAllKeys: () => DefaultStorage.getAllKeys(),
writeMulti: (items) => DefaultStorage.writeMulti(items),
encrypt,
decrypt,
decryptMulti,
getRandomBytes,
checkAndCreateDir,
requestPermission,
@@ -138,5 +155,6 @@ export default {
getCryptoKey,
removeCryptoKey,
hash,
generateCryptoKey
generateCryptoKey,
encryptMulti
};

View File

@@ -34,6 +34,7 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir } from "./io";
export const FileDownloadStatus = {
Success: 1,
@@ -62,6 +63,7 @@ export async function downloadAttachments(
canceled,
groupId
) {
await createCacheDir();
if (!attachments || !attachments.length) return;
const result = new Map();
@@ -182,6 +184,8 @@ export default async function downloadAttachment(
groupId: undefined
}
) {
await createCacheDir();
let attachment = db.attachments.attachment(hash);
if (!attachment) {
console.log("attachment not found");

View File

@@ -24,15 +24,18 @@ import { ToastEvent } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
export async function downloadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
try {
let exists = await RNFetchBlob.fs.exists(path);
if (exists) {
if (await exists(filename)) {
return true;
}
@@ -60,29 +63,34 @@ export async function downloadFile(filename, data, cancelToken) {
console.log("downloading: ", recieved, total);
});
cancelToken.cancel = request.cancel;
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
};
let response = await request;
await fileCheck(response, totalSize);
let status = response.info().status;
useAttachmentStore.getState().remove(filename);
return status >= 200 && status < 300;
} catch (e) {
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "global"
});
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "local"
});
if (e.message !== "canceled") {
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "global"
});
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "local"
});
}
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log("download file error: ", e, url, headers);
console.log("Download file error:", e, url, headers);
return false;
}
}

View File

@@ -20,32 +20,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "react-native-blob-util";
import { cacheDir, getRandomId } from "./utils";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
console.log("Read encrypted file...");
let path = `${cacheDir}/${filename}`;
try {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists =
(await RNFetchBlob.fs.exists(path)) ||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
if (!exists) {
if (!(await exists(filename))) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{
@@ -59,6 +52,7 @@ export async function readEncrypted(filename, key, cipherData) {
: "base64"
: "text"
);
console.log("file decrypted...");
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
const dCachePath = `${cacheDir}/${output}`;
output = await compressToBase64(
@@ -70,7 +64,7 @@ export async function readEncrypted(filename, key, cipherData) {
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log(e);
console.log("readEncrypted", e);
return false;
}
}
@@ -88,6 +82,7 @@ export async function hashBase64(data) {
}
export async function writeEncryptedBase64({ data, key }) {
await createCacheDir();
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
let output = await Sodium.encryptFile(key, {
@@ -103,6 +98,7 @@ export async function writeEncryptedBase64({ data, key }) {
}
export async function deleteFile(filename, data) {
await createCacheDir();
let delFilePath = cacheDir + `/${filename}`;
if (!data) {
if (!filename) return;
@@ -128,19 +124,89 @@ export async function deleteFile(filename, data) {
export async function clearFileStorage() {
try {
let files = await RNFetchBlob.fs.ls(cacheDir);
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (let file of files) {
try {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`);
} catch (e) {
console.log(e);
}
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
}
for (let file of oldCache) {
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
}
} catch (e) {
console.log(e);
console.log("clearFileStorage", e);
}
}
export async function createCacheDir() {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
console.log("Cache directory created");
}
}
export async function migrateFilesFromCache() {
try {
await createCacheDir();
const migratedFilesPath = cacheDir + "/.migrated_1";
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
if (migrated) {
console.log("Files migrated already");
return;
}
let files = await RNFetchBlob.fs.ls(cacheDir);
console.log("Files to migrate:", files.join(","));
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (let file of oldCache) {
if (file.startsWith("org.") || file.startsWith("com.")) continue;
RNFetchBlob.fs
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
.catch(console.log);
console.log("Moved", file);
}
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
} catch (e) {
console.log("migrateFilesFromCache", e);
}
}
const ABYTES = 17;
export async function exists(filename) {
let exists = await RNFetchBlob.fs.exists(`${cacheDir}/${filename}`);
let path = `${cacheDir}/${filename}`;
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists = await RNFetchBlob.fs.exists(path);
// Check if file is present in app group path.
let existsInAppGroup = false;
if (!exists && Platform.OS === "ios") {
existsInAppGroup = await RNFetchBlob.fs.exists(appGroupPath);
}
if (exists || existsInAppGroup) {
const attachment = db.attachments.attachment(filename);
const totalChunks = Math.ceil(attachment.length / attachment.chunkSize);
const totalAbytes = totalChunks * ABYTES;
const expectedFileSize = attachment.length + totalAbytes;
const stat = await RNFetchBlob.fs.stat(
existsInAppGroup ? appGroupPath : path
);
if (stat.size !== expectedFileSize) {
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);
return false;
}
exists = true;
}
return exists;
}

View File

@@ -24,11 +24,12 @@ import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
await createCacheDir();
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
@@ -72,7 +73,10 @@ export async function uploadFile(filename, data, cancelToken) {
);
});
cancelToken.cancel = request.cancel;
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
};
let response = await request;
let status = response.info().status;

View File

@@ -21,7 +21,12 @@ import * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
export const cacheDir =
Platform.OS == "ios"
? RNFetchBlob.fs.dirs.LibraryDir + "/.cache"
: RNFetchBlob.fs.dirs.DocumentDir + "/.cache";
export function getRandomId(prefix) {
return Math.random()
@@ -66,9 +71,12 @@ export function cancelable(operation) {
}
export function copyFileAsync(source, dest) {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
ScopedStorage.copyFile(source, dest, (e, r) => {
console.log(e, r);
if (e) {
reject(e);
return;
}
resolve();
});
});

View File

@@ -128,7 +128,9 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
fontSize={SIZE.md}
buttonType={{
color: color ? color : colors.primary.accent,
text: colors.static.white,
text: color
? colors.static.white
: colors.primary.accentForeground,
selected: color ? color : colors.primary.accent,
opacity: 1
}}

View File

@@ -132,7 +132,7 @@ export const AttachmentItem = ({
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs.cancel(attachment.metadata.hash);
db.fs.cancel(attachment.metadata.hash, currentProgress.type);
setCurrentProgress(null);
}}
style={{

View File

@@ -85,11 +85,13 @@ const AuthModal = () => {
background={colors.primary.background}
transparent={false}
animated={false}
centered={false}
>
<KeyboardAwareScrollView
style={{
width: "100%"
}}
enableAutomaticScroll={false}
keyboardShouldPersistTaps="handled"
>
{currentAuthMode !== AuthMode.login ? (

View File

@@ -46,6 +46,7 @@ import Input from "../ui/input";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { LoginSteps, useLogin } from "./use-login";
import BaseDialog from "../dialog/base-dialog";
function getObfuscatedEmail(email) {
if (!email) return "";
@@ -68,7 +69,8 @@ export const SessionExpired = () => {
eSendEvent(eUserLoggedIn, true);
setVisible(false);
setFocused(false);
}
},
true
);
const logout = async () => {
@@ -130,15 +132,20 @@ export const SessionExpired = () => {
return (
visible && (
<Modal
<BaseDialog
transparent={false}
background={colors.primary.background}
bounce={false}
animated={false}
centered={false}
onShow={async () => {
await sleep(300);
passwordInputRef.current?.focus();
setFocused(true);
}}
enableSheetKeyboardHandler={true}
visible={true}
>
<SheetProvider context="two_factor_verify" />
<View
style={{
width: focused ? "100%" : "99.9%",
@@ -231,7 +238,9 @@ export const SessionExpired = () => {
</View>
<Toast context="local" />
<Dialog context="session_expiry" />
</Modal>
<SheetProvider context="two_factor_verify" />
</BaseDialog>
)
);
};

View File

@@ -67,6 +67,7 @@ export const Signup = ({ changeMode, trial }) => {
const signup = async () => {
if (!validateInfo() || error) return;
if (loading) return;
setLoading(true);
try {
await db.user.signup(email.current.toLowerCase(), password.current);

View File

@@ -214,6 +214,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
code.current = value;
//onNext();
}}
caretHidden
inputStyle={{
fontSize: SIZE.lg,
height: 60,

View File

@@ -33,7 +33,7 @@ export const LoginSteps = {
passwordAuth: 3
};
export const useLogin = (onFinishLogin) => {
export const useLogin = (onFinishLogin, sessionExpired = false) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
@@ -64,10 +64,12 @@ export const useLogin = (onFinishLogin) => {
const login = async () => {
if (!validateInfo() || error) return;
try {
if (loading) return;
setLoading(true);
switch (step) {
case LoginSteps.emailAuth: {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(async (mfa, callback) => {
try {
@@ -100,7 +102,12 @@ export const useLogin = (onFinishLogin) => {
break;
}
case LoginSteps.passwordAuth: {
await db.user.authenticatePassword(email.current, password.current);
await db.user.authenticatePassword(
email.current,
password.current,
null,
sessionExpired
);
finishLogin();
break;
}

View File

@@ -17,12 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import React, { PropsWithChildren } from "react";
import { View } from "react-native";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useThemeColors } from "@notesnook/theme";
export const ContainerHeader = ({ children }) => {
export const ContainerHeader = (props: PropsWithChildren) => {
const { colors } = useThemeColors();
const selectionMode = useSelectionStore((state) => state.selectionMode);
@@ -34,7 +34,7 @@ export const ContainerHeader = ({ children }) => {
overflow: "hidden"
}}
>
{children}
{props.children}
</View>
) : null;
};

View File

@@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect } from "react";
import { Keyboard, Platform, View } from "react-native";
import { Keyboard, View } from "react-native";
import Animated, {
Easing,
useAnimatedStyle,
@@ -34,14 +35,20 @@ import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import NativeTooltip from "../../utils/tooltip";
import { PressableButton } from "../ui/pressable";
import { useThemeColors } from "@notesnook/theme";
export const FloatingButton = ({
interface FloatingButton {
title?: string;
onPress: () => void;
color?: string;
alwaysVisible?: boolean;
}
const FloatingButton = ({
title,
onPress,
color,
shouldShow = false
}) => {
alwaysVisible = false
}: FloatingButton) => {
const { colors } = useThemeColors();
const deviceMode = useSettingStore((state) => state.deviceMode);
const selectionMode = useSelectionStore((state) => state.selectionMode);
@@ -60,12 +67,8 @@ export const FloatingButton = ({
};
});
useEffect(() => {
animate(selectionMode ? 150 : 0);
}, [animate, selectionMode]);
const animate = useCallback(
(toValue) => {
(toValue: number) => {
translate.value = withTiming(toValue, {
duration: 250,
easing: Easing.elastic(1)
@@ -74,39 +77,39 @@ export const FloatingButton = ({
[translate]
);
const onKeyboardHide = useCallback(async () => {
editorState().keyboardState = false;
if (deviceMode !== "mobile") return;
animate(0);
}, [animate, deviceMode]);
const onKeyboardShow = useCallback(async () => {
editorState().keyboardState = true;
if (deviceMode !== "mobile") return;
animate(150);
}, [animate, deviceMode]);
useEffect(() => {
animate(selectionMode ? 150 : 0);
}, [animate, selectionMode]);
useEffect(() => {
let sub1 = Keyboard.addListener("keyboardDidShow", onKeyboardShow);
let sub2 = Keyboard.addListener("keyboardDidHide", onKeyboardHide);
return () => {
sub1?.remove();
sub2?.remove();
const onKeyboardHide = () => {
editorState().keyboardState = false;
if (deviceMode !== "mobile") return;
animate(0);
};
}, [deviceMode, onKeyboardHide, onKeyboardShow]);
const paddings = {
ios: 20,
android: 20,
iPad: 20
};
return deviceMode !== "mobile" && !shouldShow ? null : (
const onKeyboardShow = () => {
editorState().keyboardState = true;
if (deviceMode !== "mobile") return;
animate(150);
};
const sub = [
Keyboard.addListener("keyboardDidShow", onKeyboardShow),
Keyboard.addListener("keyboardDidHide", onKeyboardHide)
];
return () => {
sub.forEach((sub) => sub?.remove?.());
};
}, [deviceMode, animate]);
return deviceMode !== "mobile" && !alwaysVisible ? null : (
<Animated.View
style={[
{
position: "absolute",
right: 12,
bottom: paddings[Platform.isPad ? "iPad" : Platform.OS],
bottom: 20,
zIndex: 10
},
animatedStyle
@@ -115,16 +118,15 @@ export const FloatingButton = ({
<PressableButton
testID={notesnook.buttons.add}
type="accent"
buttonType={{
color: colors.static[color],
text: colors.static.white
}}
accentColor={colors.static[color as keyof typeof colors.static]}
customStyle={{
...getElevationStyle(5),
borderRadius: 100
}}
onLongPress={(event) => {
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
if (title) {
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
}
}}
onPress={onPress}
>
@@ -138,7 +140,7 @@ export const FloatingButton = ({
>
<Icon
name={title === "Clear all trash" ? "delete" : "plus"}
color="white"
color={colors.primary.accentForeground}
size={SIZE.xxl}
/>
</View>
@@ -146,3 +148,5 @@ export const FloatingButton = ({
</Animated.View>
);
};
export { FloatingButton };

View File

@@ -17,44 +17,40 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { KeyboardAvoidingView, Platform, View } from "react-native";
import React, { PropsWithChildren } from "react";
import { KeyboardAvoidingView, Platform } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { useSettingStore } from "../../stores/use-setting-store";
import { Header } from "../header";
import SelectionHeader from "../selection-header";
export const Container = ({ children }) => {
export const Container = ({ children }: PropsWithChildren) => {
const floating = useIsFloatingKeyboard();
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const insets = useGlobalSafeAreaInsets();
return (
<KeyboardAvoidingView
behavior="padding"
enabled={Platform.OS === "ios" && !floating}
style={{
flex: 1
flex: 1,
overflow: "hidden",
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
}}
>
<View
style={{
flex: 1,
overflow: "hidden",
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
}}
>
{!introCompleted ? null : (
<>
<SelectionHeader />
<Header title="Header" screen="Header" />
</>
)}
{!introCompleted ? null : (
<>
<SelectionHeader />
<Header />
</>
)}
{children}
</View>
{children}
</KeyboardAvoidingView>
);
};

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 React, { useEffect } from "react";
import React, { useEffect, useRef, useState } from "react";
import {
KeyboardAvoidingView,
Modal,
@@ -31,6 +31,9 @@ import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { useSettingStore } from "../../stores/use-setting-store";
import { BouncingView } from "../ui/transitions/bouncing-view";
import { ScopedThemeProvider } from "@notesnook/theme";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { useAppState } from "../../hooks/use-app-state";
const BaseDialog = ({
visible,
@@ -48,9 +51,13 @@ const BaseDialog = ({
bounce = true,
closeOnTouch = true,
useSafeArea = true,
avoidKeyboardResize = false
avoidKeyboardResize = false,
enableSheetKeyboardHandler = false
}) => {
const floating = useIsFloatingKeyboard();
const appState = useAppState();
const lockEvents = useRef(false);
const [internalVisible, setIntervalVisible] = useState(true);
useEffect(() => {
return () => {
@@ -58,14 +65,33 @@ const BaseDialog = ({
};
}, []);
useEffect(() => {
if (SettingsService.get().appLockMode === "background") {
if (appState === "background") {
setIntervalVisible(false);
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
setIntervalVisible(true);
unsub();
setTimeout(() => {
lockEvents.current = false;
});
}
});
}
}
}
}, [appState]);
const Wrapper = useSafeArea ? SafeAreaView : View;
return (
<ScopedThemeProvider value="dialog">
<Modal
visible={visible}
visible={visible && internalVisible}
transparent={true}
animated
statusBarTranslucent={statusBarTranslucent}
supportedOrientations={[
"portrait",
@@ -75,13 +101,17 @@ const BaseDialog = ({
"landscape-right"
]}
onShow={() => {
if (lockEvents.current) return;
if (onShow) {
onShow();
useSettingStore.getState().setSheetKeyboardHandler(false);
if (!enableSheetKeyboardHandler) {
useSettingStore.getState().setSheetKeyboardHandler(false);
}
}
}}
animationType={animation}
onRequestClose={() => {
if (lockEvents.current) return;
if (!closeOnTouch) return null;
useSettingStore.getState().setSheetKeyboardHandler(true);
onRequestClose && onRequestClose();
@@ -107,6 +137,7 @@ const BaseDialog = ({
style={[
styles.backdrop,
{
alignItems: centered ? "center" : undefined,
justifyContent: centered
? "center"
: bottom
@@ -133,8 +164,7 @@ const styles = StyleSheet.create({
backdrop: {
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center"
justifyContent: "center"
},
overlayButton: {
width: "100%",

View File

@@ -48,7 +48,7 @@ export const ProFeatures = ({ count = 6 }) => {
content: "Instantly sync to unlimited devices"
},
{
content: "A private vault to keep everything imporant always locked"
content: "A private vault to keep everything important always locked"
},
{
content:

View File

@@ -442,6 +442,7 @@ export class VaultDialog extends Component {
if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
await this._enrollFingerprint(this.password);
}
if (this.state.goToEditor) {
this._openInEditor(note);
} else if (this.state.share) {
@@ -452,7 +453,6 @@ export class VaultDialog extends Component {
await this._copyNote(note);
}
} catch (e) {
console.log(e);
this._takeErrorAction(e);
}
};
@@ -467,44 +467,40 @@ export class VaultDialog extends Component {
}
async _enrollFingerprint(password) {
try {
this.setState(
{
loading: true
},
async () => {
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
this.setState({
loading: false
});
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
context: "global"
});
this.close();
} catch (e) {
ToastEvent.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
type: "error",
context: "local"
});
this.setState({
loading: false
});
return;
}
this.setState(
{
loading: true
},
async () => {
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
this.setState({
loading: false
});
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
context: "global"
});
this.close();
} catch (e) {
this.close();
ToastEvent.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
type: "error",
context: "local"
});
this.setState({
loading: false
});
}
);
} catch (e) {
this._takeErrorAction(e);
}
}
);
}
async _createVault() {
@@ -543,7 +539,7 @@ export class VaultDialog extends Component {
.remove(this.state.note.id, this.password)
.then(() => {
ToastEvent.show({
heading: "Note permanantly unlocked.",
heading: "Note permanently unlocked.",
type: "success",
context: "global"
});

View File

@@ -39,9 +39,9 @@ class ExceptionHandler extends React.Component<{
error: Error | null;
hasError: boolean;
} = {
hasError: false,
error: null
};
hasError: false,
error: null
};
static getDerivedStateFromError(error: Error) {
return { hasError: true, error: error };
}
@@ -69,7 +69,7 @@ class ExceptionHandler extends React.Component<{
this.props.component
)}
defaultTitle={this.state.error?.message}
issueTitle="An exception occured"
issueTitle="An exception occurred"
/>
<Dialog />
</SafeAreaView>

View File

@@ -43,7 +43,7 @@ const _Header = () => {
);
const onScroll = useCallback(
(data) => {
(data: { x: number; y: number }) => {
if (data.y > 150) {
if (!hide) return;
setHide(false);

View File

@@ -43,12 +43,6 @@ export const LeftMenus = () => {
return;
}
Navigation.goBack();
if (
useNavigationStore.getState().currentScreen.name === "Signup" ||
useNavigationStore.getState().currentScreen.name === "Login"
) {
tabBarRef.current.unlock();
}
};
return isTablet && !canGoBack ? null : (

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
//@ts-ignore
import Menu from "react-native-reanimated-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import Navigation from "../../services/navigation";
@@ -35,10 +36,13 @@ export const RightMenus = () => {
const { colors } = useThemeColors();
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const deviceMode = useSettingStore((state) => state.deviceMode);
const rightButtons = useNavigationStore((state) => state.headerRightButtons);
const buttons = useNavigationStore((state) => state.headerRightButtons);
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
const buttonAction = useNavigationStore((state) => state.buttonAction);
const menuRef = useRef();
const menuRef = useRef<{
show: () => void;
hide: () => void;
}>();
return (
<View style={styles.rightBtnContainer}>
@@ -46,9 +50,12 @@ export const RightMenus = () => {
<IconButton
onPress={async () => {
SearchService.prepareSearch();
Navigation.navigate({
name: "Search"
});
Navigation.navigate(
{
name: "Search"
},
{}
);
}}
testID="icon-search"
name="magnify"
@@ -82,7 +89,7 @@ export const RightMenus = () => {
/>
) : null}
{rightButtons && rightButtons.length > 0 ? (
{buttons && buttons.length > 0 ? (
<Menu
ref={menuRef}
animationDuration={200}
@@ -104,7 +111,7 @@ export const RightMenus = () => {
/>
}
>
{rightButtons.map((item) => (
{buttons.map((item) => (
<Button
style={{
width: 150,

View File

@@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native";
import { db } from "../../common/database";
import Notebook from "../../screens/notebook";
import {
@@ -26,14 +27,12 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading";
import { useCallback } from "react";
import Tag from "../ui/tag";
import Heading from "../ui/typography/heading";
const titleState = {};
const titleState: { [name: string]: boolean } = {};
export const Title = () => {
const { colors } = useThemeColors();
@@ -42,12 +41,12 @@ export const Title = () => {
const isTopic = currentScreen?.name === "TopicNotes";
const [hide, setHide] = useState(
isNotebook
? typeof titleState[currentScreen.id] === "boolean"
? titleState[currentScreen.id]
? typeof titleState[currentScreen.id as string] === "boolean"
? titleState[currentScreen.id as string]
: true
: false
);
const isHidden = titleState[currentScreen.id];
const isHidden = titleState[currentScreen.id as string];
const notebook =
isTopic && currentScreen.notebookId
? db.notebooks?.notebook(currentScreen.notebookId)?.data
@@ -56,18 +55,18 @@ export const Title = () => {
const isTag = currentScreen?.name === "TaggedNotes";
const onScroll = useCallback(
(data) => {
(data: { x: number; y: number }) => {
if (currentScreen.name !== "Notebook") {
setHide(false);
return;
}
if (data.y > 150) {
if (!hide) return;
titleState[currentScreen.id] = false;
titleState[currentScreen.id as string] = false;
setHide(false);
} else {
if (hide) return;
titleState[currentScreen.id] = true;
titleState[currentScreen.id as string] = true;
setHide(true);
}
},
@@ -76,13 +75,13 @@ export const Title = () => {
useEffect(() => {
if (currentScreen.name === "Notebook") {
let value =
typeof titleState[currentScreen.id] === "boolean"
? titleState[currentScreen.id]
const value =
typeof titleState[currentScreen.id as string] === "boolean"
? titleState[currentScreen.id as string]
: true;
setHide(value);
} else {
setHide(titleState[currentScreen.id]);
setHide(titleState[currentScreen.id as string]);
}
}, [currentScreen.id, currentScreen.name]);
@@ -98,14 +97,7 @@ export const Title = () => {
Notebook.navigate(notebook, true);
}
return (
<View
style={{
opacity: 1,
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
}}
>
<>
{!hide && !isHidden ? (
<Heading
onPress={navigateToNotebook}
@@ -122,10 +114,17 @@ export const Title = () => {
#
</Heading>
) : null}
{title}
{title}{" "}
<Tag
visible={currentScreen.beta}
text="BETA"
style={{
backgroundColor: "transparent"
}}
textColor={colors.primary.accent}
/>
</Heading>
) : null}
<Tag visible={currentScreen.beta} text="BETA" />
</View>
</>
);
};

View File

@@ -17,35 +17,25 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { useSettingStore } from "../../../stores/use-setting-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
presentSheet
} from "../../../services/event-manager";
import React, { useRef } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { GROUP } from "../../../utils/constants";
import { useSettingStore } from "../../../stores/use-setting-store";
import { ColorValues } from "../../../utils/colors";
import { db } from "../../../common/database";
import { GROUP } from "../../../utils/constants";
import { eOpenJumpToDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
import { Button } from "../../ui/button";
import Sort from "../../sheets/sort";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import { useCallback } from "react";
export const SectionHeader = React.memo(
function SectionHeader({ item, index, type, color, screen }) {
function SectionHeader({ item, index, type, color, screen, groupOptions }) {
const { colors } = useThemeColors();
const { fontScale } = useWindowDimensions();
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions(type)
);
let groupBy = Object.keys(GROUP).find(
(key) => GROUP[key] === groupOptions.groupBy
);
@@ -65,17 +55,6 @@ export const SectionHeader = React.memo(
? "Default"
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
const onUpdate = useCallback(() => {
setGroupOptions({ ...db.settings?.getGroupOptions(type) });
}, [type]);
useEffect(() => {
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, [onUpdate]);
return (
<View
style={{

View File

@@ -40,6 +40,7 @@ import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useEditorStore } from "../../../stores/use-editor-store";
function navigateToTag(item) {
const tag = db.tags.tag(item.id);
@@ -98,6 +99,9 @@ const NoteItem = ({
dateBy = "dateCreated",
noOpen = false
}) => {
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(item);
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
@@ -108,6 +112,9 @@ const NoteItem = ({
const reminder = getUpcomingReminder(reminders);
const noteColor = ColorValues[item.color?.toLowerCase()];
const tags = getTags(item);
const primaryColors =
currentEditingNote === item.id ? colors.selected : colors.primary;
return (
<>
<View
@@ -148,7 +155,7 @@ const NoteItem = ({
borderRadius: 5,
marginRight: 5,
borderWidth: 0.5,
borderColor: colors.primary.border,
borderColor: primaryColors.border,
paddingHorizontal: 6,
marginBottom: 5
}}
@@ -179,7 +186,7 @@ const NoteItem = ({
<Paragraph
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || colors.primary.heading
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
}
style={{
flexWrap: "wrap"
@@ -192,7 +199,7 @@ const NoteItem = ({
<Heading
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || colors.primary.heading
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
}
style={{
flexWrap: "wrap"
@@ -208,6 +215,7 @@ const NoteItem = ({
style={{
flexWrap: "wrap"
}}
color={primaryColors.paragraph}
numberOfLines={2}
>
{decode(item.headline, {
@@ -262,7 +270,7 @@ const NoteItem = ({
<Icon
name="attachment"
size={SIZE.md}
color={colors.primary.icon}
color={primaryColors.icon}
/>
<Paragraph
color={colors.secondary.paragraph}
@@ -283,7 +291,7 @@ const NoteItem = ({
}}
color={
ColorValues[item.color?.toLowerCase()] ||
colors.primary.accent
primaryColors.accent
}
/>
) : null}
@@ -296,7 +304,7 @@ const NoteItem = ({
style={{
marginRight: 6
}}
color={colors.primary.icon}
color={primaryColors.icon}
/>
) : null}
@@ -354,7 +362,7 @@ const NoteItem = ({
</Paragraph>
<Paragraph
color={colors.primary.accent}
color={primaryColors.accent}
size={SIZE.xs}
style={{
marginRight: 6
@@ -424,7 +432,7 @@ const NoteItem = ({
<IconButton
testID={notesnook.listitem.menu}
color={colors.primary.paragraph}
color={primaryColors.paragraph}
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}

View File

@@ -77,7 +77,7 @@ export const openNotebookTopic = (item) => {
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
heading: "Permanently deleted items",
type: "success",
context: "local"
});

View File

@@ -43,6 +43,9 @@ const SelectionWrapper = ({
const onLongPress = () => {
if (!useSelectionStore.getState().selectionMode) {
useSelectionStore.setState({
selectedItemsList: []
});
useSelectionStore.getState().setSelectionMode(true);
}
useSelectionStore.getState().setSelectedItem(item);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import { Dimensions, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
@@ -32,6 +32,7 @@ export const Card = ({ color, warning }) => {
color = color ? color : colors.primary.accent;
const messageBoardState = useMessageStore((state) => state.message);
const announcement = useMessageStore((state) => state.announcement);
const fontScale = Dimensions.get("window").fontScale;
return !messageBoardState.visible || announcement || warning ? null : (
<View
@@ -44,7 +45,6 @@ export const Card = ({ color, warning }) => {
type="gray"
customStyle={{
paddingVertical: 12,
width: "95%",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
@@ -54,17 +54,18 @@ export const Card = ({ color, warning }) => {
<View
style={{
flexDirection: "row",
alignItems: "center"
alignItems: "center",
flexShrink: 1
}}
>
<View
style={{
width: 40,
width: 40 * fontScale,
backgroundColor:
messageBoardState.type === "error"
? hexToRGBA(colors.static.red, 0.15)
: hexToRGBA(color, 0.15),
height: 40,
height: 40 * fontScale,
borderRadius: 100,
alignItems: "center",
justifyContent: "center"
@@ -75,6 +76,7 @@ export const Card = ({ color, warning }) => {
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
allowFontScaling
name={messageBoardState.icon}
/>
</View>
@@ -91,7 +93,7 @@ export const Card = ({ color, warning }) => {
</Paragraph>
<Paragraph
style={{
flexWrap: "wrap",
flexWrap: "nowrap",
flexShrink: 1
}}
color={colors.primary.heading}
@@ -101,22 +103,24 @@ export const Card = ({ color, warning }) => {
</View>
</View>
<View
style={{
width: 40,
height: 40,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon
name="chevron-right"
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
size={SIZE.lg}
/>
</View>
{fontScale > 1 ? null : (
<View
style={{
width: 40,
height: 40,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon
name="chevron-right"
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
size={SIZE.lg}
/>
</View>
)}
</PressableButton>
</View>
);

View File

@@ -39,6 +39,7 @@ import { Empty } from "./empty";
import { getTotalNotes } from "@notesnook/common";
import { useSettingStore } from "../../stores/use-setting-store";
import ReminderItem from "../list-items/reminder";
import { useGroupOptions } from "../../hooks/use-group-options";
const renderItems = {
note: NoteWrapper,
@@ -53,15 +54,10 @@ const renderItems = {
const RenderItem = ({ item, index, type, ...restArgs }) => {
if (!item) return <View />;
const Item = renderItems[item.itemType || item.type] || View;
const groupOptions = db.settings?.getGroupOptions(type);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const totalNotes = getTotalNotes(item);
return (
<Item
item={item}
dateBy={dateBy}
index={index}
type={type}
totalNotes={totalNotes}
@@ -103,6 +99,13 @@ const List = ({
(type === "notes" && notesListMode === "compact") ||
type === "notebooks" ||
notebooksListMode === "compact";
const groupType =
screen === "Home" ? "home" : screen === "Favorites" ? "favorites" : type;
const groupOptions = useGroupOptions(groupType);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const renderItem = React.useCallback(
({ item, index }) => (
@@ -111,18 +114,22 @@ const List = ({
index={index}
color={headerProps?.color}
title={headerProps?.heading}
type={
screen === "Notes"
? "home"
: screen === "Favorites"
? "favorites"
: type
}
dateBy={dateBy}
type={groupType}
screen={screen}
isSheet={isSheet}
groupOptions={groupOptions}
/>
),
[headerProps?.color, headerProps?.heading, screen, type, isSheet]
[
headerProps?.color,
headerProps?.heading,
screen,
isSheet,
dateBy,
groupType,
groupOptions
]
);
const _onRefresh = async () => {
@@ -228,7 +235,7 @@ const List = ({
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === "Notes" ? "home" : type}
type={screen === "Home" ? "home" : type}
scrollRef={scrollRef}
/>
) : null}

View File

@@ -245,13 +245,17 @@ const MergeConflicts = () => {
};
return !visible ? null : (
<Modal
<BaseDialog
statusBarTranslucent
transparent={false}
animationType="slide"
animated={false}
bounce={false}
onRequestClose={() => {
close();
}}
centered={false}
background={colors?.primary.background}
supportedOrientations={[
"portrait",
"portrait-upside-down",
@@ -363,7 +367,7 @@ const MergeConflicts = () => {
</Animated.View>
</View>
</SafeAreaView>
</Modal>
</BaseDialog>
);
};

View File

@@ -84,7 +84,7 @@ export default function NotePreview({ session, content, note }) {
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
heading: "Permanently deleted items",
type: "success",
context: "local"
});

View File

@@ -56,7 +56,7 @@ export const CompactFeatures = ({
icon: "shield"
},
{
highlight: "Daily, weekly & montly",
highlight: "Daily, weekly & monthly",
content: "recurring reminders",
icon: "bell"
},

View File

@@ -98,7 +98,7 @@ export const features = [
icon: "bell"
},
{
highlight: "Daily, weekly & montly",
highlight: "Daily, weekly & monthly",
content: "reminders",
icon: "refresh",
pro: true
@@ -194,7 +194,7 @@ export const features = [
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
features: [
{
highlight: "Basic formating",
highlight: "Basic formatting",
content: "and lists",
icon: "format-bold"
},

View File

@@ -500,7 +500,7 @@ export const PricingPlans = ({
textAlign: "center"
}}
>
By subscribing, your will be charged on your Google Account, and
By subscribing, you will be charged on your Google Account, and
your subscription will automatically renew until you cancel prior
to the end of the then current period.
</Paragraph>

View File

@@ -16,8 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Platform, View } from "react-native";
import React, { useRef } from "react";
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
@@ -54,7 +54,6 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
const { colors } = useThemeColors();
const alias = item.alias || item.title;
const isColor = !!ColorValues[item.title];
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
@@ -62,6 +61,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
</Paragraph>
);
}
return (
<FlatList
keyboardShouldPersistTaps="always"
@@ -135,6 +135,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
}, 1000);
}}
/>
<Synced item={item} close={close} />
{DDS.isTab ? (

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { FlatList, ScrollView, View } from "react-native";
import { Dimensions, FlatList, ScrollView, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useActions } from "../../hooks/use-actions";
import { DDS } from "../../services/device-detection";
@@ -33,9 +33,11 @@ export const Items = ({ item, buttons, close }) => {
const dimensions = useSettingStore((state) => state.dimensions);
const actions = useActions({ item, close });
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
let width = dimensions.width > 600 ? 600 : dimensions.width;
let columnItemsCount = DDS.isLargeTablet() ? 7 : 5;
const shouldShrink =
Dimensions.get("window").fontScale > 1 &&
Dimensions.get("window").width < 450;
let columnItemsCount = DDS.isLargeTablet() ? 7 : shouldShrink ? 4 : 5;
let columnItemWidth = DDS.isTab
? (width - 12) / columnItemsCount
: (width - 12) / columnItemsCount;
@@ -66,6 +68,7 @@ export const Items = ({ item, buttons, close }) => {
}}
>
<Icon
allowFontScaling
name={item.icon}
size={DDS.isTab ? SIZE.xxl : SIZE.lg}
color={
@@ -78,7 +81,11 @@ export const Items = ({ item, buttons, close }) => {
/>
</PressableButton>
<Paragraph size={SIZE.xs} style={{ textAlign: "center" }}>
<Paragraph
size={SIZE.xs}
textBreakStrategy="simple"
style={{ textAlign: "center" }}
>
{item.title}
</Paragraph>
</View>
@@ -139,6 +146,7 @@ export const Items = ({ item, buttons, close }) => {
>
<Icon
name={item.icon}
allowFontScaling
size={DDS.isTab ? SIZE.xxl : SIZE.md + 4}
color={
item.on
@@ -150,7 +158,11 @@ export const Items = ({ item, buttons, close }) => {
/>
</PressableButton>
<Paragraph size={SIZE.xxs + 1} style={{ textAlign: "center" }}>
<Paragraph
textBreakStrategy="simple"
size={SIZE.xxs + 1}
style={{ textAlign: "center" }}
>
{item.title}
</Paragraph>
</PressableButton>
@@ -163,9 +175,12 @@ export const Items = ({ item, buttons, close }) => {
"copy",
"share",
"export",
"lock-unlock",
"publish"
"lock-unlock"
];
if (!shouldShrink) {
topBarItemsList.push("publish");
}
const topBarItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) > -1
);

View File

@@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View, useWindowDimensions } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useUserStore } from "../../stores/use-user-store";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
@@ -33,6 +33,8 @@ export const Synced = ({ item, close }) => {
const user = useUserStore((state) => state.user);
const lastSynced = useUserStore((state) => state.lastSynced);
const dimensions = useWindowDimensions();
const shouldShrink = dimensions.fontScale > 1 && dimensions.width < 450;
return user && lastSynced >= item.dateModified ? (
<View
style={{
@@ -49,7 +51,11 @@ export const Synced = ({ item, close }) => {
borderTopColor: colors.secondary.background
}}
>
<Icon name="shield-key-outline" color={colors.primary.accent} size={SIZE.xxxl} />
<Icon
name="shield-key-outline"
color={colors.primary.accent}
size={shouldShrink ? SIZE.xxl : SIZE.xxxl}
/>
<View
style={{
@@ -67,15 +73,17 @@ export const Synced = ({ item, close }) => {
>
Encrypted and synced
</Heading>
<Paragraph
style={{
flexWrap: "wrap"
}}
size={SIZE.xs}
color={colors.primary.paragraph}
>
No one can view this {item.itemType || item.type} except you.
</Paragraph>
{shouldShrink ? null : (
<Paragraph
style={{
flexWrap: "wrap"
}}
size={SIZE.xs}
color={colors.primary.paragraph}
>
No one can view this {item.itemType || item.type} except you.
</Paragraph>
)}
</View>
<Button
@@ -91,8 +99,8 @@ export const Synced = ({ item, close }) => {
console.error(e);
}
}}
fontSize={SIZE.xs}
title="Learn more"
fontSize={SIZE.xs}
height={30}
type="grayAccent"
/>

View File

@@ -207,7 +207,6 @@ export const SelectionHeader = React.memo(() => {
{screen === "Trash" ||
screen === "Notebooks" ||
screen === "Notebook" ||
screen === "Reminders" ? null : (
<>
<IconButton
@@ -224,6 +223,21 @@ export const SelectionHeader = React.memo(() => {
name="pound"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
customStyle={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
//setSelectionMode(false);
@@ -239,20 +253,6 @@ export const SelectionHeader = React.memo(() => {
name="plus"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
customStyle={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
</>
)}
@@ -317,23 +317,8 @@ export const SelectionHeader = React.memo(() => {
customStyle={{
marginLeft: 10
}}
onPress={async () => {
presentDialog({
title: `Delete ${
selectedItemsList.length > 1 ? "items" : "item"
}`,
paragraph: `Are you sure you want to delete ${
selectedItemsList.length > 1 ? "these items?" : "this item?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: () => {
deleteItems();
},
positiveType: "errorShade"
});
return;
onPress={() => {
deleteItems();
}}
tooltipText="Move to trash"
tooltipPosition={1}

View File

@@ -198,7 +198,6 @@ const SheetProvider = ({ context = "global" }) => {
key={data.actionText}
title={data.actionText}
accentColor={data.iconColor}
accentText="light"
type="accent"
height={40}
width={250}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef, useState } from "react";
import { FlatList } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { ListHeaderInputItem } from "./list-header-item.js";
@@ -54,7 +54,7 @@ export const FilteredList = ({
}, [data, onChangeText]);
return (
<FlatList
<FlashList
{...restProps}
data={filtered}
ref={listRef}

View File

@@ -161,9 +161,6 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
: "deselected";
if (itemState[notebook.id] === "selected") {
count++;
contextValue.select(notebook);
} else {
contextValue.deselect(notebook);
}
for (let topic of notebook.topics) {
itemState[topic.id] = state
@@ -176,9 +173,6 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
: "deselected";
if (itemState[topic.id] === "selected") {
count++;
contextValue.select(topic);
} else {
contextValue.deselect(topic);
}
}
}
@@ -189,7 +183,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
useItemSelectionStore.getState().setItemState(itemState);
},
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
[getSelectedNotesCountInItem, selectedItemsList]
);
const getItemsForItem = (item) => {
@@ -214,7 +208,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
const updateItemState = useCallback(function (item, state) {
const itemState = useItemSelectionStore.getState().itemState;
const itemState = { ...useItemSelectionStore.getState().itemState };
const mergeState = {
[item.id]: state
};
@@ -370,56 +364,93 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
</View>
<SelectionProvider value={contextValue}>
<FilteredList
<View
style={{
paddingHorizontal: 12,
maxHeight: dimensions.height * 0.85
maxHeight: dimensions.height * 0.85,
height: 50 * (notebooks.length + 2)
}}
ListEmptyComponent={
notebooks.length > 0 ? null : (
<View
style={{
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center"
>
<FilteredList
ListEmptyComponent={
notebooks.length > 0 ? null : (
<View
style={{
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<Icon
name="book-outline"
color={colors.primary.icon}
size={100}
/>
<Paragraph style={{ marginBottom: 10 }}>
You do not have any notebooks.
</Paragraph>
</View>
)
}
estimatedItemSize={50}
data={notebooks}
hasHeaderSearch={true}
renderItem={({ item, index }) => (
<ListItem
item={item}
key={item.id}
index={index}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
infoText={
<>
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
</>
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
hasNotes: getSelectedNotesCountInItem(topic) > 0,
style: {
marginBottom: 0,
height: 40
},
onPress: (item) => {
const itemState =
useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
if (currentState !== "selected") {
resetItemState("deselected");
contextValue.select(item);
} else {
contextValue.deselect(item);
}
},
key: item.id,
type: "transparent"
})}
icon={(expanded) => ({
name: expanded ? "chevron-up" : "chevron-down",
color: expanded
? colors.primary.accent
: colors.primary.paragraph
})}
onScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
>
<Icon
name="book-outline"
color={colors.primary.icon}
size={100}
/>
<Paragraph style={{ marginBottom: 10 }}>
You do not have any notebooks.
</Paragraph>
</View>
)
}
data={notebooks}
hasHeaderSearch={true}
renderItem={({ item, index }) => (
<ListItem
item={item}
key={item.id}
index={index}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
infoText={
<>
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
</>
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
hasNotes: getSelectedNotesCountInItem(topic) > 0,
style: {
marginBottom: 0,
height: 40
},
onPress: (item) => {
hasSubList={true}
hasHeaderSearch={false}
type="grayBg"
sublistItemType="topic"
onAddItem={(title) => {
return onAddTopic(title, item);
}}
onAddSublistItem={(item) => {
openAddTopicDialog(item);
}}
onPress={(item) => {
const itemState =
useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
@@ -429,47 +460,16 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
} else {
contextValue.deselect(item);
}
},
key: item.id,
type: "transparent"
})}
icon={(expanded) => ({
name: expanded ? "chevron-up" : "chevron-down",
color: expanded
? colors.primary.accent
: colors.primary.paragraph
})}
onScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
hasSubList={true}
hasHeaderSearch={false}
type="grayBg"
sublistItemType="topic"
onAddItem={(title) => {
return onAddTopic(title, item);
}}
onAddSublistItem={(item) => {
openAddTopicDialog(item);
}}
onPress={(item) => {
const itemState = useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
if (currentState !== "selected") {
resetItemState("deselected");
contextValue.select(item);
} else {
contextValue.deselect(item);
}
}}
/>
)}
itemType="notebook"
onAddItem={async (title) => {
return await onAddNotebook(title);
}}
ListFooterComponent={<View style={{ height: 20 }} />}
/>
}}
/>
)}
itemType="notebook"
onAddItem={async (title) => {
return await onAddNotebook(title);
}}
ListFooterComponent={<View style={{ height: 20 }} />}
/>
</View>
</SelectionProvider>
</View>
</>

View File

@@ -273,6 +273,7 @@ export const ListItem = ({
alignSelf: "flex-end",
maxHeight: 250
}}
estimatedItemSize={40}
itemType={sublistItemType}
hasHeaderSearch={hasHeaderSearch}
renderItem={({ item, index }) => (

View File

@@ -35,10 +35,14 @@ import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { useStoredRef } from "../../../hooks/use-stored-ref";
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
const { colors } = useThemeColors();
const body = useRef(defaultBody);
const title = useRef(defaultTitle);
const body = useStoredRef("issueBody", defaultBody);
const title = useStoredRef("issueTitle", defaultTitle);
const user = useUserStore((state) => state.user);
const [loading, setLoading] = useState(false);
const bodyRef = useRef();
@@ -70,6 +74,8 @@ Logged in: ${user ? "yes" : "no"}`,
});
setLoading(false);
eSendEvent(eCloseSheet);
body.reset();
title.reset();
await sleep(300);
presentDialog({
title: "Issue reported",
@@ -105,7 +111,7 @@ Logged in: ${user ? "yes" : "no"}`,
} catch (e) {
setLoading(false);
ToastEvent.show({
heading: "An error occured",
heading: "An error occurred",
message: e.message,
type: "error"
});

View File

@@ -177,7 +177,7 @@ export default function Migrate() {
textAlign: "center"
}}
>
An error occured while migrating your data. You can logout of your
An error occurred while migrating your data. You can logout of your
account and try to relogin. However this is not recommended as it
may result in some data loss if your data was not synced.
</Paragraph>

View File

@@ -64,7 +64,7 @@ export const Progress = () => {
width={null}
animated={true}
useNativeDriver
progress={currentProgress || 0.1}
indeterminate
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
@@ -74,7 +74,7 @@ export const Progress = () => {
{progress ? (
<Paragraph color={colors.secondary.paragraph}>
{progress.type?.slice(0, 1).toUpperCase() + progress.type?.slice(1)}
ing {progress?.current}/{progress?.total}
ing {progress?.current}
</Paragraph>
) : null}
</View>

View File

@@ -40,7 +40,9 @@ import { requestInAppReview } from "../../../services/app-review";
const PublishNoteSheet = ({ note: item, update }) => {
const { colors } = useThemeColors();
const actionSheetRef = useRef();
const loading = useAttachmentStore((state) => state.loading);
const attachmentDownloads = useAttachmentStore((state) => state.downloading);
const downloading = attachmentDownloads[`monograph-${item.id}`];
const [selfDestruct, setSelfDestruct] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const [note, setNote] = useState(item);
@@ -137,9 +139,9 @@ const PublishNoteSheet = ({ note: item, update }) => {
}}
>
Please wait...
{loading && loading.current && loading.total
{downloading && downloading.current && downloading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
downloading?.current / downloading?.total
})`
: ""}
</Paragraph>

View File

@@ -253,6 +253,57 @@ export default function ReminderSheet({
marginBottom: DDS.isTab ? 25 : undefined
}}
>
{reminderMode === ReminderModes.Permanent ? null : (
<ScrollView
style={{
flexDirection: "row",
borderWidth: 1,
marginTop: 12,
borderRadius: 5,
borderColor: colors.primary.border,
paddingLeft: 12,
height: 50
}}
horizontal
>
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={mode}
style={{
marginRight: 12,
borderRadius: 100
}}
icon={
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
}
height={35}
type={
reminderNotificationMode ===
ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
]
? "grayAccent"
: "gray"
}
onPress={() => {
const _mode = ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
] as Reminder["priority"];
SettingsService.set({
reminderNotificationMode: _mode
});
setReminderNotificatioMode(_mode);
}}
/>
))}
</ScrollView>
)}
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
@@ -280,15 +331,21 @@ export default function ReminderSheet({
}}
height={80}
wrapperStyle={{
marginBottom: 20
marginBottom: 12
}}
/>
<View
<ScrollView
style={{
flexDirection: "row",
marginBottom: 12
marginBottom: 12,
height: 50,
borderWidth: 1,
borderRadius: 5,
borderColor: colors.primary.border,
paddingLeft: 12
}}
horizontal
>
{Object.keys(ReminderModes).map((mode) => (
<Button
@@ -327,7 +384,7 @@ export default function ReminderSheet({
}}
/>
))}
</View>
</ScrollView>
{reminderMode === ReminderModes.Repeat ? (
<View
@@ -474,9 +531,7 @@ export default function ReminderSheet({
androidVariant="nativeAndroid"
is24hourSource="locale"
locale={
db.settings?.getTimeFormat() === "24-hour"
? "en_GB.UTF8"
: "en_US.UTF8"
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
}
mode={reminderMode === ReminderModes.Repeat ? "time" : "datetime"}
/>
@@ -498,53 +553,6 @@ export default function ReminderSheet({
</View>
)}
{reminderMode === ReminderModes.Permanent ? null : (
<View
style={{
flexDirection: "row",
marginBottom: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: colors.secondary.background
}}
>
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={mode}
style={{
marginRight: 12,
borderRadius: 100
}}
icon={
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
}
height={35}
type={
reminderNotificationMode ===
ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
]
? "grayAccent"
: "gray"
}
onPress={() => {
const _mode = ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
] as Reminder["priority"];
SettingsService.set({
reminderNotificationMode: _mode
});
setReminderNotificatioMode(_mode);
}}
/>
))}
</View>
)}
{reminderMode === ReminderModes.Once ||
reminderMode === ReminderModes.Permanent ? null : (
<View
@@ -595,6 +603,11 @@ export default function ReminderSheet({
}}
/>
</ScrollView>
<View
style={{
height: 10
}}
/>
</View>
);
}

View File

@@ -22,7 +22,9 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import DocumentPicker, {
DocumentPickerResponse
} from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
@@ -45,6 +47,8 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { unzip } from "react-native-zip-archive";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
@@ -129,75 +133,71 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 300);
}, 1000);
}, []);
const restore = async (item) => {
if (restoring) {
return;
}
try {
setRestoring(true);
let prefix = Platform.OS === "ios" ? "" : "file:/";
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(item.uri, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(prefix + item.path, "utf8");
}
backup = JSON.parse(backup);
if (backup.data.iv && backup.data.salt) {
withPassword(
async (value) => {
try {
await restoreBackup(backup, value);
close();
setRestoring(false);
return true;
} catch (e) {
backupError(e);
return false;
}
},
() => {
setRestoring(false);
try {
const file = Platform.OS === "ios" ? item.path : item.uri;
console.log(file);
if (file.endsWith(".nnbackupz")) {
setRestoring(true);
if (Platform.OS === "android") {
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
}
);
} else {
await restoreBackup(backup);
close();
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
console.log("copying");
await copyFileAsync(file, cacheFile);
console.log("copied");
await restoreFromZip(cacheFile);
} else {
await restoreFromZip(file, false);
}
} else if (file.endsWith(".nnbackup")) {
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(file, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(file, "utf8");
}
await restoreFromNNBackup(JSON.parse(backup));
}
} catch (e) {
console.log("error", e);
setRestoring(false);
backupError(e);
}
};
const withPassword = (onsubmit, onclose = () => {}) => {
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: onclose,
negativeText: "Cancel",
positivePress: async (password) => {
try {
return await onsubmit(password);
} catch (e) {
ToastEvent.show({
heading: "Failed to backup data",
message: e.message,
type: "error",
context: "global"
});
return false;
const withPassword = () => {
return new Promise((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password) => {
resolve(password);
resolved = true;
return true;
}
}
});
});
};
@@ -217,21 +217,164 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
files = files
.filter((file) => {
const name = Platform.OS === "android" ? file.name : file.filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
setTimeout(() => {
setLoading(false);
}, 1000);
setLoading(false);
} catch (e) {
console.log(e);
setLoading(false);
}
};
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
await db.initCollections();
initialize();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
return true;
};
const backupError = (e) => {
ToastEvent.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
/**
*
* @param {string} file
*/
async function restoreFromZip(file, remove) {
try {
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
await unzip(file, zipOutputFolder);
console.log("Unzipped files successfully to", zipOutputFolder);
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
throw new Error("Backup file is invalid");
}
let password;
console.log(`Found ${backupFiles?.length} files to restore from backup`);
for (const path of backupFiles) {
if (path === ".nnbackup") continue;
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const parsed = JSON.parse(data);
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
password = await withPassword();
if (!password) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password);
console.log("Imported", path);
}
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (remove) {
RNFetchBlob.fs.unlink(file).catch(console.log);
}
await db.initCollections();
initialize();
setRestoring(false);
close();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
} catch (e) {
backupError(e);
setRestoring(false);
}
}
/**
*
* @param {string} file
*/
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const password = await withPassword();
if (password) {
try {
await restoreBackup(backup, password);
close();
setRestoring(false);
} catch (e) {
setRestoring(false);
backupError(e);
}
} else {
setRestoring(false);
}
} else {
await restoreBackup(backup);
setRestoring(false);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
}
const button = {
title: "Restore from files",
onPress: async () => {
if (restoring) {
return;
}
try {
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
if (file.name.endsWith(".nnbackupz")) {
setRestoring(true);
await restoreFromZip(file.fileCopyUri, true);
} else if (file.name.endsWith(".nnbackup")) {
RNFetchBlob.fs.unlink(file.fileCopyUri).catch(console.log);
setRestoring(true);
const data = await fetch(file.uri);
await restoreFromNNBackup(await data.json());
}
} catch (e) {
console.log("error", e.stack);
setRestoring(false);
backupError(e);
}
}
};
const renderItem = ({ item, index }) => (
<View
style={{
@@ -271,76 +414,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View>
);
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
setRestoring(false);
initialize();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
};
const backupError = (e) => {
ToastEvent.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
const button = {
title: "Restore from files",
onPress: () => {
if (restoring) {
return;
}
DocumentPicker.pickSingle()
.then((r) => {
setRestoring(true);
fetch(r.uri)
.then(async (r) => {
try {
let backup = await r.json();
if (backup.data.iv && backup.data.salt) {
withPassword(
async (value) => {
try {
restoreBackup(backup, value).then(() => {
close();
setRestoring(false);
});
return true;
} catch (e) {
backupError(e);
setRestoring(false);
return false;
}
},
() => {
setRestoring(false);
}
);
} else {
await restoreBackup(backup);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
})
.catch(console.log);
})
.catch(console.log);
}
};
return (
<>
<View>

View File

@@ -16,6 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import qclone from "qclone";
import React, {
createContext,
@@ -26,7 +27,7 @@ import React, {
useRef,
useState
} from "react";
import { RefreshControl, View } from "react-native";
import { RefreshControl, useWindowDimensions, View } from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
@@ -45,7 +46,6 @@ import {
import useNavigationStore, {
NotebookScreenParams
} from "../../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import {
eOnNewTopicAdded,
eOnTopicSheetUpdate,
@@ -54,18 +54,17 @@ import {
import { normalize, SIZE } from "../../../utils/size";
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
import { getTotalNotes } from "@notesnook/common";
import { groupArray } from "@notesnook/core/dist/utils/grouping";
import Config from "react-native-config";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { MMKV } from "../../../common/database/mmkv";
import { openEditor } from "../../../screens/notes/common";
import { getTotalNotes } from "@notesnook/common";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Properties } from "../../properties";
import Sort from "../sort";
import { useSelectionStore } from "../../../stores/use-selection-store";
type ConfigItem = { id: string; type: string };
class TopicSheetConfig {
@@ -108,7 +107,8 @@ export const TopicsSheet = () => {
)
: []
);
const currentItem = useRef<string>();
const { fontScale } = useWindowDimensions();
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions("topics")
);
@@ -206,6 +206,11 @@ export const TopicsSheet = () => {
if (canShow) {
setTimeout(() => {
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
if (currentItem.current !== id) {
setSelection([]);
setEnabled(false);
}
currentItem.current = id;
const notebook = db.notebooks?.notebook(id as string)?.data;
const snapPoint = isTopic
? 0
@@ -226,6 +231,8 @@ export const TopicsSheet = () => {
}
}, 300);
} else {
setSelection([]);
setEnabled(false);
ref.current?.hide();
}
}, [
@@ -271,7 +278,7 @@ export const TopicsSheet = () => {
backgroundColor: colors.secondary.background
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [25, 100]}
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
initialSnapIndex={1}
backgroundInteractionEnabled
gestureEnabled
@@ -305,8 +312,8 @@ export const TopicsSheet = () => {
</View>
<View
style={{
maxHeight: 300,
height: 300,
maxHeight: 450,
height: 450,
width: "100%"
}}
>
@@ -329,30 +336,19 @@ export const TopicsSheet = () => {
{enabled ? (
<IconButton
customStyle={{
marginLeft: 10
marginLeft: 10,
width: 40 * fontScale,
height: 40 * fontScale
}}
onPress={async () => {
//@ts-ignore
useSelectionStore.setState({
selectedItemsList: selection
});
presentDialog({
title: `Delete ${
selection.length > 1 ? "topics" : "topics"
}`,
paragraph: `Are you sure you want to delete ${
selection.length > 1 ? "these topics?" : "this topic?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
await deleteItems();
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
},
positiveType: "errorShade"
});
await deleteItems();
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
return;
}}
color={colors.primary.icon}
@@ -378,8 +374,8 @@ export const TopicsSheet = () => {
color={colors.primary.icon}
size={22}
customStyle={{
width: 40,
height: 40
width: 40 * fontScale,
height: 40 * fontScale
}}
/>
<IconButton
@@ -389,8 +385,8 @@ export const TopicsSheet = () => {
color={colors.primary.icon}
size={22}
customStyle={{
width: 40,
height: 40
width: 40 * fontScale,
height: 40 * fontScale
}}
/>
@@ -408,8 +404,8 @@ export const TopicsSheet = () => {
color={colors.primary.icon}
size={22}
customStyle={{
width: 40,
height: 40
width: 40 * fontScale,
height: 40 * fontScale
}}
/>
</>
@@ -483,6 +479,7 @@ const TopicItem = ({
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
const isFocused = screen.id === item.id;
const notesCount = getTotalNotes(item);
const { fontScale } = useWindowDimensions();
return (
<PressableButton
@@ -519,6 +516,10 @@ const TopicItem = ({
<IconButton
size={SIZE.lg}
color={isSelected ? colors.selected.icon : colors.primary.icon}
top={0}
left={0}
bottom={0}
right={0}
name={
isSelected
? "check-circle-outline"
@@ -538,8 +539,8 @@ const TopicItem = ({
<IconButton
name="dots-horizontal"
customStyle={{
width: 40,
height: 40
width: 40 * fontScale,
height: 40 * fontScale
}}
testID={notesnook.ids.notebook.menu}
onPress={() => {

View File

@@ -17,20 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Dimensions, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import { normalize, SIZE } from "../../utils/size";
import { SIZE, normalize } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import Tag from "../ui/tag";
export const MenuItem = React.memo(
function MenuItem({ item, index, testID, rightBtn }) {
@@ -40,6 +38,7 @@ export const MenuItem = React.memo(
);
const screenId = item.name.toLowerCase() + "_navigation";
let isFocused = headerTextState?.id === screenId;
const primaryColors = isFocused ? colors.selected : colors.primary;
const _onPress = () => {
if (item.func) {
@@ -110,6 +109,7 @@ export const MenuItem = React.memo(
textAlignVertical: "center",
textAlign: "left"
}}
allowFontScaling
name={item.icon}
color={
item.icon === "crown"
@@ -128,14 +128,28 @@ export const MenuItem = React.memo(
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
)}
<Tag visible={item.isBeta} text="BETA" />
{item.isBeta ? (
<View
style={{
borderRadius: 100,
backgroundColor: primaryColors.accent,
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
BETA
</Paragraph>
</View>
) : null}
</View>
{item.switch ? (
<ToggleSwitch
isOn={item.on}
onColor={colors.primary.accent}
offColor={colors.primary.icon}
onColor={primaryColors.accent}
offColor={primaryColors.icon}
size="small"
animationSpeed={150}
onToggle={_onPress}

View File

@@ -108,9 +108,12 @@ export const PinItem = React.memo(
alias = item?.alias || item?.title;
const [visible, setVisible] = useState(false);
const [headerTextState, setHeaderTextState] = useState(null);
const primaryColors =
headerTextState?.id === item.id ? colors.selected : colors.primary;
const color =
headerTextState?.id === item.id
? colors.primary.accent
? colors.selected.accent
: colors.primary.icon;
const fwdRef = useRef();
@@ -206,13 +209,19 @@ export const PinItem = React.memo(
justifyContent: "center"
}}
>
<Icon color={color} size={SIZE.lg - 2} name={icons[item.type]} />
<Icon
allowFontScaling
color={color}
size={SIZE.lg - 2}
name={icons[item.type]}
/>
<Icon
style={{
position: "absolute",
bottom: -6,
left: -6
}}
allowFontScaling
color={color}
size={SIZE.xs}
name="arrow-top-right-thick"
@@ -230,7 +239,7 @@ export const PinItem = React.memo(
style={{
flexWrap: "wrap"
}}
color={colors.primary.heading}
color={primaryColors.heading}
size={SIZE.md}
>
{alias}
@@ -238,7 +247,7 @@ export const PinItem = React.memo(
) : (
<Paragraph
numberOfLines={1}
color={colors.primary.paragraph}
color={primaryColors.paragraph}
size={SIZE.md}
>
{alias}

View File

@@ -113,6 +113,7 @@ export const UserStatus = () => {
<Icon
name="checkbox-blank-circle"
size={11}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
@@ -135,7 +136,7 @@ export const UserStatus = () => {
? "Last sync failed, tap to try again"
: syncing
? `Syncing your notes${
progress ? ` (${progress.current}/${progress.total})` : ""
progress ? ` (${progress.current})` : ""
}`
: "Tap here to sync your notes."}
</Paragraph>
@@ -149,9 +150,15 @@ export const UserStatus = () => {
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon color={colors.primary.accent} name="sync" size={SIZE.lg} />
<Icon
allowFontScaling
color={colors.primary.accent}
name="sync"
size={SIZE.lg}
/>
)
) : null}
</PressableButton>

View File

@@ -77,7 +77,9 @@ export const Tip = ({
alignSelf: "flex-start",
borderRadius: 100,
borderWidth: 1,
borderColor: colors.static[color as never] || colors.primary[color as never] as string
borderColor:
colors.static[color as never] ||
(colors.primary[color as never] as string)
}}
/>
@@ -139,7 +141,7 @@ export const Tip = ({
icon={tip.button.icon}
buttonType={{
color: colors.static[color as never],
text: colors.static.white
text: colors.primary.accentForeground
}}
style={{
marginTop: 10

View File

@@ -134,6 +134,7 @@ export const Button = ({
{icon && !loading && iconPosition === "left" ? (
<Icon
name={icon}
allowFontScaling
style={[{ marginRight: 0 }, iconStyle as any]}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
@@ -170,6 +171,7 @@ export const Button = ({
{icon && !loading && iconPosition === "right" ? (
<Icon
name={icon}
allowFontScaling
style={[{ marginLeft: 0 }, iconStyle as any]}
color={iconColor || buttonType?.text || textColor}
size={iconSize}

View File

@@ -40,7 +40,6 @@ interface IconButtonProps extends PressableButtonProps {
iconStyle?: TextStyle;
}
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
export const IconButton = ({
onPress,
name,
@@ -88,10 +87,10 @@ export const IconButton = ({
...customStyle
}}
>
<AnimatedIcon
layout={Layout}
<Icon
name={name}
style={iconStyle as any}
allowFontScaling
color={
restProps.disabled
? RGB_Linear_Shade(-0.05, hexToRGBA(colors.secondary.background))

View File

@@ -108,7 +108,7 @@ export const useButton = ({
},
accent: {
primary: accent || colors.primary.accent,
text: text || colors.primary.paragraph,
text: text || colors.primary.accentForeground,
selected: accent || colors.primary.accent
},
inverted: {

View File

@@ -17,15 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef } from "react";
import { Platform, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../../stores/use-setting-store";
import { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import { PremiumToast } from "../../premium/premium-toast";
import { Toast } from "../../toast";
import { BouncingView } from "../transitions/bouncing-view";
import { useAppState } from "../../../hooks/use-app-state";
import SettingsService from "../../../services/settings";
import { useUserStore } from "../../../stores/use-user-store";
const SheetWrapper = ({
children,
@@ -41,6 +43,7 @@ const SheetWrapper = ({
enableGesturesInScrollView = false,
bottomPadding = true
}) => {
const localRef = useRef(null);
const { colors } = useThemeColors("sheet");
const deviceMode = useSettingStore((state) => state.deviceMode);
const sheetKeyboardHandler = useSettingStore(
@@ -49,8 +52,9 @@ const SheetWrapper = ({
const largeTablet = deviceMode === "tablet";
const smallTablet = deviceMode === "smallTablet";
const dimensions = useSettingStore((state) => state.dimensions);
const pitchBlack = useSettingStore((state) => state.settings.pitchBlack);
const insets = useGlobalSafeAreaInsets();
const appState = useAppState();
const lockEvents = useRef(false);
let width = dimensions.width > 600 ? 600 : 500;
const style = React.useMemo(() => {
@@ -69,19 +73,40 @@ const SheetWrapper = ({
}, [colors.primary.background, largeTablet, smallTablet, width]);
const _onOpen = () => {
if (lockEvents.current) return;
onOpen && onOpen();
};
const _onClose = async () => {
if (lockEvents.current) return;
if (onClose) {
onClose();
}
};
useEffect(() => {
if (SettingsService.get().appLockMode === "background") {
if (appState === "background") {
const ref = fwdRef || localRef;
ref?.current?.hide();
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
ref?.current?.show();
unsub();
lockEvents.current = false;
}
});
}
}
}
}, [appState, fwdRef]);
return (
<ScopedThemeProvider value="sheet">
<ActionSheet
ref={fwdRef}
ref={fwdRef || localRef}
testIDs={{
backdrop: "sheet-backdrop"
}}
@@ -121,19 +146,17 @@ const SheetWrapper = ({
}
onClose={_onClose}
>
<BouncingView>
{children}
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
</BouncingView>
{children}
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
</ActionSheet>
</ScopedThemeProvider>
);

View File

@@ -16,9 +16,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { ViewStyle } from "react-native";
import { SIZE } from "../../../utils/size";
import Paragraph from "../typography/paragraph";
@@ -37,7 +37,7 @@ export default function Tag({
}) {
const { colors } = useThemeColors();
return !visible ? null : (
<View
<Paragraph
style={{
backgroundColor: background || colors.primary.accent,
borderRadius: 100,
@@ -46,13 +46,14 @@ export default function Tag({
marginLeft: 2,
marginTop: -10,
height: 20,
justifyContent: "center",
textAlignVertical: "center",
textAlign: "center",
...style
}}
color={textColor || colors.primary.accentForeground}
size={SIZE.xxs}
>
<Paragraph color={textColor || colors.static.white} size={SIZE.xxs}>
{text}
</Paragraph>
</View>
{text}
</Paragraph>
);
}

View File

@@ -59,7 +59,6 @@ const Heading = ({
<Component
layout={restProps.layout || Layout}
allowFontScaling={true}
maxFontSizeMultiplier={1}
{...restProps}
style={[
{

View File

@@ -45,8 +45,6 @@ const Paragraph = ({
return (
<Component
layout={restProps.layout || Layout}
allowFontScaling
maxFontSizeMultiplier={1}
{...restProps}
style={[
{

View File

@@ -70,8 +70,8 @@ const NotebookWelcome = () => {
count: 5
},
{
title: "Recipies",
description: "I love cooking and collecting recipies",
title: "Recipes",
description: "I love cooking and collecting recipes",
count: 10
}
]);
@@ -195,7 +195,7 @@ const notebooks: { id: string; steps: TStep[] } = {
>
<Paragraph size={SIZE.xs}>
<Icon color={colors.primary.icon} size={SIZE.sm} name="note" />{" "}
Feburary 2022 Week 2
February 2022 Week 2
</Paragraph>
</View>
<View
@@ -211,7 +211,7 @@ const notebooks: { id: string; steps: TStep[] } = {
>
<Paragraph size={SIZE.xs}>
<Icon color={colors.primary.icon} size={SIZE.sm} name="note" />{" "}
Feburary 2022 Week 1
February 2022 Week 1
</Paragraph>
</View>
<View
@@ -257,7 +257,7 @@ const notebooks: { id: string; steps: TStep[] } = {
title: "Tasks",
type: "topic"
}}
onPress={() => {}}
onPress={() => { }}
/>
<PinItem
@@ -267,7 +267,7 @@ const notebooks: { id: string; steps: TStep[] } = {
title: "Work and office",
type: "notebook"
}}
onPress={() => {}}
onPress={() => { }}
/>
</View>
),

View File

@@ -188,7 +188,7 @@ export const useActions = ({ close = () => null, item }) => {
Notifications.displayNotification({
title: item.title,
message: item.headline || text,
subtitle: item.headline || text,
subtitle: "",
bigText: html,
ongoing: true,
actions: ["UNPIN"],
@@ -520,7 +520,7 @@ export const useActions = ({ close = () => null, item }) => {
await sleep(300);
presentDialog({
title: "Permanent delete",
paragraph: `Are you sure you want to delete this ${item.itemType} permanantly from trash?`,
paragraph: `Are you sure you want to delete this ${item.itemType} permanently from trash?`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
@@ -529,7 +529,7 @@ export const useActions = ({ close = () => null, item }) => {
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
heading: "Permanently deleted items",
type: "success",
context: "local"
});
@@ -649,7 +649,7 @@ export const useActions = ({ close = () => null, item }) => {
},
{
id: "favorite",
title: item.favorite ? "Unfavorite" : "Favorite",
title: item.favorite ? "Unfav" : "Fav",
icon: item.favorite ? "star-off" : "star-outline",
func: addToFavorites,
close: false,

View File

@@ -83,7 +83,6 @@ import {
import { getGithubVersion } from "../utils/github-version";
import { tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time";
import { useThemeColors } from "@notesnook/theme";
const onCheckSyncStatus = async (type) => {
const { disableSync, disableAutoSync } = SettingsService.get();
@@ -101,23 +100,26 @@ const onSyncAborted = () => {
useUserStore.getState().setSyncing(false, SyncStatus.Failed);
};
const onSyncProgress = ({ type, total, current }) => {
if (type !== "download") return;
if (total < 10 || current % 10 === 0) {
initAfterSync();
}
};
const onFileEncryptionProgress = ({ total, progress }) => {
useAttachmentStore
.getState()
.setEncryptionProgress((progress / total).toFixed(2));
};
const onLoadingAttachmentProgress = (data) => {
useAttachmentStore
.getState()
.setLoading(data.total === data.current ? null : data);
const onDownloadingAttachmentProgress = (data) => {
useAttachmentStore.getState().setDownloading(data);
};
const onUploadingAttachmentProgress = (data) => {
useAttachmentStore.getState().setUploading(data);
};
const onDownloadedAttachmentProgress = (data) => {
useAttachmentStore.getState().setDownloading(data);
};
const onUploadedAttachmentProgress = (data) => {
useAttachmentStore.getState().setUploading(data);
};
const onUserSessionExpired = async () => {
@@ -167,13 +169,15 @@ const onUserSubscriptionStatusChanged = async (userStatus) => {
useMessageStore.getState().setAnnouncement();
};
const onRequestPartialSync = async (full, force) => {
const onRequestPartialSync = async (full, force, lastSyncTime) => {
if (SettingsService.get().disableAutoSync) return;
DatabaseLogger.info(`onRequestPartialSync full:${full}, force:${force}`);
DatabaseLogger.info(
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
);
if (full || force) {
await Sync.run("global", force, full);
await Sync.run("global", force, full, undefined, lastSyncTime);
} else {
await Sync.run("global", false, false);
await Sync.run("global", false, false, undefined, lastSyncTime);
}
};
@@ -253,7 +257,6 @@ export const useAppEvents = () => {
]);
const syncedOnLaunch = useRef(false);
const { isDark } = useThemeColors();
const refValues = useRef({
subsriptionSuccessListener: null,
subsriptionErrorListener: null,
@@ -278,7 +281,6 @@ export const useAppEvents = () => {
const eventManager = db?.eventManager;
eventSubscriptions = [
eventManager?.subscribe(EVENTS.syncCompleted, onSyncComplete),
eventManager?.subscribe(EVENTS.syncProgress, onSyncProgress),
eventManager?.subscribe(
EVENTS.databaseSyncRequested,
onRequestPartialSync
@@ -308,7 +310,16 @@ export const useAppEvents = () => {
EVENTS.userSubscriptionUpdated,
onUserSubscriptionStatusChanged
),
EV.subscribe(EVENTS.attachmentsLoading, onLoadingAttachmentProgress),
EV.subscribe(EVENTS.fileDownload, onDownloadingAttachmentProgress),
EV.subscribe(EVENTS.fileUpload, onUploadingAttachmentProgress),
EV.subscribe(EVENTS.fileDownloaded, onDownloadedAttachmentProgress),
EV.subscribe(EVENTS.fileUploaded, onUploadedAttachmentProgress),
EV.subscribe(EVENTS.downloadCanceled, (data) => {
useAttachmentStore.getState().setDownloading(data);
}),
EV.subscribe(EVENTS.uploadCanceled, (data) => {
useAttachmentStore.getState().setUploading(data);
}),
eSubscribeEvent(eUserLoggedIn, onUserUpdated)
];
@@ -456,7 +467,7 @@ export const useAppEvents = () => {
});
}
} catch (e) {
ToastEvent.error(e, "An error occured", "global");
ToastEvent.error(e, "An error occurred", "global");
}
user = await db.user.getUser();
@@ -533,6 +544,12 @@ export const useAppEvents = () => {
if (refValues.current?.isReconnecting || !refValues.current?.isUserReady)
return;
if (useSettingStore.getState().appDidEnterBackgroundForAction) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
console.log("AppDidEnterForegroundAfterAction");
return;
}
if (SettingsService.get().sessionExpired) {
refValues.current.isReconnecting = false;
return;

View File

@@ -0,0 +1,49 @@
/*
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 { useEffect, useState } from "react";
import { db } from "../common/database";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import Navigation from "../services/navigation";
export function useGroupOptions(type: any) {
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions(type)
);
useEffect(() => {
const onUpdate = () => {
const options = db.settings?.getGroupOptions(type) as any;
if (
groupOptions?.groupBy !== options.groupBy ||
groupOptions?.sortBy !== options.sortBy ||
groupOptions?.sortDirection !== groupOptions?.sortDirection
) {
setGroupOptions({ ...options });
Navigation.queueRoutesForUpdate();
}
};
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, [type, groupOptions]);
return groupOptions;
}

View File

@@ -0,0 +1,52 @@
/*
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 { useRef } from "react";
import { MMKV } from "../common/database/mmkv";
export function useStoredRef<T>(
key: string,
initialValue: T
): {
current: T;
reset(): void;
} {
const refKey = `storedRef:${key}`;
const value = useRef(
MMKV.getMap<{ current: T }>(refKey)?.current || initialValue
);
const frameRef = useRef(0);
return {
get current() {
return value.current;
},
set current(next: T) {
value.current = next;
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
current: value.current
});
});
},
reset() {
MMKV.removeItem(refKey);
}
};
}

View File

@@ -0,0 +1,49 @@
/*
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 { useRef, useState } from "react";
import { MMKV } from "../common/database/mmkv";
export function useStoredValue<T>(
key: string,
initialValue: T
): { value: T; reset(): void } {
const refKey = `storedState:${key}`;
const [value, setValue] = useState<T>(
MMKV.getMap<{ value: T }>(refKey)?.value || initialValue
);
const frameRef = useRef(0);
return {
get value() {
return value;
},
set value(next: T) {
setValue(next);
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
value: value
});
});
},
reset() {
MMKV.removeItem(refKey);
}
};
}

View File

@@ -384,7 +384,16 @@ const _TabsHolder = () => {
}, []);
useEffect(() => {
changeNavigationBarColor(colors.primary.background, isDark, true);
function updateSystemBars() {
changeNavigationBarColor(colors.primary.background, isDark, true);
StatusBar.setBackgroundColor("transparent");
StatusBar.setTranslucent(true);
StatusBar.setBarStyle(isDark ? "light-content" : "dark-content");
}
updateSystemBars();
setTimeout(() => {
updateSystemBars();
}, 1000);
}, [colors.primary.background, isDark]);
return (
@@ -406,11 +415,7 @@ const _TabsHolder = () => {
: 0
}}
>
<StatusBar
barStyle={isDark ? "light-content" : "dark-content"}
translucent={true}
backgroundColor="transparent"
/>
<StatusBar translucent={true} backgroundColor="transparent" />
{!introCompleted ? (
<NavigationStack />

View File

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

View File

@@ -167,7 +167,7 @@ const Editor = React.memo(
globalThis.noToolbar=${noToolbar};
globalThis.noHeader=${noHeader};
`}
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
useSharedProcessPool={false}
javaScriptEnabled={true}
focusable={true}
onContentProcessDidTerminate={onError}

View File

@@ -1,81 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
export const ProgressBar = () => {
const { colors } = useThemeColors();
const loading = useAttachmentStore((state) => state.loading);
const [prog, setProg] = useState(0);
const [visible, setVisible] = useState(false);
const timer = useRef();
const insets = useGlobalSafeAreaInsets();
const [width, setWidth] = useState(false);
useEffect(() => {
if (loading) {
if (loading.current !== loading.total) {
setVisible(true);
setProg(loading.current / loading.total);
} else {
clear();
}
} else {
clear();
}
}, [loading]);
const clear = () => {
clearTimeout(timer.current);
timer.current = null;
timer.current = setTimeout(() => {
setProg(1);
setTimeout(() => {
setVisible(false);
}, 1000);
}, 100);
};
return visible ? (
<View
style={{
justifyContent: "center",
position: "absolute",
zIndex: 1,
marginTop: insets.top + 45,
width: "100%"
}}
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}
>
<ProgressBarComponent
size={SIZE.xxl}
progress={prog}
color={colors.primary.accent}
borderWidth={0}
height={1}
width={width || 400}
/>
</View>
) : null;
};

View File

@@ -0,0 +1,136 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useEditorStore } from "../../stores/use-editor-store";
export const ProgressBar = () => {
const { colors } = useThemeColors();
const currentlyEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const downloading = useAttachmentStore((state) => state.downloading);
const loading = currentlyEditingNote
? downloading?.[currentlyEditingNote]
: undefined;
const attachmentProgress = useAttachmentStore((state) => state.progress);
const [progress, setProgress] = useState(0);
const [visible, setVisible] = useState(false);
const timer = useRef<NodeJS.Timeout>();
const insets = useGlobalSafeAreaInsets();
const [width, setWidth] = useState(400);
const groupProgressInfo = useRef<{
[name: string]: {
total: number;
current: number;
};
}>({});
const currentItemProgress = loading?.filename
? attachmentProgress?.[loading?.filename]
: undefined;
useEffect(() => {
if (loading) {
console.log(loading);
if (
loading.current === loading.total &&
typeof loading.success === "boolean"
) {
clear();
return;
}
setVisible(true);
if (!loading.filename) return;
if (!groupProgressInfo.current[loading.filename]) {
groupProgressInfo.current[loading.filename] = {
total: 1,
current: 0
};
}
const itemProgressCurrent = groupProgressInfo.current[loading.filename];
const itemTotalSize =
currentItemProgress?.total || itemProgressCurrent.total;
const itemCurrentProgress =
currentItemProgress?.recieved ||
currentItemProgress?.sent ||
itemProgressCurrent.current;
groupProgressInfo.current[loading.filename] = {
current: itemCurrentProgress,
total: itemTotalSize
};
const itemProgressPercent = itemCurrentProgress / itemTotalSize;
setProgress(
((loading.current || 0) + itemProgressPercent) / (loading.total || 1)
);
} else {
clear();
}
}, [currentItemProgress, loading]);
const clear = () => {
clearTimeout(timer.current);
timer.current = undefined;
timer.current = setTimeout(() => {
setProgress(1);
setTimeout(() => {
setVisible(false);
setProgress(0);
groupProgressInfo.current = {};
}, 1000);
}, 0);
};
return (
<View
style={{
justifyContent: "center",
position: "absolute",
zIndex: visible ? 1 : -1,
marginTop: insets.top + 45,
width: "100%"
}}
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}
>
<ProgressBarComponent
progress={progress}
color={colors.primary.accent}
borderWidth={0}
borderRadius={0}
height={2}
width={width || 400}
/>
</View>
);
};

View File

@@ -113,7 +113,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
};
setSessionId = async (id: string | null) =>
await this.doAsync(`globalThis.sessionId = "${id}"`);
await this.doAsync(`globalThis.sessionId = "${id}";`);
setStatus = async (date: string | undefined, saved: string) =>
await this.doAsync(

View File

@@ -34,6 +34,7 @@ import PremiumService from "../../../services/premium";
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
import { eCloseSheet } from "../../../utils/events";
import { editorController, editorState } from "./utils";
import { useSettingStore } from "../../../stores/use-setting-store";
const showEncryptionSheet = (file) => {
presentSheet({
@@ -62,6 +63,7 @@ const file = async (fileOptions) => {
let file;
try {
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
file = await DocumentPicker.pick(options);
} catch (e) {
return;
@@ -135,6 +137,7 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchCamera(
{
includeBase64: true,
@@ -156,6 +159,7 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchImageLibrary(
{
includeBase64: true,

View File

@@ -52,6 +52,7 @@ export type Settings = {
fontFamily: string;
dateFormat: string;
timeFormat: string;
fontScale: number;
};
export type EditorProps = {

View File

@@ -28,7 +28,8 @@ import {
InteractionManager,
Keyboard,
KeyboardEventListener,
NativeEventSubscription
NativeEventSubscription,
useWindowDimensions
} from "react-native";
import { WebViewMessageEvent } from "react-native-webview";
import { db } from "../../../common/database";
@@ -142,6 +143,8 @@ export const useEditorEvents = (
const handleBack = useRef<NativeEventSubscription>();
const readonly = useEditorStore((state) => state.readonly);
const isPremium = useUserStore((state) => state.premium);
const { fontScale } = useWindowDimensions();
const doubleSpacedLines = useSettingStore(
(state) => state.settings?.doubleSpacedLines
);
@@ -195,13 +198,13 @@ export const useEditorEvents = (
: defaultFontSize,
fontFamily: SettingsService.get().defaultFontFamily,
dateFormat: db.settings?.getDateFormat(),
timeFormat: db.settings?.getTimeFormat()
timeFormat: db.settings?.getTimeFormat(),
fontScale
});
}, [
fullscreen,
isPremium,
readonly,
editor.sessionId,
editor.loading,
deviceMode,
tools,
@@ -215,7 +218,8 @@ export const useEditorEvents = (
defaultFontFamily,
dateFormat,
timeFormat,
loading
loading,
fontScale
]);
const onBackPress = useCallback(async () => {
@@ -224,6 +228,8 @@ export const useEditorEvents = (
logger.info("editor handled back event", editorHandledBack);
return;
}
editorState().currentlyEditing = false;
editor.reset();
setTimeout(async () => {
if (deviceMode !== "mobile" && fullscreen) {
if (fullscreen) {
@@ -236,14 +242,10 @@ export const useEditorEvents = (
editorState().movedAway = true;
tabBarRef.current?.goToPage(0);
}
setImmediate(() => {
useEditorStore.getState().setCurrentlyEditingNote(null);
setTimeout(() => {
Navigation.queueRoutesForUpdate();
}, 500);
});
editorState().currentlyEditing = false;
editor.reset();
setTimeout(() => {
Navigation.queueRoutesForUpdate();
}, 500);
}, 1);
}, [editor, deviceMode, fullscreen]);
@@ -339,8 +341,12 @@ export const useEditorEvents = (
});
}
if (editorMessage.type === EventTypes.back) {
return onBackPress();
}
if (
editorMessage.sessionId !== editor.sessionId &&
editorMessage.sessionId !== editor.sessionId.current &&
editorMessage.type !== EditorEvents.status
) {
return;
@@ -426,9 +432,6 @@ export const useEditorEvents = (
editorState().isFullscreen = true;
eSendEvent(eOpenFullscreenEditor);
break;
case EventTypes.back:
onBackPress();
break;
case EventTypes.link:
openLinkInBrowser(editorMessage.value as string);
break;

View File

@@ -17,11 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFormattedDate } from "@notesnook/common";
import { EVENTS } from "@notesnook/core/dist/common";
import { useThemeEngineStore } from "@notesnook/theme";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import WebView from "react-native-webview";
import { db } from "../../../common/database";
import { DatabaseLogger, db } from "../../../common/database";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import { DDS } from "../../../services/device-detection";
import {
@@ -31,6 +32,7 @@ import {
openVault
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { TipManager } from "../../../services/tip-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
@@ -52,7 +54,6 @@ import {
makeSessionId,
post
} from "./utils";
import { getFormattedDate } from "@notesnook/common";
export const useEditor = (
editorId = "",
@@ -62,8 +63,7 @@ export const useEditor = (
const theme = useThemeEngineStore((state) => state.theme);
const [loading, setLoading] = useState(false);
const [sessionId, setSessionId] = useState<string>(makeSessionId());
const sessionIdRef = useRef(sessionId);
const sessionIdRef = useRef(makeSessionId());
const editorRef = useRef<WebView>(null);
const currentNote = useRef<NoteType | null>();
const currentContent = useRef<Content | null>();
@@ -98,10 +98,6 @@ export const useEditor = (
postMessage(EditorEvents.theme, theme);
}, [theme, postMessage]);
useEffect(() => {
sessionIdRef.current = sessionId;
}, [sessionId]);
useEffect(() => {
commands.setTags(currentNote.current);
}, [commands, tags]);
@@ -133,19 +129,27 @@ export const useEditor = (
const reset = useCallback(
async (resetState = true, resetContent = true) => {
currentNote.current?.id && db.fs?.cancel(currentNote.current.id);
currentNote.current?.id &&
db.fs?.cancel(currentNote.current.id, "download");
currentNote.current = null;
loadedImages.current = {};
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
loadingState.current = undefined;
lock.current = false;
useEditorStore.getState().setReadonly(false);
resetContent && postMessage(EditorEvents.title, "");
lastContentChangeTime.current = 0;
resetContent && (await commands.clearContent());
resetContent && (await commands.clearTags());
if (resetState) {
const newSessionId = makeSessionId();
sessionIdRef.current = newSessionId;
await commands.setSessionId(newSessionId);
isDefaultEditor &&
useEditorStore.getState().setCurrentlyEditingNote(null);
placeholderTip.current = TipManager.placeholderTip();
@@ -234,6 +238,10 @@ export const useEditor = (
id && useEditorStore.getState().setCurrentlyEditingNote(id);
});
}
if (Notifications.isNotePinned(id as string)) {
Notifications.pinNote(id as string);
}
} else {
noteData.contentId = note?.contentId;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -258,7 +266,8 @@ export const useEditor = (
saveCount.current++;
return id;
} catch (e) {
console.log("Error saving note: ", e);
console.error(e);
DatabaseLogger.error(e as Error);
}
},
[commands, isDefaultEditor, postMessage, readonly, reset]
@@ -273,6 +282,7 @@ export const useEditor = (
noteId: currentNote.current?.id as string
};
} else {
if (!note.contentId) return;
currentContent.current = await db.content?.raw(note.contentId);
}
}, []);
@@ -349,7 +359,6 @@ export const useEditor = (
if (item && item.type === "new") {
currentNote.current && (await reset());
const nextSessionId = makeSessionId(item as NoteType);
setSessionId(nextSessionId);
sessionIdRef.current = nextSessionId;
sessionHistoryId.current = Date.now();
await commands.setSessionId(nextSessionId);
@@ -360,12 +369,23 @@ export const useEditor = (
if (!item.forced && currentNote.current?.id === item.id) return;
state.current.movedAway = false;
state.current.currentlyEditing = true;
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
currentNote.current && (await reset(false, false));
if (currentNote.current?.id !== item.id) {
currentNote.current && (await reset(false, false));
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
}
await loadContent(item as NoteType);
if (loadingState.current === currentContent.current?.data) {
if (
currentNote.current?.id === item.id &&
loadingState.current &&
currentContent.current?.data &&
loadingState.current === currentContent.current?.data
) {
return;
}
if (
!currentContent.current?.data ||
currentContent.current?.data.length < 50000
@@ -380,20 +400,21 @@ export const useEditor = (
}
lastContentChangeTime.current = item.dateEdited;
const nextSessionId = makeSessionId(item as NoteType);
sessionIdRef.current = nextSessionId;
lockedSessionId.current = nextSessionId;
sessionHistoryId.current = Date.now();
setSessionId(nextSessionId);
commands.setSessionId(nextSessionId);
sessionIdRef.current = nextSessionId;
await commands.setSessionId(nextSessionId);
currentNote.current = item as NoteType;
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
await postMessage(EditorEvents.title, item.title);
loadingState.current = currentContent.current?.data;
await postMessage(
EditorEvents.html,
currentContent.current?.data,
10000
);
if (currentContent.current?.data) {
await postMessage(
EditorEvents.html,
currentContent.current?.data,
10000
);
}
loadingState.current = undefined;
useEditorStore.getState().setReadonly(item.readonly);
await commands.setTags(currentNote.current);
@@ -519,6 +540,14 @@ export const useEditor = (
}) => {
if (lock.current || lockedSessionId.current === forSessionId) return;
lastContentChangeTime.current = Date.now();
if (
sessionHistoryId.current &&
Date.now() - sessionHistoryId.current > 5 * 60 * 1000
) {
sessionHistoryId.current = Date.now();
}
if (type === EditorEvents.content) {
currentContent.current = {
data: content,
@@ -602,7 +631,7 @@ export const useEditor = (
useEffect(() => {
state.current.saveCount = 0;
}, [sessionId, loading]);
}, [loading]);
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
@@ -654,8 +683,7 @@ export const useEditor = (
loading,
setLoading,
state,
sessionId,
setSessionId,
sessionId: sessionIdRef,
note: currentNote,
onReady,
saveContent,

View File

@@ -55,6 +55,7 @@ export const EditorWrapper = ({ width }) => {
prevState.current = state;
return;
}
if (useSettingStore.getState().appDidEnterBackgroundForAction) return;
if (state === "active") {
editorController.current.onReady();
editorController.current.overlay(false);

View File

@@ -29,6 +29,7 @@ import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { openEditor } from "../notes/common";
const prepareSearch = () => {
SearchService.update({
placeholder: "Type a keyword to search in notes",
@@ -70,7 +71,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
<List
listData={notes}
type="notes"
screen="Notes"
screen="Home"
loading={loading || !isFocused}
headerProps={{
heading: "Notes"

View File

@@ -71,7 +71,7 @@ export const SearchBar = () => {
} catch (e) {
console.log(e);
ToastEvent.show({
heading: "Error occured while searching",
heading: "Error occurred while searching",
message: e.message,
type: "error"
});

View File

@@ -42,7 +42,8 @@ export async function verifyUser(
negativeText: closeText || "Cancel",
positivePress: async (value) => {
try {
let verified = await db.user.verifyPassword(value);
const user = await db.user.getUser();
let verified = !user ? true : await db.user.verifyPassword(value);
if (verified) {
sleep(300).then(async () => {
await onsuccess();
@@ -58,7 +59,7 @@ export async function verifyUser(
}
} catch (e) {
ToastEvent.show({
heading: "Failed to backup data",
heading: "Failed to verify",
message: e.message,
type: "error",
context: "global"

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