Compare commits

..

228 Commits

Author SHA1 Message Date
ammarahm-ed
c1708260b8 mobile: fix tag not updated on list item on rename 2023-04-07 14:45:30 +05:00
ammarahm-ed
45ebf0670f mobile: show error when note is locked 2023-04-07 02:22:49 +05:00
ammarahm-ed
9cfaa90232 mobile: use text exporter from core 2023-04-07 02:22:49 +05:00
ammarahm-ed
edf4a248ed web: remove unnecessary param passed to export 2023-04-07 02:22:49 +05:00
ammarahm-ed
14d7ec6b4e core: allow exports with custom content 2023-04-07 02:22:49 +05:00
Ammar Ahmed
cd2ca94c3d mobile: Fix crash when leaving note after search (#2289) 2023-04-07 01:10:52 +05:00
Abdullah Atta
f1609aaa46 editor: do not convert \n characters to paragraphs 2023-04-06 01:40:55 +05:00
ammarahm-ed
851364c0bf mobile: release 2.4.11 2023-04-05 02:45:02 +05:00
ammarahm-ed
913a1743b5 mobile: update deps 2023-04-05 02:13:49 +05:00
ammarahm-ed
dc5572d4b9 editor: enable passing custom sessionId 2023-04-05 02:13:49 +05:00
ammarahm-ed
554560903c mobile: improve share extension search 2023-04-05 02:13:49 +05:00
ammarahm-ed
885e4a325c mobile: fix snap points for sheet on android 2023-04-05 02:13:49 +05:00
ammarahm-ed
156ce7e711 mobile: ensure note is saved when change note 2023-04-05 02:13:49 +05:00
Abdullah Atta
71ecc05bd6 web: bump version to v2.4.7 2023-04-05 01:54:51 +05:00
Abdullah Atta
98b93d42c1 theme: publish 2023-04-05 01:53:29 +05:00
Abdullah Atta
2e7935cacc editor: publish 2023-04-05 01:53:16 +05:00
Abdullah Atta
50d64af7f6 web: update export html test snapshots 2023-04-04 23:35:35 +05:00
Abdullah Atta
e452fd3d2f core: update export tests snapshots 2023-04-04 23:16:04 +05:00
Abdullah Atta
64dd481402 web: fix cannot read property id of undefined 2023-04-04 22:04:46 +05:00
Abdullah Atta
28f647fa06 web: fix a couple of state mgmt issues with move note dialog 2023-04-04 22:04:14 +05:00
ammarahm-ed
9eb1061cd1 mobile: refactor 2023-04-04 22:04:14 +05:00
ammarahm-ed
3cab264213 mobile: increase the timeout 2023-04-04 22:04:14 +05:00
ammarahm-ed
fb56e7c866 mobile: exit current topic if it is deleted. 2023-04-04 22:04:14 +05:00
ammarahm-ed
234e368c4d mobile: cleanup 2023-04-04 22:04:14 +05:00
ammarahm-ed
4de4693505 mobile: fix topic sheet snaps to top in topics screen 2023-04-04 22:04:14 +05:00
ammarahm-ed
5563c7d5f8 mobile: remove floating button on topic screen 2023-04-04 22:04:14 +05:00
ammarahm-ed
93225e5a30 mobile: fix topic sheet related state issues 2023-04-04 22:04:14 +05:00
ammarahm-ed
0ab9846732 mobile: fix edit notebook sheet does not close after saving edits 2023-04-04 22:04:14 +05:00
ammarahm-ed
f6f1cf0ae7 mobile: fix edit notebook button not working 2023-04-04 22:04:14 +05:00
ammarahm-ed
69615d54db mobile: remember last position of sheet in notebook 2023-04-04 22:04:14 +05:00
ammarahm-ed
a990d0f404 mobile: fix move note sheet not opening 2023-04-04 22:04:14 +05:00
ammarahm-ed
4aeff48e74 mobile: cleanup 2023-04-04 22:04:14 +05:00
ammarahm-ed
78c1a75ec9 mobile: improve opening sheet 2023-04-04 22:04:14 +05:00
ammarahm-ed
9e6664a80e mobile: improve topics-sheet ux 2023-04-04 22:04:14 +05:00
ammarahm-ed
b306a09747 editor: refresh editor on line spacing changed 2023-04-04 17:23:27 +05:00
ammarahm-ed
fab52012ad mobile: fix loading indicator color 2023-04-04 16:26:07 +05:00
ammarahm-ed
fc39c714b6 mobile: update project pods 2023-04-04 16:26:07 +05:00
ammarahm-ed
5fff4bbb0e mobile: fix font sizes 2023-04-04 16:26:07 +05:00
ammarahm-ed
0a29571ad8 mobile: upgrade share extension to support organization 2023-04-04 16:26:07 +05:00
ammarahm-ed
275fa1b3ca mobile: fix loading editor screen on app launch 2023-04-04 16:25:07 +05:00
ammarahm-ed
79889665e5 mobile: cleanup deps 2023-04-04 16:25:07 +05:00
ammarahm-ed
9888ad327c mobile: fix editor hidden when changing orientation
from landscape to
portrait
2023-04-04 16:25:07 +05:00
ammarahm-ed
77ab2d7740 mobile: skip showing loading overlay for relatively smaller
notes
2023-04-04 16:25:07 +05:00
ammarahm-ed
509dc4b541 editor: scroll to top on note change 2023-04-04 16:25:07 +05:00
ammarahm-ed
ff84ee2d42 mobile: improve note saving reliability &
performance
2023-04-04 16:25:07 +05:00
ammarahm-ed
d838c2e8ad editor: update word count immediately after note is loaded
Previously when opening note, the word count on mobile would
be 0, then update to actual word count. This has been fixed so
correct word count shows when opening not without any
delay.
2023-04-04 16:25:07 +05:00
ammarahm-ed
28d718acf6 mobile: show no loading for relatively smaller notes
Previously if a note had smaller content, loading would still take
quite a bit long which seemed unnecessary. Now loading will show
only  if note takes long enough to load which should make the app
feel much more snappier than before when opening notes.
2023-04-04 16:25:07 +05:00
Muhammad Ali
adf546f29c web: disable deleting locked notes (#2244) 2023-04-04 16:24:28 +05:00
Abdulrehman-Jafer
b18cf277f6 core: fix code block overflow in PDF export (#2245)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-04-04 16:19:02 +05:00
ammarahm-ed
8ab26eb852 mobile: fix crash where notebook is not present 2023-03-31 12:56:20 +05:00
ammarahm-ed
aac43274a0 mobile: release 2.4.10 2023-03-30 17:22:00 +05:00
Muhammad Ali
2fb2c4247a web: do not add "none" tool in Configure Toolbar dialog (#2193)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-03-30 17:10:43 +05:00
Abdulrehman-Jafer
cb1d32d177 core: fix txt, pdf & html export line spacing (#2215)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-03-30 17:09:00 +05:00
ammarahm-ed
75e1985a6f mobile: save value on change 2023-03-30 16:48:55 +05:00
ammarahm-ed
0093b39f64 mobile: fix nav bar does not follow app theme 2023-03-30 16:07:58 +05:00
ammarahm-ed
d19c2ef735 mobile: add mastodon social link 2023-03-30 15:30:55 +05:00
ammarahm-ed
db527cfbf3 mobile: fix splashscreen logo quality 2023-03-30 15:04:08 +05:00
ammarahm-ed
7da8ddb24f mobile: topic title should to single line 2023-03-30 15:02:01 +05:00
ammarahm-ed
5d401ac9ac mobile: use diff sheet size on each platform 2023-03-30 13:54:21 +05:00
ammarahm-ed
9605eafa00 mobile: add support for sorting topics in sheet 2023-03-30 13:54:21 +05:00
Ammar Ahmed
783460c685 Merge pull request #2218 from streetwriters/fix-reminder-date-set
mobile: fix reminder date picker not going in past
2023-03-29 16:54:24 +05:00
ammarahm-ed
af906d8ace mobile: add some null checks 2023-03-29 16:54:04 +05:00
ammarahm-ed
fca26a8793 mobile: fix reminder date picker not going in past 2023-03-29 16:29:40 +05:00
Muhammad Ali
27863def9c web: fix RTL support for outline lists (#2175)
Co-authored-by: Muhammad Ali <alihamuh@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-03-24 12:51:00 +05:00
Abdullah Atta
f1d4fd1d5d editor: fix crash when theme.colors is undefined 2023-03-24 12:49:26 +05:00
Abdullah Atta
61b7e43d08 web: do not interrupt importer if notebook is undefined 2023-03-24 11:43:37 +05:00
Abdullah Atta
87ebe1918c web: use debounce instead of inlineDebounce for selection updates in editor 2023-03-24 11:41:03 +05:00
ammarahm-ed
4c5253c267 mobile: release v2.4.9 2023-03-24 02:54:36 +05:00
Abdulrehman-Jafer
f5be8a0c86 web: migrate keyboard.js to typescript (#2160)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-24 00:57:55 +05:00
Abdullah Atta
5392582819 web: fix crash on opening an empty notebook 2023-03-24 00:35:29 +05:00
Abdullah Atta
651ae4cdaa web: bump version to 2.4.6 2023-03-24 00:28:19 +05:00
Abdullah Atta
d87b5c5386 desktop: don't show tray menu icons on flatpak builds 2023-03-24 00:28:19 +05:00
ammarahm-ed
d5331b1997 mobile: release v2.4.8 2023-03-24 00:18:12 +05:00
ammarahm-ed
0a6f8fa18e mobile: improve publish note sheet ui 2023-03-24 00:12:18 +05:00
Abdullah Atta
c1ba16eb80 web: group top level notebook notes 2023-03-23 23:58:17 +05:00
Ammar Ahmed
0cd01b25bd Merge pull request #2187 from streetwriters/fix/publish-note-mobile
Fix publish note with password on mobile
2023-03-23 23:52:50 +05:00
Abdullah Atta
f0c1300ec8 web: hyper optimize move note dialog 2023-03-23 23:48:49 +05:00
ammarahm-ed
70eee2a224 mobile: cleanup logs 2023-03-23 23:48:49 +05:00
ammarahm-ed
42470d1d67 mobile: fix add button color in dark mode 2023-03-23 23:48:49 +05:00
ammarahm-ed
7fc58a3fa5 mobile: make bottom padding optional in sheet 2023-03-23 23:48:49 +05:00
ammarahm-ed
e186037c03 mobile: fix bottom padding on sheet 2023-03-23 23:48:49 +05:00
ammarahm-ed
732a0ffea4 mobile: fix duplicate key warning 2023-03-23 23:48:49 +05:00
ammarahm-ed
89f2e9fde1 mobile: show selected indicator on notebook 2023-03-23 23:48:49 +05:00
Abdullah Atta
c369541a7f web: indicate which notebooks have selected topics 2023-03-23 23:48:49 +05:00
Abdullah Atta
0b7a157ab1 web: enable multiselect mode if note is in > 1 nb/topic 2023-03-23 23:48:49 +05:00
ammarahm-ed
9b86c6ebb7 mobile: fix multiselection bugs 2023-03-23 23:48:49 +05:00
ammarahm-ed
22cd07e414 mobile: improve selection performance 2023-03-23 23:48:49 +05:00
ammarahm-ed
47f919cb61 mobile: fix reset select does not reset selected topics 2023-03-23 23:48:49 +05:00
Abdulrehman-Jafer
5b3c889b7c web: migrate css.js to typescript (#2165)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 17:03:23 +05:00
Abdullah Atta
77ac53215c web: remove document as any in page-visibility 2023-03-23 13:55:52 +05:00
Abdulrehman-Jafer
167c7125d2 web: migrate page-visibility.js to typescript (#2166)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 13:48:57 +05:00
Abdullah Atta
ba56fd0279 web: don't reset reminders before getting the user 2023-03-23 13:46:54 +05:00
Abdullah Atta
8e795b729a web: read trash cleanup interval from db on startup 2023-03-23 13:46:54 +05:00
Abdullah Atta
a4eb597f35 web: improve notebook vertical pane resizing 2023-03-23 13:46:44 +05:00
Abdullah Atta
0aeade69da web: fix topic search 2023-03-23 13:43:45 +05:00
Abdullah Atta
90a3bd3307 web: fix crash on creating a topic 2023-03-23 13:43:45 +05:00
ammarahm-ed
7a61e3558e mobile: ui cleanup 2023-03-23 13:39:14 +05:00
Abdulrehman-Jafer
dd9c555bff web: migrate parse.js to typescript (#2162)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 13:36:31 +05:00
ammarahm-ed
50f84780bb mobile: fix publish note with password not working 2023-03-23 13:23:04 +05:00
Abdulrehman-Jafer
e12372364d web: add support for realtime search (#2171)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 12:45:33 +05:00
ammarahm-ed
969611c9e6 mobile: release v2.4.7 2023-03-22 15:05:24 +05:00
ammarahm-ed
06399fe3bd mobile: fix title hidden on navigate sometimes 2023-03-22 15:02:55 +05:00
Abdullah Atta
35c5a90b4e web: update version highlights 2023-03-22 13:47:41 +05:00
Abdullah Atta
3c70cccd14 web: topic is rendered incomplete when opened directly 2023-03-22 13:42:36 +05:00
Abdullah Atta
a3c24bc666 editor: fix image resizing 2023-03-22 13:28:25 +05:00
ammarahm-ed
16b00d21a6 mobile: cleanup 2023-03-22 13:28:25 +05:00
Abdullah Atta
88f90c6240 editor: add padding to placeholder 2023-03-22 13:28:25 +05:00
Abdullah Atta
a5c44dcb3b editor: add src to image on copy 2023-03-22 13:28:25 +05:00
Abdullah Atta
c0a2dd139a web: only load new media attachments on realtime sync 2023-03-22 13:28:25 +05:00
Abdullah Atta
b354a26f0a core: allow selectively downloading media attachments 2023-03-22 13:28:25 +05:00
ammarahm-ed
b3de1205d9 mobile: cleanup 2023-03-22 13:28:25 +05:00
ammarahm-ed
f18e8ae421 mobile: properly handle loading images in editor 2023-03-22 13:28:25 +05:00
ammarahm-ed
a46b863802 core: return only attachment meta if
attachment already exists
2023-03-22 13:28:25 +05:00
ammarahm-ed
2befa03cd2 mobile: conform to new image loading api 2023-03-22 13:28:25 +05:00
Abdullah Atta
763a108abb editor: always use relative height for resizer 2023-03-22 13:28:25 +05:00
Abdullah Atta
30a069c845 editor: always use black icons on image overlay 2023-03-22 13:28:25 +05:00
Abdullah Atta
ffbf5cdc88 editor: fade-in on image load 2023-03-22 13:28:25 +05:00
Abdullah Atta
4318a65a99 editor: use dataurl attr for inserting an image 2023-03-22 13:28:25 +05:00
Abdullah Atta
bc433c56ae core: don't return invalid dataurl if image doesn't exist 2023-03-22 13:28:25 +05:00
Abdullah Atta
e43740c6bc editor: add comments for why we use UTITypes 2023-03-22 13:28:25 +05:00
Abdullah Atta
6123bccf4b editor: only save image height on load 2023-03-22 13:28:25 +05:00
Abdullah Atta
08106c20e8 editor: use dataurl instead of src attr for loading images 2023-03-22 13:28:25 +05:00
Abdullah Atta
eaaecbd5e5 core: hash base64 before saving it as attachment to prevent dups 2023-03-22 13:28:25 +05:00
Abdullah Atta
29608099db web: writeEncrypted -> writeEncryptedBase64 2023-03-22 13:28:25 +05:00
Abdullah Atta
7a773937c2 core: writeEncrypted now accepts only base64 input 2023-03-22 13:28:25 +05:00
Abdullah Atta
fa4a298bfa web: use NodeJS.Platform instead of process.platform 2023-03-22 10:45:03 +05:00
Abdulrehman-Jafer
966bedbb49 web: migrate platform.js to typescript (#2163)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 10:42:49 +05:00
Abdulrehman-Jafer
159b6f3ca6 web: migrate dimensions.js to typescript (#2153)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 09:21:48 +05:00
Abdulrehman-Jafer
de1b0fc466 web: migrate filename.js to typescript (#2156)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 09:07:52 +05:00
Abdulrehman-Jafer
b6a3394dee web: migrate object.js to typescript (#2161)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 09:00:29 +05:00
Abdulrehman-Jafer
f5ac51db13 web: migrate random.js to typescript (#2164)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 08:59:07 +05:00
Abdullah Atta
1ec0b65fe8 web: extract attachment extension from mime-type 2023-03-21 09:59:45 +05:00
Abdullah Atta
d9a77ff658 core: migrate filename.ts to javascript 2023-03-21 09:59:45 +05:00
ammarahm-ed
c45118515c editor: add react-scripts patch 2023-03-21 09:59:45 +05:00
ammarahm-ed
f9b8865351 editor: export blob as b64 with correct content type 2023-03-21 09:59:45 +05:00
ammarahm-ed
1e92b3fc92 mobile: resolve attachment hash from src if hash not avail 2023-03-21 09:59:45 +05:00
ammarahm-ed
f34fb858ec mobile: add filename extension before download 2023-03-21 09:59:45 +05:00
ammarahm-ed
1fdede5518 core: add getFileNameWithExtension utility 2023-03-21 09:59:45 +05:00
Abdulrehman-Jafer
a454ec4944 web: migrate color.js to typescript (#2151)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-21 09:37:56 +05:00
Abdullah Atta
5afbf103ac ci: disable arm64 builds for snap 2023-03-21 09:25:15 +05:00
Abdullah Atta
2ef1b76bfb ci: disable deb/rpm builds 2023-03-21 09:03:15 +05:00
Abdullah Atta
f7d06d3b82 desktop: enable arm64 builds 2023-03-21 08:40:03 +05:00
Abdullah Atta
e405468471 web: bump version to 2.4.5 2023-03-21 08:07:11 +05:00
Abdullah Atta
9d2bacdc5f web: replace fixed 7 days with trash cleanup interval 2023-03-21 08:06:34 +05:00
Abdullah Atta
b44d4702be web: update @notesnook-importer/core to v1.7.1 2023-03-20 18:52:05 +05:00
Abdullah Atta
03803dbc23 editor: fix task-list tests 2023-03-20 18:05:27 +05:00
Abdullah Atta
9ea675ded1 editor: fix sub checklists not sorting 2023-03-20 18:05:27 +05:00
Abdullah Atta
c85d623e1c web: update tab/window title for locked & conflicted notes 2023-03-20 18:05:27 +05:00
ammarahm-ed
e6f1320b1d mobile: optimize sheets 2023-03-20 17:08:06 +05:00
Abdullah Atta
80f49e22e5 web: reset tab/window title on new note 2023-03-20 17:05:18 +05:00
Abdullah Atta
4884c27a9a editor: update patches 2023-03-20 16:40:58 +05:00
Ammar Ahmed
4976a4dab4 Merge pull request #2143 from streetwriters/fix-ipad-orientation
mobile: fix ipad orientation window sizing
2023-03-20 16:36:02 +05:00
Ammar Ahmed
5eb0b5faa7 Merge pull request #2145 from streetwriters/fix-gallery-not-shown-reupload
Fix gallery not shown when reupload image
2023-03-20 16:35:49 +05:00
Ammar Ahmed
5e33cb93c6 editor: fix focus last node on tap bottom area on mobile (#2148) 2023-03-20 16:35:23 +05:00
ammarahm-ed
b4220d349f editor: fix loading copied image on ios 2023-03-20 16:34:46 +05:00
Abdullah Atta
d4752685d0 editor: keep marks when creating new list items
we shouldn't reset the user's selected styles like bold/italic when they
create a new list item.
2023-03-20 16:33:14 +05:00
ammarahm-ed
0b06d2a4bd mobile: fix attachment dialog ui 2023-03-20 15:09:52 +05:00
ammarahm-ed
a37d9a193f mobile: fix gallery not shown on reupload image 2023-03-20 15:09:23 +05:00
Abdulrehman-Jafer
b1d465e8bf web: fix monographs and reminders search (#2142)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-20 14:40:51 +05:00
Abdullah Atta
31e17c305f web: show correct sync status when sync is disabled 2023-03-20 14:01:20 +05:00
Abdullah Atta
72db8724ec editor: improve gradient overlay on image 2023-03-20 14:00:50 +05:00
ammarahm-ed
b64cb22186 mobile: fix ipad orientation window sizing 2023-03-20 13:51:16 +05:00
Abdullah Atta
f5ca1220de web: add confirm & progress for subscription cancelling 2023-03-20 12:53:20 +05:00
Abdullah Atta
fb54125ee6 web: simplify subscription cancellation 2023-03-20 12:53:20 +05:00
Abdullah Atta
be957d058f core: add basic subscription handling 2023-03-20 12:53:20 +05:00
Abdullah Atta
54a3e3c927 mobile: remove react-native-actions-sheet patch 2023-03-20 12:47:23 +05:00
ammarahm-ed
aabbf7c1c1 mobile: fix tags not properly shown in sheet 2023-03-20 11:32:49 +05:00
ammarahm-ed
7773ba0804 mobile: finalize topic sheet ui 2023-03-20 11:28:53 +05:00
ammarahm-ed
9f812772fe mobile: fix tag text position 2023-03-20 11:28:53 +05:00
ammarahm-ed
75191afc3e mobile: fix image upload broken on ios 2023-03-20 11:28:53 +05:00
Abdulrehman-Jafer
1c25baccea web: set tab/window title to note title (#2127)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-19 12:24:55 +05:00
Abdullah Atta
b1d4ccc27e editor: fix dropdown needing 2 clicks to open 2023-03-18 12:28:08 +05:00
Abdulrehman-Jafer
0d714847f3 web: copy version on clicking the version item in settings (#2131)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-03-18 12:25:20 +05:00
Abdullah Atta
760d4abbd0 web: groups shouldn't be ignored in reminders list 2023-03-18 11:58:30 +05:00
Abdullah Atta
7c1bb33ea9 mobile: update package-lock.json 2023-03-18 11:58:30 +05:00
ammarahm-ed
5ba8ce549e mobile: disable update check during tests 2023-03-18 11:58:30 +05:00
ammarahm-ed
a449c614ee mobile: make all tests pass 2023-03-18 11:58:30 +05:00
Abdullah Atta
12e6688f48 web: nuke Remove from topic menu item 2023-03-18 11:58:30 +05:00
Abdullah Atta
da47ef159b web: fix multiselect for notebook actions on note 2023-03-18 11:58:30 +05:00
Abdullah Atta
efc678d3cd core: removeFromAllNotebooks should unlink notebook relations too 2023-03-18 11:58:30 +05:00
ammarahm-ed
d7a126ab85 mobile: add test ids 2023-03-18 11:58:30 +05:00
ammarahm-ed
bc9c976c95 mobile: fix header title size in topic 2023-03-18 11:58:30 +05:00
ammarahm-ed
26af23771d mobile: fix refresh control colors 2023-03-18 11:58:30 +05:00
ammarahm-ed
590f2d80d3 mobile: add remove from notebook with multi-select 2023-03-18 11:58:30 +05:00
ammarahm-ed
fdeb34ff0a mobile: improve wording of delete notebook dialog 2023-03-18 11:58:30 +05:00
Abdullah Atta
ec76be776c web: make topics optional in importer 2023-03-18 11:58:30 +05:00
Abdullah Atta
4de5691428 web: clarify notebook/topic delete dialog wording 2023-03-18 11:58:30 +05:00
ammarahm-ed
ae5b339251 mobile: change color of sort direction button 2023-03-18 11:58:30 +05:00
ammarahm-ed
a6021943e1 mobile: top level notes 2023-03-18 11:58:30 +05:00
Abdullah Atta
765fcafc38 web: fix all tests & improve notebook/topic deletion 2023-03-18 11:58:30 +05:00
Abdullah Atta
7a53881eb3 web: remove unnecessary console.log 2023-03-18 11:58:30 +05:00
Abdullah Atta
854f22f373 web: improve how reminder bar looks when pane is resized 2023-03-18 11:58:30 +05:00
Abdullah Atta
0566f98323 web: disable horizontal scroll in list container 2023-03-18 11:58:30 +05:00
Abdullah Atta
435a358c4c web: switch to compact ui for tag items 2023-03-18 11:58:30 +05:00
Abdullah Atta
3f55422d45 web: add support for top-level notes in notebooks 2023-03-18 11:58:30 +05:00
Abdullah Atta
9804c1fbb1 core: include notebook notes in total notes count 2023-03-18 11:58:30 +05:00
Abdullah Atta
c3b8c4b630 mobile: update help tips 2023-03-18 11:58:30 +05:00
Abdullah Atta
8c4d8622d8 web: replace svg placeholders with helpful tips 2023-03-18 11:58:30 +05:00
Abdullah Atta
86770b32bb core: add support for linking notes with notebooks
this basically allows notes to exist at the top-level in a notebook
2023-03-18 11:58:30 +05:00
Abdullah Atta
74c86c1e5f editor: table options not visible on old tables 2023-03-17 22:44:29 +05:00
ammarahm-ed
1538b74c63 mobile: make never option premium in trash interval 2023-03-17 22:07:19 +05:00
Abdullah Atta
5934c518e6 web: refactor OptionItem to take objects instead of strings 2023-03-17 22:07:19 +05:00
alihamuh
71dfd0cf9a web: changed config to db for trashClean up interval 2023-03-17 22:07:19 +05:00
ammarahm-ed
984bf1f15f core: remove trash duration fn 2023-03-17 22:07:19 +05:00
ammarahm-ed
a14949b75a mobile: remove pro check 2023-03-17 22:07:19 +05:00
ammarahm-ed
7bbe138d91 mobile: cleanup 2023-03-17 22:07:19 +05:00
ammarahm-ed
65dea70ae5 mobile: add trash interval settings 2023-03-17 22:07:19 +05:00
ammarahm-ed
a07c8b9e11 core: add trash cleanup duration settings 2023-03-17 22:07:19 +05:00
Abdullah Atta
69b82c61f8 web: change wording of trash settings 2023-03-17 22:07:19 +05:00
alihamuh
fb8df74242 web: added trash setting to control delete time 2023-03-17 22:07:19 +05:00
Abdullah Atta
3674594832 core: do a healthcheck before logging out user on session revoke 2023-03-17 17:03:00 +05:00
Ammar Ahmed
1ee24cde07 editor: add support for attachment previews (#2123)
This adds support for basic attachment preview support. Currently only image previews are supported on mobile.

    Fixed image downloading not working on mobile
    Added image preview support on mobile with support for loading full quality images in full screen.
    Added Attachment preview logic in editor, so in future we can support preview for other files such as audio & video
2023-03-17 14:43:53 +05:00
Abdullah Atta
93baed35f4 editor: fix code-block tests to use our own getChangedNodes fn
(cherry picked from commit ff246fb9092ea002b76738f3f7082b5c5f10ea6c)
2023-03-13 12:41:29 +05:00
Abdullah Atta
3e1420d496 editor: get rid of @remirror/core-utils
(cherry picked from commit c74f15c86b2f25c9f572ea74f47243e1b52f9c1f)
2023-03-13 12:41:02 +05:00
Abdullah Atta
ebc4324ef1 web: upgrade @notesnook-importer/core to v1.6.0 2023-03-10 18:24:59 +05:00
Abdullah Atta
6ba04f4731 editor: move cell properties button outside of table settings
this fixes #1410
2023-03-06 16:44:09 +05:00
Abdulrehman-Jafer
7359e23b65 web: fix autocomplete caret color in all inputs (#2073)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-06 15:25:14 +05:00
Abdullah Atta
01387bae94 editor: update code-block test snapshost 2023-03-06 15:15:52 +05:00
Abdullah Atta
df1d61ecdf ci: build editor before running tests 2023-03-06 15:15:52 +05:00
Abdullah Atta
cb644dbea6 editor: disable wrapping for all lines in codeblocks 2023-03-06 15:15:52 +05:00
Abdullah Atta
fc5ea0ad51 editor: add tests for codeblock 2023-03-06 15:15:52 +05:00
Abdullah Atta
8108dbae8a editor: move codeblock language loading into highlighter plugin 2023-03-06 15:15:52 +05:00
Abdullah Atta
b14a9d6eca editor: improve code block detection when pasting 2023-03-06 15:15:52 +05:00
Abdullah Atta
2f4a7f5410 editor: migrate tests to vitest 2023-03-06 15:15:52 +05:00
Abdulrehman-Jafer
dfa718f8ea web: fix autocomplete text color for all inputs (#2057)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-06 14:53:35 +05:00
Abdullah Atta
f842700192 web: upgrade @notesnook/importer to v1.5.4 2023-03-03 20:37:33 +05:00
ammarahm-ed
953d0295dc mobile: update release notes 2023-03-02 13:42:25 +05:00
Ammar Ahmed
836f607aa0 mobile: fix reminders not working on android (#2038) 2023-03-02 13:38:58 +05:00
294 changed files with 18435 additions and 18088 deletions

View File

@@ -215,17 +215,17 @@ jobs:
- name: Build snap
if: inputs.publish-snap
run: |
npx electron-builder --linux snap -p never
npx electron-builder --linux snap:x64 -p never
working-directory: ./apps/web/desktop
- name: Build AppImage deb and rpm
- name: Build AppImage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
npx electron-builder --linux AppImage deb rpm -p always
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
else
npx electron-builder --linux AppImage deb rpm -p never
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
fi
working-directory: ./apps/web/desktop

View File

@@ -50,5 +50,8 @@ jobs:
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Build editor
run: npx nx build @notesnook/editor
- name: Run all @notesnook/editor tests
run: npx nx test @notesnook/editor

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -28,8 +28,16 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir } from "./utils";
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
export default async function downloadAttachment(hash, global = true) {
export default async function downloadAttachment(
hash,
global = true,
options = {
silent: false,
cache: false
}
) {
let attachment = db.attachments.attachment(hash);
if (!attachment) {
console.log("attachment not found");
@@ -37,11 +45,13 @@ export default async function downloadAttachment(hash, global = true) {
}
let folder = {};
if (Platform.OS === "android") {
folder = await ScopedStorage.openDocumentTree();
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir("/downloads/");
if (!options.cache) {
if (Platform.OS === "android") {
folder = await ScopedStorage.openDocumentTree();
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir("/downloads/");
}
}
try {
@@ -54,6 +64,11 @@ export default async function downloadAttachment(hash, global = true) {
)
return;
let filename = getFileNameWithExtension(
attachment.metadata.filename,
attachment.metadata.type
);
let key = await db.attachments.decryptKey(attachment.key);
let info = {
iv: attachment.iv,
@@ -63,46 +78,50 @@ export default async function downloadAttachment(hash, global = true) {
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
fileName: attachment.metadata.filename,
uri: folder.uri,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize
};
let fileUri = await Sodium.decryptFile(key, info, "file");
ToastEvent.show({
heading: "Download successful",
message: attachment.metadata.filename + " downloaded",
type: "success"
});
let fileUri = await Sodium.decryptFile(
key,
info,
options.cache ? "cache" : "file"
);
if (attachment.dateUploaded) {
if (!options.silent) {
ToastEvent.show({
heading: "Download successful",
message: filename + " downloaded",
type: "success"
});
}
if (
attachment.dateUploaded &&
!attachment.metadata?.type?.startsWith("image")
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios") {
fileUri = folder.uri + `/${attachment.metadata.filename}`;
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
}
if (!options.silent) {
presentSheet({
title: "File downloaded",
paragraph: `${filename} saved to ${
Platform.OS === "android"
? "selected path"
: "File Manager/Notesnook/downloads"
}`,
icon: "download",
context: global ? null : attachment.metadata.hash,
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
});
}
console.log("saved file uri: ", fileUri);
presentSheet({
title: "File downloaded",
paragraph: `${attachment.metadata.filename} saved to ${
Platform.OS === "android"
? "selected path"
: "File Manager/Notesnook/downloads"
}`,
icon: "download",
context: global ? null : attachment.metadata.hash,
component: (
<ShareComponent
uri={fileUri}
name={attachment.metadata.filename}
padding={12}
/>
)
});
return fileUri;
} catch (e) {
console.log("download attachment error: ", e);

View File

@@ -23,14 +23,16 @@ import {
deleteFile,
exists,
readEncrypted,
writeEncrypted
writeEncryptedBase64,
hashBase64
} from "./io";
import { uploadFile } from "./upload";
import { cancelable } from "./utils";
export default {
readEncrypted,
writeEncrypted,
writeEncryptedBase64,
hashBase64,
uploadFile: cancelable(uploadFile),
downloadFile: cancelable(downloadFile),
deleteFile,

View File

@@ -63,17 +63,26 @@ export async function readEncrypted(filename, key, cipherData) {
}
}
export async function writeEncrypted(filename, { data, type, key }) {
console.log("file input: ", { type, key });
export async function hashBase64(data) {
const hash = await Sodium.hashFile({
type: "base64",
data,
uri: ""
});
return {
hash: hash,
type: "xxh64"
};
}
export async function writeEncryptedBase64({ data, key }) {
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
console.log(filepath);
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
let output = await Sodium.encryptFile(key, {
uri: Platform.OS === "ios" ? filepath : "file://" + filepath,
type: "url"
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log("encrypted file output: ", output);
return {
...output,

View File

@@ -46,7 +46,6 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const encryptionProgress = useAttachmentStore(
(state) => state.encryptionProgress
);
const onPress = () => {
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
@@ -122,7 +121,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
</View>
</View>
{currentProgress || encryptionProgress || encryption ? (
{currentProgress ||
(encryptionProgress && encryptionProgress !== "0.00") ||
encryption ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {

View File

@@ -17,70 +17,33 @@ 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 React, { useRef, useState } from "react";
import { View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import {
eCloseAttachmentDialog,
eOpenAttachmentsDialog
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import DialogHeader from "../dialog/dialog-header";
import { Toast } from "../toast";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import SheetWrapper from "../ui/sheet";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
export const AttachmentDialog = () => {
import { FlatList } from "react-native-actions-sheet";
export const AttachmentDialog = ({ data }) => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
const actionSheetRef = useRef();
const [attachments, setAttachments] = useState([]);
const [note, setNote] = useState(data);
const [attachments, setAttachments] = useState(
data
? db.attachments.ofNote(data.id, "all")
: [...(db.attachments.all || [])]
);
const attachmentSearchValue = useRef();
const searchTimer = useRef();
const [loading, setLoading] = useState(false);
useEffect(() => {
eSubscribeEvent(eOpenAttachmentsDialog, open);
eSubscribeEvent(eCloseAttachmentDialog, close);
return () => {
eUnSubscribeEvent(eOpenAttachmentsDialog, open);
eUnSubscribeEvent(eCloseAttachmentDialog, close);
};
}, [visible]);
const open = (data) => {
if (data?.id) {
setNote(data);
let _attachments = db.attachments.ofNote(data.id, "all");
setAttachments(_attachments);
} else {
setAttachments([...db.attachments.all]);
}
setVisible(true);
};
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
actionSheetRef.current?.hide();
setVisible(false);
};
const onChangeText = (text) => {
attachmentSearchValue.current = text;
if (
@@ -104,107 +67,101 @@ export const AttachmentDialog = () => {
<AttachmentItem setAttachments={setAttachments} attachment={item} />
);
return !visible ? null : (
<SheetWrapper
centered={false}
fwdRef={actionSheetRef}
onClose={async () => {
setVisible(false);
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<Toast context="local" />
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<DialogHeader
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
<DialogHeader
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setLoading(false);
setAttachments([...db.attachments.all]);
}
setLoading(false);
}
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
) : null}
<FlatList
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
initialNumToRender={10}
windowSize={5}
ListEmptyComponent={
<View
style={{
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
) : null}
}
data={attachments}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
<FlatList
nestedScrollEnabled
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
<View
style={{
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
data={attachments}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10
}}
>
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
{" "}All attachments are end-to-end encrypted.
</Paragraph>
</View>
</SheetWrapper>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10
}}
>
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
{" "}All attachments are end-to-end encrypted.
</Paragraph>
</View>
);
};
AttachmentDialog.present = (note) => {
presentSheet({
component: () => <AttachmentDialog data={note} />
});
};

View File

@@ -21,7 +21,6 @@ import React from "react";
import { useNoteStore } from "../../stores/use-notes-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { AnnouncementDialog } from "../announcements";
import { AttachmentDialog } from "../attachments";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog";
@@ -34,10 +33,6 @@ import MergeConflicts from "../merge-conflicts";
import PremiumDialog from "../premium";
import { Expiring } from "../premium/expiring";
import SheetProvider from "../sheet-provider";
import { AddNotebookSheet } from "../sheets/add-notebook";
import AddToNotebookSheet from "../sheets/add-to";
import ManageTagsSheet from "../sheets/manage-tags";
import PublishNoteSheet from "../sheets/publish-note";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data";
@@ -51,7 +46,6 @@ const DialogProvider = () => {
<LoadingDialog />
<Dialog context="global" />
<AddTopicDialog colors={colors} />
<AddNotebookSheet colors={colors} />
<PremiumDialog colors={colors} />
<AuthModal colors={colors} />
<MergeConflicts />
@@ -61,12 +55,8 @@ const DialogProvider = () => {
<RestoreDataSheet />
<ResultDialog />
<VaultDialog colors={colors} />
<AddToNotebookSheet colors={colors} />
<RateAppSheet />
<ImagePreview />
<PublishNoteSheet />
<ManageTagsSheet />
<AttachmentDialog />
{loading ? null : <Expiring />}
<AnnouncementDialog />
<SessionExpired />

View File

@@ -35,6 +35,7 @@ import BaseDialog from "./base-dialog";
import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
import { useCallback } from "react";
import { Button } from "../ui/button";
export const Dialog = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
@@ -54,7 +55,11 @@ export const Dialog = ({ context = "global" }) => {
input: false,
inputPlaceholder: "Enter some text",
defaultValue: "",
disableBackdropClosing: false
disableBackdropClosing: false,
check: {
info: "Check",
type: "transparent"
}
});
useEffect(() => {
@@ -87,6 +92,7 @@ export const Dialog = ({ context = "global" }) => {
if (data.context !== context) return;
setDialogInfo(data);
setVisible(true);
setInputValue(data.defaultValue);
},
[context]
);
@@ -137,6 +143,9 @@ export const Dialog = ({ context = "global" }) => {
paragraph={dialogInfo.paragraph}
paragraphColor={dialogInfo.paragraphColor}
padding={12}
style={{
minHeight: 0
}}
/>
<Seperator half />
@@ -163,6 +172,28 @@ export const Dialog = ({ context = "global" }) => {
</View>
) : null}
{dialogInfo.check ? (
<>
<Button
onPress={() => {
setInputValue(!inputValue);
}}
icon={
inputValue
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
style={{
justifyContent: "flex-start"
}}
height={35}
width="100%"
title={dialogInfo.check.info}
type={inputValue ? dialogInfo.check.type : "gray"}
/>
</>
) : null}
<DialogButtons
onPressNegative={onNegativePress}
onPressPositive={dialogInfo.positivePress && onPressPositive}

View File

@@ -22,6 +22,7 @@ import { View } from "react-native";
import { useMenuStore } from "../../../stores/use-menu-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
@@ -30,6 +31,7 @@ import Navigation from "../../../services/navigation";
import { db } from "../../../common/database";
import {
eCloseAddTopicDialog,
eOnTopicSheetUpdate,
eOpenAddTopicDialog
} from "../../../utils/events";
import { sleep } from "../../../utils/time";
@@ -40,6 +42,7 @@ import DialogHeader from "../../dialog/dialog-header";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import { Toast } from "../../toast";
import { useRelationStore } from "../../../stores/use-relation-store";
export class AddTopicDialog extends React.Component {
constructor(props) {
@@ -59,14 +62,12 @@ export class AddTopicDialog extends React.Component {
addNewTopic = async () => {
try {
this.setState({ loading: true });
if (!this.title || this.title?.trim() === "") {
ToastEvent.show({
heading: "Topic title is required",
type: "error",
context: "local"
});
this.setState({ loading: false });
return;
}
@@ -78,10 +79,13 @@ export class AddTopicDialog extends React.Component {
await db.notebooks.notebook(topic.notebookId).topics.add(topic);
}
this.setState({ loading: false });
this.close();
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
useMenuStore.getState().setMenuPins();
setTimeout(() => {
Navigation.queueRoutesForUpdate();
useMenuStore.getState().setMenuPins();
});
eSendEvent(eOnTopicSheetUpdate);
useRelationStore.getState().update();
} catch (e) {
console.error(e);
}
@@ -177,7 +181,6 @@ export class AddTopicDialog extends React.Component {
positiveTitle={this.toEdit ? "Save" : "Add"}
onPressNegative={() => this.close()}
onPressPositive={() => this.addNewTopic()}
loading={this.state.loading}
/>
</DialogContainer>
<Toast context="local" />

View File

@@ -135,7 +135,6 @@ const ResultDialog = () => {
paddingHorizontal: 12
}}
onPress={close}
height={50}
fontSize={SIZE.md + 2}
/>
</View>

View File

@@ -207,13 +207,7 @@ export class VaultDialog extends Component {
return;
}
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"TopicNotes",
"TaggedNotes",
"ColoredNotes"
);
Navigation.queueRoutesForUpdate();
this.password = null;
this.confirmPassword = null;

View File

@@ -30,7 +30,6 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import Tag from "../ui/tag";
@@ -64,13 +63,15 @@ export const Title = () => {
}
if (data.y > 150) {
if (!hide) return;
titleState[currentScreen.id] = false;
setHide(false);
} else {
if (hide) return;
titleState[currentScreen.id] = true;
setHide(true);
}
},
[currentScreen.name, hide]
[currentScreen.id, currentScreen.name, hide]
);
useEffect(() => {
@@ -85,10 +86,6 @@ export const Title = () => {
}
}, [currentScreen.id, currentScreen.name]);
useEffect(() => {
titleState[currentScreen.id] = hide;
}, [currentScreen.id, hide]);
useEffect(() => {
eSubscribeEvent(eScrollEvent, onScroll);
return () => {
@@ -112,25 +109,16 @@ export const Title = () => {
{!hide && !isHidden ? (
<Heading
onPress={navigateToNotebook}
numberOfLines={isTopic ? 2 : 1}
size={isTopic ? SIZE.md + 2 : SIZE.xl}
numberOfLines={1}
size={SIZE.xl}
style={{
flexWrap: "wrap",
marginTop: Platform.OS === "ios" ? -1 : 0
}}
color={currentScreen.color || colors.heading}
>
{isTopic ? (
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
{notebook?.title}
{"\n"}
</Paragraph>
) : null}
{isTag ? (
<Heading
size={isTopic ? SIZE.md + 2 : SIZE.xl}
color={colors.accent}
>
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
) : null}

View File

@@ -18,18 +18,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { Platform, View } from "react-native";
import ImageViewer from "react-native-image-zoom-viewer";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { cacheDir } from "../../common/filesystem/utils";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import Sodium from "@ammarahmed/react-native-sodium";
import dataurl from "@notesnook/core/utils/dataurl";
const ImagePreview = () => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [image, setImage] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
@@ -39,9 +47,29 @@ const ImagePreview = () => {
};
}, []);
const open = (image) => {
setImage(image);
const open = async (image) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
let hash = image.hash;
if (!hash && dataurl.toObject(image.src)) {
const data = dataurl.toObject(image.src);
if (!data) return;
hash = await Sodium.hashFile({
data: data.data,
type: "base64",
uri: ""
});
}
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
setImage("file://" + path);
setLoading(false);
}, 100);
};
const close = () => {
@@ -59,44 +87,60 @@ const ImagePreview = () => {
backgroundColor: "black"
}}
>
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
renderHeader={() => (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: 30
}}
>
<IconButton
name="close"
color="white"
onPress={() => {
close();
{loading ? (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center"
}}
>
<ProgressBarComponent
indeterminate
color={colors.accent}
borderColor="transparent"
/>
</View>
) : (
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
renderHeader={() => (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: Platform.OS === "android" ? 30 : 0
}}
/>
</View>
)}
imageUrls={[
{
url: image
}
]}
/>
>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
/>
</View>
)}
imageUrls={[
{
url: image
}
]}
/>
)}
</View>
</BaseDialog>
)

View File

@@ -54,7 +54,6 @@ import Config from "react-native-config";
import { getGithubVersion } from "../../utils/github-version";
import notifee from "@notifee/react-native";
const Launcher = React.memo(
function Launcher() {
const colors = useThemeStore((state) => state.colors);
@@ -151,7 +150,7 @@ const Launcher = React.memo(
}, [introCompleted]);
const checkAppUpdateAvailable = async () => {
if (__DEV__) return;
if (__DEV__ || Config.isTesting === "true") return;
try {
const version =
Config.GITHUB_RELEASE === "true"

View File

@@ -24,8 +24,10 @@ import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import Notebook from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import useNavigationStore from "../../../stores/use-navigation-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
@@ -39,10 +41,6 @@ import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const navigateToTopic = (topic) => {
TopicNotes.navigate(topic, true);
};
function navigateToTag(item) {
const tag = db.tags.tag(item.id);
if (!tag) return;
@@ -55,30 +53,48 @@ const showActionSheet = (item) => {
function getNotebook(item) {
const isTrash = item.type === "trash";
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
const currentId = useNavigationStore.getState().currentScreen.id;
if (isTrash) return [];
const items = [];
const notebooks = db.relations.to(item, "notebook") || [];
return item.notebooks.reduce(function (prev, curr) {
if (prev && prev.length > 0) return prev;
const topicId = curr.topics[0];
const notebook = db.notebooks?.notebook(curr.id)?.data;
if (!notebook) return [];
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) return [];
for (let notebook of notebooks) {
if (items.length > 1) break;
if (notebook.id === currentId) continue;
items.push(notebook);
}
return [
{
title: `${notebook?.title} ${topic?.title}`,
notebook: notebook,
topic: topic
if (item.notebooks) {
for (let nb of item.notebooks) {
if (items.length > 1) break;
const notebook = db.notebooks?.notebook(nb.id)?.data;
if (!notebook) continue;
for (let topicId of nb.topics) {
if (items.length > 1) break;
if (topicId === currentId) continue;
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) continue;
items.push(topic);
}
];
}, []);
}
}
return items;
}
function getTags(item) {
const noteTags = item.tags?.slice(0, 3) || [];
const tags = [];
for (const tagName of noteTags) {
const tag = db.tags.tag(tagName);
if (!tag) continue;
tags.push(tag);
}
return tags;
}
const NoteItem = ({
item,
isTrash,
tags,
dateBy = "dateCreated",
noOpen = false
}) => {
@@ -88,11 +104,13 @@ const NoteItem = ({
);
const compactMode = notesListMode === "compact";
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
const notebooks = React.useMemo(() => getNotebook(item), [item]);
const _update = useRelationStore((state) => state.updater);
// eslint-disable-next-line react-hooks/exhaustive-deps
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
const reminders = db.relations.from(item, "reminder");
const reminder = getUpcomingReminder(reminders);
const _update = useRelationStore((state) => state.updater);
const noteColor = COLORS_NOTE[item.color?.toLowerCase()];
const tags = getTags(item);
return (
<>
<View
@@ -112,12 +130,17 @@ const NoteItem = ({
flexWrap: "wrap"
}}
>
{notebooks?.map((_item) => (
{notebooks?.map((item) => (
<Button
title={_item.title}
key={_item}
title={
item.title.length > 25
? item.title.slice(0, 25) + "..."
: item.title
}
tooltipText={item.title}
key={item.id}
height={25}
icon="book-outline"
icon={item.type === "topic" ? "bookmark" : "book-outline"}
type="grayBg"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
@@ -132,7 +155,13 @@ const NoteItem = ({
paddingHorizontal: 6,
marginBottom: 5
}}
onPress={() => navigateToTopic(_item.topic)}
onPress={() => {
if (item.type === "topic") {
TopicNotes.navigate(item, true);
} else {
Notebook.navigate(item);
}
}}
/>
))}

View File

@@ -100,7 +100,7 @@ export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
};
export const NoteWrapper = React.memo(
function NoteWrapper({ item, index, tags, dateBy, isSheet }) {
function NoteWrapper({ item, index, dateBy, isSheet }) {
const isTrash = item.type === "trash";
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
@@ -113,7 +113,7 @@ export const NoteWrapper = React.memo(
isSheet={isSheet}
item={item}
>
<NoteItem item={item} dateBy={dateBy} tags={tags} isTrash={isTrash} />
<NoteItem item={item} dateBy={dateBy} isTrash={isTrash} />
</SelectionWrapper>
);
},
@@ -125,10 +125,6 @@ export const NoteWrapper = React.memo(
return false;
}
if (JSON.stringify(prev.tags) !== JSON.stringify(next.tags)) {
return false;
}
if (prev.item !== next.item) {
return false;
}

View File

@@ -64,16 +64,7 @@ export const openNotebookTopic = (item) => {
negativeText: "Delete",
positivePress: async () => {
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate(
"Tags",
"Notes",
"Notebooks",
"Favorites",
"Trash",
"TaggedNotes",
"ColoredNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Restore successful",

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, { useRef } from "react";
import React, { useEffect, useRef } from "react";
import { RefreshControl, View } from "react-native";
import { FlashList } from "@shopify/flash-list";
import Animated, { FadeInDown } from "react-native-reanimated";
@@ -57,24 +57,10 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const totalNotes = getTotalNotes(item);
const tags =
item.tags
?.slice(0, 3)
?.map((item) => {
let tag = db.tags.tag(item);
if (!tag) return null;
return {
title: tag.title,
id: tag.id,
alias: tag.alias
};
})
.filter((t) => t !== null) || [];
return (
<Item
item={item}
tags={tags}
dateBy={dateBy}
index={index}
type={type}
@@ -103,7 +89,9 @@ const List = ({
ListHeader,
warning,
isSheet = false,
onMomentumScrollEnd
onMomentumScrollEnd,
handlers,
ScrollComponent
}) => {
const colors = useThemeStore((state) => state.colors);
const scrollRef = useRef();
@@ -151,14 +139,21 @@ const List = ({
[screen]
);
useEffect(() => {
eSendEvent(eScrollEvent, {
y: 0,
screen
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
let styles = {
width: "100%",
minHeight: 1,
minWidth: 1
};
const _keyExtractor = (item) => item.id || item.title;
const ListView = ScrollComponent ? ScrollComponent : FlashList;
return (
<>
<Animated.View
@@ -167,7 +162,8 @@ const List = ({
}}
entering={type === "search" ? undefined : FadeInDown}
>
<FlashList
<ListView
{...handlers}
style={styles}
ref={scrollRef}
testID={notesnook.list.id}

View File

@@ -85,13 +85,7 @@ const MergeConflicts = () => {
}
});
}
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
if (editorController.current?.note?.id === note.id) {
// reload the note in editor
eSendEvent(eOnLoadNote, {

View File

@@ -44,16 +44,7 @@ export default function NotePreview({ session, content, note }) {
async function restore() {
if (note && note.type === "trash") {
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate(
"Tags",
"Notes",
"Notebooks",
"Favorites",
"Trash",
"TaggedNotes",
"ColoredNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Restore successful",
@@ -73,13 +64,7 @@ export default function NotePreview({ session, content, note }) {
}
eSendEvent(eCloseSheet, "note_history");
eSendEvent(eCloseSheet);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
ToastEvent.show({
heading: "Note restored successfully",

View File

@@ -43,13 +43,7 @@ export const ColorTags = ({ item }) => {
let _note = db.notes.note(note.id).data;
setNote({ ..._note });
setColorNotes();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
eSendEvent(refreshNotesPage);
};
@@ -89,6 +83,7 @@ export const ColorTags = ({ item }) => {
flexWrap: "wrap",
flexGrow: isTablet ? undefined : 1,
paddingHorizontal: 12,
paddingRight: 0,
alignItems: "center",
justifyContent: isTablet ? "center" : "space-between"
}}

View File

@@ -16,10 +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 { Platform, View } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { presentSheet } from "../../services/event-manager";
@@ -37,7 +36,6 @@ import { Items } from "./items";
import Notebooks from "./notebooks";
import { Synced } from "./synced";
import { Tags, TagStrip } from "./tags";
const Line = ({ top = 6, bottom = 6 }) => {
const colors = useThemeStore((state) => state.colors);
return (
@@ -53,20 +51,11 @@ const Line = ({ top = 6, bottom = 6 }) => {
);
};
export const Properties = ({
close = () => {},
item,
buttons = [],
getRef
}) => {
export const Properties = ({ close = () => {}, item, buttons = [] }) => {
const colors = useThemeStore((state) => state.colors);
const alias = item.alias || item.title;
const isColor = !!COLORS_NOTE[item.title];
const onScrollEnd = () => {
getRef().current?.handleChildScrollEnd();
};
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
@@ -75,89 +64,92 @@ export const Properties = ({
);
}
return (
<ScrollView
nestedScrollEnabled
onMomentumScrollEnd={onScrollEnd}
<FlatList
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
style={{
backgroundColor: colors.bg,
paddingHorizontal: 0,
borderBottomRightRadius: DDS.isLargeTablet() ? 10 : 1,
borderBottomLeftRadius: DDS.isLargeTablet() ? 10 : 1,
maxHeight: "100%"
}}
>
<View
style={{
paddingHorizontal: 12,
marginTop: 5,
zIndex: 10
}}
>
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
data={[0]}
keyExtractor={() => "properties-scroll-item"}
renderItem={() => (
<View>
<View
style={{
paddingHorizontal: 12,
marginTop: 5,
zIndex: 10
}}
>
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
) : null}
{alias}
</Heading>
) : null}
{alias}
</Heading>
{item.type === "note" ? (
<TagStrip close={close} item={item} />
) : null}
{item.type === "note" ? <TagStrip close={close} item={item} /> : null}
{item.type === "reminder" ? (
<ReminderTime
reminder={item}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
backgroundColor: "transparent",
paddingHorizontal: 0
}}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>
<Line top={12} />
{item.type === "reminder" ? (
<ReminderTime
reminder={item}
<DateMeta item={item} />
<Line bottom={0} />
{item.type === "note" ? <Tags close={close} item={item} /> : null}
<View
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
backgroundColor: "transparent",
paddingHorizontal: 0
paddingHorizontal: 12
}}
>
<Notebooks note={item} close={close} />
</View>
<Items
item={item}
buttons={buttons}
close={() => {
close();
setTimeout(() => {
SearchService.updateAndSearch();
}, 1000);
}}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>
<Line top={12} />
<Synced item={item} close={close} />
<DevMode item={item} />
<DateMeta item={item} />
<Line bottom={0} />
{item.type === "note" ? <Tags close={close} item={item} /> : null}
<View
style={{
paddingHorizontal: 12
}}
>
{item.notebooks ? <Notebooks note={item} close={close} /> : null}
</View>
<Items
item={item}
buttons={buttons}
close={() => {
close();
setTimeout(() => {
SearchService.updateAndSearch();
}, 1000);
}}
/>
<Synced item={item} close={close} />
<DevMode item={item} />
{DDS.isTab ? (
<View
style={{
height: 20
}}
/>
) : null}
<SheetProvider context="properties" />
</ScrollView>
{DDS.isTab ? (
<View
style={{
height: 20
}}
/>
) : null}
<SheetProvider context="properties" />
</View>
)}
/>
);
};
@@ -187,6 +179,7 @@ Properties.present = (item, buttons = [], isSheet) => {
"lock-unlock",
"trash",
"remove-from-topic",
"remove-from-notebook",
"history",
"read-only",
"reminders",
@@ -224,7 +217,7 @@ Properties.present = (item, buttons = [], isSheet) => {
close={() => {
close();
}}
getRef={() => ref}
actionSheetRef={ref}
item={props[0]}
buttons={props[1]}
/>

View File

@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { 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";
@@ -85,8 +84,9 @@ export const Items = ({ item, buttons, close }) => {
</View>
);
const renderColumnItem = ({ item }) => (
const renderColumnItem = (item) => (
<Button
key={item.name + item.title}
buttonType={{
text: item.on
? colors.accent
@@ -108,7 +108,8 @@ export const Items = ({ item, buttons, close }) => {
/>
);
const renderTopBarItem = ({ item }) => {
const renderTopBarItem = (item, index) => {
const isLast = index === topBarItems.length;
return (
<PressableButton
onPress={item.func}
@@ -118,7 +119,7 @@ export const Items = ({ item, buttons, close }) => {
alignItems: "center",
width: topBarItemWidth,
marginBottom: 10,
marginRight: 10,
marginRight: isLast ? 0 : 10,
backgroundColor: "transparent"
}}
>
@@ -169,7 +170,7 @@ export const Items = ({ item, buttons, close }) => {
"lock-unlock",
"publish"
];
const bottomBarItems = data.filter(
const topBarItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) > -1
);
@@ -178,21 +179,19 @@ export const Items = ({ item, buttons, close }) => {
);
const topBarItemWidth =
(width - (bottomBarItems.length * 10 + 14)) / bottomBarItems.length;
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
return item.type === "note" ? (
<>
<FlatList
data={bottomBarItems}
keyExtractor={(item) => item.title}
<ScrollView
horizontal
disableVirtualization={true}
style={{
paddingHorizontal: 12,
paddingTop: 12
}}
renderItem={renderTopBarItem}
/>
>
{topBarItems.map(renderTopBarItem)}
</ScrollView>
<FlatList
data={bottomGridItems}
@@ -202,10 +201,8 @@ export const Items = ({ item, buttons, close }) => {
disableVirtualization={true}
style={{
marginTop: item.type !== "note" ? 10 : 0,
paddingTop: 10
}}
columnWrapperStyle={{
justifyContent: "flex-start"
paddingTop: 10,
marginLeft: 6
}}
contentContainerStyle={{
alignSelf: "center",
@@ -216,11 +213,6 @@ export const Items = ({ item, buttons, close }) => {
/>
</>
) : (
<FlatList
data={data}
keyExtractor={(item) => item.title}
renderItem={renderColumnItem}
disableVirtualization={true}
/>
<View data={data}>{data.map(renderColumnItem)}</View>
);
};

View File

@@ -35,8 +35,16 @@ export default function Notebooks({ note, close, full }) {
const colors = useThemeStore((state) => state.colors);
const notebooks = useNotebookStore((state) => state.notebooks);
function getNotebooks(item) {
if (!item.notebooks || item.notebooks.length < 1) return [];
let filteredNotebooks = [];
const relations = db.relations.to(note, "notebook");
filteredNotebooks.push(
...relations.map((notebook) => ({
...notebook,
topics: []
}))
);
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
for (let notebookReference of item.notebooks) {
let notebook = {
...(notebooks.find((item) => item.id === notebookReference.id) || {})
@@ -45,7 +53,14 @@ export default function Notebooks({ note, close, full }) {
notebook.topics = notebook.topics.filter((topic) => {
return notebookReference.topics.findIndex((t) => t === topic.id) > -1;
});
filteredNotebooks.push(notebook);
const index = filteredNotebooks.findIndex(
(item) => item.id === notebook.id
);
if (index > -1) {
filteredNotebooks[index].topics = notebook.topics;
} else {
filteredNotebooks.push(notebook);
}
}
}
return filteredNotebooks;
@@ -63,7 +78,6 @@ export default function Notebooks({ note, close, full }) {
if (!item) return;
TopicNotes.navigate(item, true);
};
const renderItem = (item) => (
<View
key={item.id}
@@ -78,7 +92,8 @@ export default function Notebooks({ note, close, full }) {
borderWidth: full ? 0 : 1,
borderColor: colors.nav,
borderRadius: 10,
backgroundColor: full ? "transparent" : colors.nav
backgroundColor: full ? "transparent" : colors.nav,
minHeight: 42
}}
>
<Icon
@@ -130,13 +145,7 @@ export default function Notebooks({ note, close, full }) {
note
);
useNotebookStore.getState().setNotebooks();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
ToastEvent.show({
heading: "Note removed from topic",
context: "local",
@@ -160,7 +169,7 @@ export default function Notebooks({ note, close, full }) {
</View>
);
return !note.notebooks || note.notebooks.length === 0 ? null : (
return noteNotebooks.length === 0 ? null : (
<View
style={{
width: "100%",

View File

@@ -94,7 +94,7 @@ export const Synced = ({ item, close }) => {
fontSize={SIZE.xs + 1}
title="Learn more"
height={30}
type="transparent"
type="grayAccent"
/>
</View>
) : null;

View File

@@ -21,11 +21,10 @@ import React from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { TaggedNotes } from "../../screens/notes/tagged";
import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { eOpenTagsDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import ManageTagsSheet from "../sheets/manage-tags";
import { Button } from "../ui/button";
import { ColorTags } from "./color-tags";
export const Tags = ({ item, close }) => {
@@ -41,14 +40,14 @@ export const Tags = ({ item, close }) => {
flexWrap: "wrap",
alignItems: "center",
paddingHorizontal: 12,
alignSelf: "center"
alignSelf: "center",
justifyContent: "space-between",
width: "100%"
}}
>
<Button
onPress={async () => {
close();
await sleep(300);
eSendEvent(eOpenTagsDialog, item);
ManageTagsSheet.present(item);
}}
buttonType={{
text: colors.accent
@@ -79,8 +78,8 @@ export const TagStrip = ({ item, close }) => {
alignItems: "center"
}}
>
{item.tags.map((item) =>
item ? <TagItem key={item} tag={item} close={close} /> : null
{item.tags.map((tag) =>
tag ? <TagItem key={tag} tag={tag} close={close} /> : null
)}
</View>
) : null;
@@ -102,7 +101,6 @@ const TagItem = ({ tag, close }) => {
marginTop: 0,
backgroundColor: "transparent"
};
return (
<Button
onPress={onPress}

View File

@@ -21,18 +21,18 @@ import React, { useCallback, useEffect } from "react";
import { BackHandler, Platform, View } from "react-native";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { eSendEvent, ToastEvent } from "../../services/event-manager";
import { ToastEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import SearchService from "../../services/search";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { eOpenMoveNoteDialog } from "../../utils/events";
import { deleteItems } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
@@ -64,13 +64,7 @@ export const SelectionHeader = React.memo(() => {
selectedItemsList.forEach((item) => {
db.notes.note(item.id).favorite();
});
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
clearSelection();
}
};
@@ -82,16 +76,7 @@ export const SelectionHeader = React.memo(() => {
noteIds.push(item.id);
});
await db.trash.restore(...noteIds);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Trash",
"Notebooks",
"Tags"
);
Navigation.queueRoutesForUpdate();
clearSelection();
ToastEvent.show({
@@ -118,16 +103,7 @@ export const SelectionHeader = React.memo(() => {
noteIds.push(item.id);
});
await db.trash.delete(...noteIds);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Trash",
"Notebooks",
"Tags"
);
Navigation.queueRoutesForUpdate();
clearSelection();
}
},
@@ -201,7 +177,7 @@ export const SelectionHeader = React.memo(() => {
}}
>
<Heading size={SIZE.md} color={colors.accent}>
{selectedItemsList.length + " Selected"}
{selectedItemsList.length}
</Heading>
</View>
</View>
@@ -237,7 +213,7 @@ export const SelectionHeader = React.memo(() => {
onPress={async () => {
//setSelectionMode(false);
await sleep(100);
eSendEvent(eOpenMoveNoteDialog);
MoveNoteSheet.present();
}}
customStyle={{
marginLeft: 10
@@ -264,36 +240,40 @@ export const SelectionHeader = React.memo(() => {
</>
)}
{screen === "TopicNotes" ? (
{screen === "TopicNotes" || screen === "Notebook" ? (
<IconButton
onPress={async () => {
if (selectedItemsList.length > 0) {
const currentTopic =
const currentScreen =
useNavigationStore.getState().currentScreen;
await db.notes.removeFromNotebook(
{
id: currentTopic.notebookId,
topic: currentTopic.id
},
...selectedItemsList.map((item) => item.id)
);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
if (screen === "Notebook") {
for (const item of selectedItemsList) {
await db.relations.unlink(
{ type: "notebook", id: currentScreen.id },
item
);
}
} else {
await db.notes.removeFromNotebook(
{
id: currentScreen.notebookId,
topic: currentScreen.id
},
...selectedItemsList.map((item) => item.id)
);
}
Navigation.queueRoutesForUpdate();
clearSelection();
}
}}
customStyle={{
marginLeft: 10
}}
tooltipText="Remove from topic"
tooltipText={`Remove from ${
screen === "Notebook" ? "notebook" : "topic"
}`}
tooltipPosition={4}
testID="select-minus"
color={colors.pri}

View File

@@ -36,7 +36,7 @@ import Paragraph from "../ui/typography/paragraph";
const SheetProvider = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [dialogData, setDialogData] = useState(null);
const [data, setData] = useState(null);
const actionSheetRef = useRef();
const editor = useRef({
refocus: false
@@ -52,42 +52,35 @@ const SheetProvider = ({ context = "global" }) => {
}, [close, open, visible]);
const open = useCallback(
async (data) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
if (visible || dialogData) {
setDialogData(null);
setVisible(false);
await sleep(0);
}
setDialogData(data);
async (payload) => {
if (!payload.context) payload.context = "global";
if (payload.context !== context) return;
setData(payload);
setVisible(true);
if (data.editor) {
if (payload.editor) {
editor.current.refocus = false;
if (editorState().keyboardState) {
// tiny.call(EditorWebView, tiny.cacheRange + tiny.blur);
editor.current.refocus = true;
}
}
},
[context, dialogData, visible]
[context]
);
useEffect(() => {
(async () => {
if (visible && dialogData) {
if (dialogData.editor) await sleep(100);
if (visible && data) {
if (data.editor) await sleep(100);
actionSheetRef.current?.setModalVisible(true);
return;
} else {
if (editor.current?.refocus) {
editorState().isFocused = true;
// tiny.call(EditorWebView, tiny.restoreRange + tiny.clearRange);
editor.current.refocus = false;
}
}
})();
}, [visible, dialogData]);
}, [visible, data]);
const close = useCallback(
(ctx) => {
@@ -98,34 +91,31 @@ const SheetProvider = ({ context = "global" }) => {
[context]
);
return !visible || !dialogData ? null : (
return !visible || !data ? null : (
<SheetWrapper
fwdRef={actionSheetRef}
gestureEnabled={!dialogData?.progress && !dialogData?.disableClosing}
closeOnTouchBackdrop={
!dialogData?.progress && !dialogData?.disableClosing
}
gestureEnabled={!data?.progress && !data?.disableClosing}
closeOnTouchBackdrop={!data?.progress && !data?.disableClosing}
onClose={() => {
dialogData.onClose && dialogData.onClose();
data.onClose && data.onClose();
setVisible(false);
setDialogData(null);
setData(null);
}}
bottomPadding={!data.noBottomPadding}
enableGesturesInScrollView={data.enableGesturesInScrollView}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginBottom:
!dialogData.progress &&
!dialogData.icon &&
!dialogData.title &&
!dialogData.paragraph
!data.progress && !data.icon && !data.title && !data.paragraph
? 0
: 10,
paddingHorizontal: 12
}}
>
{dialogData?.progress ? (
{data?.progress ? (
<ActivityIndicator
style={{
marginTop: 15
@@ -135,47 +125,48 @@ const SheetProvider = ({ context = "global" }) => {
/>
) : null}
{dialogData?.icon ? (
{data?.icon ? (
<Icon
color={colors[dialogData.iconColor] || colors.accent}
name={dialogData.icon}
color={colors[data.iconColor] || colors.accent}
name={data.icon}
size={50}
/>
) : null}
{dialogData?.title ? <Heading> {dialogData?.title}</Heading> : null}
{data?.title ? <Heading> {data?.title}</Heading> : null}
{dialogData?.paragraph ? (
{data?.paragraph ? (
<Paragraph style={{ textAlign: "center" }}>
{dialogData?.paragraph}
{data?.paragraph}
</Paragraph>
) : null}
</View>
{typeof dialogData.component === "function"
? dialogData.component(
{typeof data.component === "function"
? data.component(
actionSheetRef,
() => close(context),
(data) => {
if (!data) return;
setDialogData((prevData) => {
setData((prevData) => {
return {
...prevData,
...data
};
});
}
},
colors
)
: dialogData.component}
: data.component}
<View
style={{
paddingHorizontal: 12,
marginBottom: dialogData.valueArray ? 12 : 0
marginBottom: data.valueArray ? 12 : 0
}}
>
{dialogData.valueArray &&
dialogData.valueArray.map((v) => (
{data.valueArray &&
data.valueArray.map((v) => (
<Button
title={v}
type="gray"
@@ -197,12 +188,12 @@ const SheetProvider = ({ context = "global" }) => {
paddingHorizontal: 12
}}
>
{dialogData?.action ? (
{data?.action ? (
<Button
onPress={dialogData.action}
key={dialogData.actionText}
title={dialogData.actionText}
accentColor={dialogData.iconColor || "accent"}
onPress={data.action}
key={data.actionText}
title={data.actionText}
accentColor={data.iconColor || "accent"}
accentText="light"
type="accent"
height={45}
@@ -214,15 +205,14 @@ const SheetProvider = ({ context = "global" }) => {
/>
) : null}
{dialogData?.actionsArray &&
dialogData?.actionsArray.map((item) => (
{data?.actionsArray &&
data?.actionsArray.map((item) => (
<Button
onPress={item.action}
key={item.accentText}
title={item.actionText}
icon={item.icon && item.icon}
type={item.type || "accent"}
height={50}
style={{
marginBottom: 10
}}
@@ -231,7 +221,7 @@ const SheetProvider = ({ context = "global" }) => {
/>
))}
{dialogData?.learnMore ? (
{data?.learnMore ? (
<Paragraph
style={{
alignSelf: "center",
@@ -239,7 +229,7 @@ const SheetProvider = ({ context = "global" }) => {
textDecorationLine: "underline"
}}
size={SIZE.xs}
onPress={dialogData.learnMorePress}
onPress={data.learnMorePress}
color={colors.icon}
>
<Icon
@@ -247,7 +237,7 @@ const SheetProvider = ({ context = "global" }) => {
name="information-outline"
size={SIZE.xs}
/>{" "}
{dialogData.learnMore}
{data.learnMore}
</Paragraph>
) : null}
</View>

View File

@@ -25,41 +25,34 @@ import {
TouchableOpacity,
View
} from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { notesnook } from "../../../../e2e/test.ids";
import { useMenuStore } from "../../../stores/use-menu-store";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { db } from "../../../common/database";
import {
eCloseAddNotebookDialog,
eOpenAddNotebookDialog
} from "../../../utils/events";
import { DDS } from "../../../services/device-detection";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { ph, pv, SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import { IconButton } from "../../ui/icon-button";
import { Button } from "../../ui/button";
import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import { MoveNotes } from "../move-notes/movenote";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import { Toast } from "../../toast";
import { MoveNotes } from "../move-notes/movenote";
import { FlatList } from "react-native-actions-sheet";
let refs = [];
export class AddNotebookSheet extends React.Component {
constructor(props) {
super(props);
refs = [];
this.state = {
notebook: null,
visible: false,
topics: [],
notebook: props.notebook,
topics:
props.notebook?.topics?.map((item) => {
return item.title;
}) || [],
description: null,
titleFocused: false,
descFocused: false,
@@ -68,13 +61,14 @@ export class AddNotebookSheet extends React.Component {
editTopic: false,
loading: false
};
this.title = null;
this.description = null;
this.title = props.notebook?.title;
this.description = props.notebook?.description;
this.listRef;
this.prevItem = null;
this.prevIndex = null;
this.currentSelectedInput = null;
this.id = null;
this.id = props.notebook?.id;
this.backPressCount = 0;
this.currentInputValue = null;
this.titleRef;
@@ -83,57 +77,22 @@ export class AddNotebookSheet extends React.Component {
this.hiddenInput = createRef();
this.topicInputRef = createRef();
this.addingTopic = false;
this.actionSheetRef = createRef();
}
componentDidMount() {
eSubscribeEvent(eOpenAddNotebookDialog, this.open);
eSubscribeEvent(eCloseAddNotebookDialog, this.close);
this.actionSheetRef = props.actionSheetRef;
}
componentWillUnmount() {
eUnSubscribeEvent(eOpenAddNotebookDialog, this.open);
eUnSubscribeEvent(eCloseAddNotebookDialog, this.close);
refs = [];
}
open = (notebook) => {
refs = [];
if (notebook) {
let topicsList = [];
notebook.topics.forEach((item) => {
topicsList.push(item.title);
});
this.id = notebook.id;
this.title = notebook.title;
this.description = notebook.description;
this.setState({
topics: [...topicsList],
visible: true,
notebook: notebook
});
} else {
this.setState({
visible: true,
notebook: null
});
}
sleep(100).then(() => {
this.actionSheetRef.current?.show();
componentDidMount() {
sleep(300).then(() => {
!this.state.notebook && this.titleRef?.focus();
});
};
}
close = () => {
this.actionSheetRef.current?.hide();
refs = [];
this.prevIndex = null;
this.prevItem = null;
this.currentSelectedInput = null;
this.title = null;
this.description = null;
this.currentInputValue = null;
this.id = null;
this.props.close(true);
};
onDelete = (index) => {
@@ -223,6 +182,7 @@ export class AddNotebookSheet extends React.Component {
});
await db.notebooks.notebook(toEdit.id).topics.add(...nextTopics);
this.close();
} else {
newNotebookId = await db.notebooks.add({
title: this.title,
@@ -232,23 +192,9 @@ export class AddNotebookSheet extends React.Component {
});
}
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate(
"Notes",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
this.setState({
loading: false
});
this.close();
await sleep(300);
if (!notebook) {
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
}
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
};
onSubmit = (forward = true) => {
@@ -265,7 +211,7 @@ export class AddNotebookSheet extends React.Component {
topics: prevTopics
});
setTimeout(() => {
this.listRef.scrollToEnd({ animated: true });
this.listRef.current?.scrollToEnd?.({ animated: true });
}, 30);
this.currentInputValue = null;
} else {
@@ -289,7 +235,7 @@ export class AddNotebookSheet extends React.Component {
if (forward) {
setTimeout(() => {
this.listRef.scrollToEnd({ animated: true });
this.listRef.current?.scrollToEnd?.({ animated: true });
}, 30);
}
}
@@ -301,166 +247,138 @@ export class AddNotebookSheet extends React.Component {
render() {
const { colors } = this.props;
const { topics, visible, topicInputFocused, notebook } = this.state;
if (!visible) return null;
const { topics, topicInputFocused, notebook } = this.state;
return (
<SheetWrapper
onOpen={async () => {
this.topicsToDelete = [];
await sleep(300);
!this.state.notebook && this.titleRef?.focus();
<View
style={{
maxHeight: DDS.isTab ? "90%" : "96%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: 12
}}
fwdRef={this.actionSheetRef}
onClose={() => {
this.close();
this.setState({
visible: false,
topics: [],
descFocused: false,
titleFocused: false,
editTopic: false,
notebook: null
});
}}
statusBarTranslucent={false}
onRequestClose={this.close}
>
<View
<TextInput
ref={this.hiddenInput}
style={{
maxHeight: DDS.isTab ? "90%" : "96%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: 12
width: 1,
height: 1,
opacity: 0,
position: "absolute"
}}
>
<TextInput
ref={this.hiddenInput}
style={{
width: 1,
height: 1,
opacity: 0,
position: "absolute"
}}
blurOnSubmit={false}
/>
<DialogHeader
title={
notebook && notebook.dateCreated
? "Edit Notebook"
: "New Notebook"
blurOnSubmit={false}
/>
<DialogHeader
title={
notebook && notebook.dateCreated ? "Edit Notebook" : "New Notebook"
}
paragraph={
notebook && notebook.dateCreated
? "You are editing " + this.title + " notebook."
: "Notebooks are the best way to organize your notes."
}
/>
<Seperator half />
<Input
fwdRef={(ref) => (this.titleRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
this.title = value;
}}
placeholder="Enter a title"
onSubmit={() => {
this.descriptionRef.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : null}
/>
<Input
fwdRef={(ref) => (this.descriptionRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
this.description = value;
}}
placeholder="Describe your notebook."
onSubmit={() => {
this.topicInputRef.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.description : null}
/>
<Input
fwdRef={this.topicInputRef}
testID={notesnook.ids.dialogs.notebook.inputs.topic}
onChangeText={(value) => {
this.currentInputValue = value;
if (this.prevItem !== null) {
refs[this.prevIndex].setNativeProps({
text: value,
style: {
borderBottomColor: colors.accent
}
});
}
paragraph={
notebook && notebook.dateCreated
? "You are editing " + this.title + " notebook."
: "Notebooks are the best way to organize your notes."
}
/>
<Seperator half />
}}
returnKeyLabel="Done"
returnKeyType="done"
onSubmit={() => {
this.onSubmit();
}}
blurOnSubmit={false}
button={{
testID: "topic-add-button",
icon: this.state.editTopic ? "check" : "plus",
onPress: this.onSubmit,
color: topicInputFocused ? colors.accent : colors.icon
}}
placeholder="Add a topic"
/>
<Input
fwdRef={(ref) => (this.titleRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
this.title = value;
}}
placeholder="Enter a title"
onSubmit={() => {
this.descriptionRef.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : null}
/>
<Input
fwdRef={(ref) => (this.descriptionRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
this.description = value;
}}
placeholder="Describe your notebook."
onSubmit={() => {
this.topicInputRef.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.description : null}
/>
<Input
fwdRef={this.topicInputRef}
testID={notesnook.ids.dialogs.notebook.inputs.topic}
onChangeText={(value) => {
this.currentInputValue = value;
if (this.prevItem !== null) {
refs[this.prevIndex].setNativeProps({
text: value,
style: {
borderBottomColor: colors.accent
}
<FlatList
data={topics}
ref={(ref) => (this.listRef = ref)}
nestedScrollEnabled
keyExtractor={(item, index) => item + index.toString()}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={<View style={{ height: 50 }} />}
renderItem={({ item, index }) => (
<TopicItem
item={item}
onPress={(item, index) => {
this.prevIndex = index;
this.prevItem = item;
this.topicInputRef.current?.setNativeProps({
text: item
});
}
}}
returnKeyLabel="Done"
returnKeyType="done"
onSubmit={() => {
this.onSubmit();
}}
blurOnSubmit={false}
button={{
testID: "topic-add-button",
icon: this.state.editTopic ? "check" : "plus",
onPress: this.onSubmit,
color: topicInputFocused ? colors.accent : colors.icon
}}
placeholder="Add a topic"
/>
<FlatList
data={topics}
ref={(ref) => (this.listRef = ref)}
nestedScrollEnabled
keyExtractor={(item, index) => item + index.toString()}
onMomentumScrollEnd={() => {
this.actionSheetRef.current?.handleChildScrollEnd();
}}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={<View style={{ height: 50 }} />}
renderItem={({ item, index }) => (
<TopicItem
item={item}
onPress={(item, index) => {
this.prevIndex = index;
this.prevItem = item;
this.topicInputRef.current?.setNativeProps({
text: item
});
this.topicInputRef.current?.focus();
this.currentInputValue = item;
this.setState({
editTopic: true
});
}}
onDelete={this.onDelete}
index={index}
colors={colors}
/>
)}
/>
<Seperator />
<Button
width="100%"
height={50}
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated
? "Save changes"
: "Create notebook"
}
type="accent"
onPress={this.addNewNotebook}
/>
{/*
this.topicInputRef.current?.focus();
this.currentInputValue = item;
this.setState({
editTopic: true
});
}}
onDelete={this.onDelete}
index={index}
colors={colors}
/>
)}
/>
<Seperator />
<Button
width="100%"
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated
? "Save changes"
: "Create notebook"
}
type="accent"
onPress={this.addNewNotebook}
/>
{/*
{Platform.OS === 'ios' && (
<View
style={{
@@ -468,14 +386,24 @@ export class AddNotebookSheet extends React.Component {
}}
/>
)} */}
</View>
<Toast context="local" />
</SheetWrapper>
</View>
);
}
}
AddNotebookSheet.present = (notebook) => {
presentSheet({
component: (ref, close, _update, colors) => (
<AddNotebookSheet
actionSheetRef={ref}
notebook={notebook}
close={close}
colors={colors}
/>
)
});
};
const TopicItem = ({ item, index, colors, onPress, onDelete }) => {
const topicRef = (ref) => (refs[index] = ref);

View File

@@ -20,13 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { createContext, useContext } from "react";
export const SelectionContext = createContext({
enabled: false,
selected: [],
toggleSelection: (item) => null,
deselect: (item) => null,
select: (item) => null,
isSelected: (item) => null,
setMultiSelect: () => null,
deselectAll: () => null
});
export const SelectionProvider = SelectionContext.Provider;

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-gesture-handler";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { ListHeaderInputItem } from "./list-header-item.js";
@@ -27,6 +27,7 @@ export const FilteredList = ({
itemType,
onAddItem,
hasHeaderSearch,
listRef,
...restProps
}) => {
const [filtered, setFiltered] = useState(data);
@@ -56,6 +57,7 @@ export const FilteredList = ({
<FlatList
{...restProps}
data={filtered}
ref={listRef}
ListHeaderComponent={
hasHeaderSearch ? (
<ListHeaderInputItem
@@ -69,7 +71,6 @@ export const FilteredList = ({
}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
nestedScrollEnabled
/>
);
};

View File

@@ -17,90 +17,44 @@ 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, {
createRef,
useCallback,
useEffect,
useMemo,
useState
} from "react";
import React, { useCallback, useEffect, useMemo } from "react";
import { Keyboard, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
eSubscribeEvent,
eUnSubscribeEvent,
eSendEvent,
presentSheet,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eOpenMoveNoteDialog } from "../../../utils/events";
import { eOnTopicSheetUpdate } from "../../../utils/events";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { SelectionProvider } from "./context";
import { FilteredList } from "./filtered-list";
import { ListItem } from "./list-item";
import { useItemSelectionStore } from "./store";
const actionSheetRef = createRef();
const AddToNotebookSheet = () => {
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
function open(note) {
setNote(note);
setVisible(true);
actionSheetRef.current?.setModalVisible(true);
}
useEffect(() => {
eSubscribeEvent(eOpenMoveNoteDialog, open);
return () => {
eUnSubscribeEvent(eOpenMoveNoteDialog, open);
};
}, []);
const _onClose = () => {
setVisible(false);
setNote(null);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
};
return !visible ? null : (
<SheetWrapper fwdRef={actionSheetRef} onClose={_onClose}>
<MoveNoteComponent note={note} />
</SheetWrapper>
);
};
export default AddToNotebookSheet;
const MoveNoteComponent = ({ note }) => {
const MoveNoteSheet = ({ note, actionSheetRef }) => {
const colors = useThemeStore((state) => state.colors);
const [multiSelect, setMultiSelect] = useState(false);
const notebooks = useNotebookStore((state) =>
state.notebooks.filter((n) => n?.type === "notebook")
);
const [edited, setEdited] = useState(false);
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const [itemState, setItemState] = useState({});
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const onAddNotebook = async (title) => {
if (!title || title.trim().length === 0) {
@@ -159,13 +113,13 @@ const MoveNoteComponent = ({ note }) => {
(item) => {
switch (item.type) {
case "notebook": {
const noteIds = [];
for (let topic of item.topics) {
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
}
const notes = db.relations.from(item, "note");
if (notes.length === 0) return 0;
let count = 0;
selectedItemsList.forEach((item) =>
noteIds.indexOf(item.id) > -1 ? count++ : undefined
notes.findIndex((note) => note.id === item.id) > -1
? count++
: undefined
);
return count;
}
@@ -184,42 +138,55 @@ const MoveNoteComponent = ({ note }) => {
useEffect(() => {
resetItemState();
return () => {
useItemSelectionStore.getState().setMultiSelect(false);
useItemSelectionStore.getState().setItemState({});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const resetItemState = useCallback(
(state) => {
setItemState(() => {
const itemState = {};
const notebooks = db.notebooks.all;
for (let notebook of notebooks) {
itemState[notebook.id] = state
const itemState = {};
const notebooks = db.notebooks.all;
let count = 0;
for (let notebook of notebooks) {
itemState[notebook.id] = state
? state
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
? "selected"
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[notebook.id] === "selected") {
count++;
contextValue.select(notebook);
} else {
contextValue.deselect(notebook);
}
for (let topic of notebook.topics) {
itemState[topic.id] = state
? state
: areAllSelectedItemsInAllTopics(notebook, selectedItemsList) &&
getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
getSelectedNotesCountInItem(topic, selectedItemsList)
? "selected"
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[notebook.id] === "selected") {
contextValue.select(notebook);
}
for (let topic of notebook.topics) {
itemState[topic.id] = state
? state
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
getSelectedNotesCountInItem(topic, selectedItemsList)
? "selected"
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[topic.id] === "selected") {
contextValue.select(topic);
}
if (itemState[topic.id] === "selected") {
count++;
contextValue.select(topic);
} else {
contextValue.deselect(topic);
}
}
return itemState;
});
}
if (count > 1) {
useItemSelectionStore.getState().setMultiSelect(true);
} else {
useItemSelectionStore.getState().setMultiSelect(false);
}
useItemSelectionStore.getState().setItemState(itemState);
},
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
);
@@ -231,11 +198,11 @@ const MoveNoteComponent = ({ note }) => {
}
};
function areAllSelectedItemsInAllTopics(notebook, items) {
function areAllSelectedItemsInNotebook(notebook, items) {
const notes = db.relations.from(notebook, "note");
if (notes.length === 0) return false;
return items.every((item) => {
return notebook.topics.every((topic) => {
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
});
return notes.find((note) => note.id === item.id);
});
}
@@ -246,51 +213,26 @@ const MoveNoteComponent = ({ note }) => {
}
const updateItemState = useCallback(function (item, state) {
setItemState((itemState) => {
const mergeState = {
[item.id]: state
};
const notebooks = db.notebooks.all;
const notebook =
item.type === "notebook"
? item
: notebooks.find((n) => n.id === item.notebookId);
const intermediate = notebook.topics.some((topic) => {
return topic.id === item.id
? state === "selected"
: itemState[topic.id] === "selected";
});
if (intermediate) mergeState[notebook.id] = "intermediate";
const selected = notebook.topics.every((topic) => {
return topic.id === item.id
? state === "selected"
: itemState[topic.id] === "selected";
});
if (selected) mergeState[notebook.id] = "selected";
if (!selected && !intermediate) mergeState[notebook.id] = "deselected";
return {
...itemState,
...mergeState
};
const itemState = useItemSelectionStore.getState().itemState;
const mergeState = {
[item.id]: state
};
useItemSelectionStore.getState().setItemState({
...itemState,
...mergeState
});
}, []);
const contextValue = useMemo(
() => ({
enabled: multiSelect,
toggleSelection: (item) => {
setItemState((itemState) => {
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
return itemState;
});
const itemState = useItemSelectionStore.getState().itemState;
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
},
setMultiSelect: setMultiSelect,
deselect: (item) => {
updateItemState(item, "deselected");
},
@@ -301,7 +243,7 @@ const MoveNoteComponent = ({ note }) => {
resetItemState(state);
}
}),
[multiSelect, resetItemState, updateItemState]
[resetItemState, updateItemState]
);
const getItemFromId = (id) => {
@@ -314,38 +256,45 @@ const MoveNoteComponent = ({ note }) => {
};
const onSave = async () => {
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
const itemState = useItemSelectionStore.getState().itemState;
for (const id in itemState) {
const item = getItemFromId(id);
if (item.type === "notebook") continue;
const noteIds = selectedItemsList.map((n) => n.id);
if (itemState[id] === "selected") {
await db.notes.addToNotebook(
{
topic: item.id,
id: item.notebookId,
rebuildCache: true
},
...noteIds
);
if (item.type === "notebook") {
for (let noteId of noteIds) {
db.relations.add(item, { id: noteId, type: "note" });
}
} else {
await db.notes.addToNotebook(
{
topic: item.id,
id: item.notebookId,
rebuildCache: true
},
...noteIds
);
}
} else if (itemState[id] === "deselected") {
await db.notes.removeFromNotebook(
{
id: item.notebookId,
topic: item.id,
rebuildCache: true
},
...noteIds
);
if (item.type === "notebook") {
for (let noteId of noteIds) {
db.relations.unlink(item, { id: noteId, type: "note" });
}
} else {
await db.notes.removeFromNotebook(
{
id: item.notebookId,
topic: item.id,
rebuildCache: true
},
...noteIds
);
}
}
}
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
setNotebooks();
eSendEvent(eOnTopicSheetUpdate);
SearchService.updateAndSearch();
actionSheetRef.current?.hide();
};
@@ -397,34 +346,32 @@ const MoveNoteComponent = ({ note }) => {
/>
</View>
{multiSelect ? (
<View
<View
style={{
paddingHorizontal: 12
}}
>
<Button
title="Reset selection"
height={30}
style={{
paddingHorizontal: 12
alignSelf: "flex-start",
paddingHorizontal: 0,
width: "100%",
marginTop: 6
}}
>
<Button
title="Reset selection"
height={30}
style={{
alignSelf: "flex-start",
paddingHorizontal: 0
}}
onPress={() => {
resetItemState();
setMultiSelect(false);
}}
/>
</View>
) : null}
type="grayAccent"
onPress={() => {
resetItemState();
}}
/>
</View>
<SelectionProvider value={contextValue}>
<FilteredList
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
style={{
paddingHorizontal: 12
paddingHorizontal: 12,
maxHeight: dimensions.height * 0.85
}}
ListEmptyComponent={
notebooks.length > 0 ? null : (
@@ -450,12 +397,8 @@ const MoveNoteComponent = ({ note }) => {
item={item}
key={item.id}
index={index}
intermediate={itemState[item.id] === "intermediate"}
removed={
itemState[item.id] === "deselected" &&
getSelectedNotesCountInItem(item) > 0
}
isSelected={itemState[item.id] === "selected"}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
infoText={
<>
{item.topics.length === 1
@@ -465,25 +408,18 @@ const MoveNoteComponent = ({ note }) => {
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
selected: itemState[topic.id] === "selected",
intermediate: itemState[topic.id] === "intermediate",
isSelected: itemState[topic.id] === "selected",
removed:
itemState[topic.id] === "deselected" &&
getSelectedNotesCountInItem(topic) > 0,
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);
updateItemState(
notebooks.find((n) => n.id === item.notebookId),
"intermediate"
);
} else {
contextValue.deselect(item);
}
@@ -508,22 +444,35 @@ const MoveNoteComponent = ({ note }) => {
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: 200
}}
/>
}
ListFooterComponent={<View style={{ height: 20 }} />}
/>
</SelectionProvider>
</View>
</>
);
};
MoveNoteSheet.present = (note) => {
presentSheet({
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
enableGesturesInScrollView: false,
noBottomPadding: true
});
};
export default MoveNoteSheet;

View File

@@ -17,8 +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, { useState } from "react";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { db } from "../../../common/database";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
@@ -27,13 +29,78 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useSelectionContext } from "./context";
import { FilteredList } from "./filtered-list";
import { useItemSelectionStore } from "./store";
const _ListItem = ({
const SelectionIndicator = ({
item,
hasNotes,
selectItem,
onPress,
onChange
}) => {
const itemState = useItemSelectionStore((state) => state.itemState[item.id]);
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const isSelected = itemState === "selected";
const isIntermediate = itemState === "intermediate";
const isRemoved = !isSelected && hasNotes;
const colors = useThemeStore((state) => state.colors);
useEffect(() => {
onChange?.();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemState]);
return (
<IconButton
size={22}
customStyle={{
marginRight: 5,
width: 23,
height: 23
}}
color={
isRemoved
? colors.red
: isIntermediate || isSelected
? colors.accent
: colors.icon
}
onPress={() => {
if (multiSelect) return selectItem();
onPress?.(item);
}}
onLongPress={() => {
useItemSelectionStore.getState().setMultiSelect(true);
selectItem();
}}
testID={
isRemoved
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: isIntermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
name={
isRemoved
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: isIntermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
);
};
export const ListItem = ({
item,
index,
icon,
infoText,
intermediate,
hasSubList,
onPress,
onScrollEnd,
@@ -43,29 +110,49 @@ const _ListItem = ({
sublistItemType,
onAddItem,
getSublistItemProps,
removed,
isSelected,
hasHeaderSearch,
onAddSublistItem
onAddSublistItem,
hasNotes,
onChange,
sheetRef
}) => {
const { enabled, toggleSelection, setMultiSelect, select, deselect } =
useSelectionContext();
const { toggleSelection } = useSelectionContext();
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const [showSelectedIndicator, setShowSelectedIndicator] = useState(false);
const colors = useThemeStore((state) => state.colors);
const [expanded, setExpanded] = useState(false);
function selectItem() {
const currentState = isSelected;
toggleSelection(item);
if (item.type === "notebook") {
item.topics.forEach((item) => {
if (currentState) {
deselect(item);
} else {
select(item);
}
});
}
}
const getSelectedNotesCountInNotebookTopics = (item) => {
if (item.type === "topic") return;
let count = 0;
const noteIds = [];
for (let topic of item.topics) {
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
if (useItemSelectionStore.getState().itemState[topic.id] === "selected") {
count++;
}
}
useSelectionStore.getState().selectedItemsList.forEach((item) => {
if (noteIds.indexOf(item.id) > -1) {
count++;
}
});
return count;
};
useEffect(() => {
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
}, [item]);
const onChangeSubItem = () => {
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
};
return (
<View
style={{
@@ -77,12 +164,12 @@ const _ListItem = ({
<PressableButton
onPress={() => {
if (hasSubList) return setExpanded(!expanded);
if (enabled) return selectItem();
if (multiSelect) return selectItem();
onPress?.(item);
}}
type={type}
onLongPress={() => {
setMultiSelect(true);
useItemSelectionStore.getState().setMultiSelect(true);
selectItem();
}}
customStyle={{
@@ -107,46 +194,12 @@ const _ListItem = ({
alignItems: "center"
}}
>
<IconButton
size={22}
customStyle={{
marginRight: 5,
width: 23,
height: 23
}}
color={
removed
? colors.red
: intermediate || isSelected
? colors.accent
: colors.icon
}
onPress={() => {
if (item.type === "notebook") {
setMultiSelect(true);
}
selectItem();
if (enabled) return;
onPress?.(item);
}}
testID={
removed
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: intermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
name={
removed
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: intermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
<SelectionIndicator
hasNotes={hasNotes}
onPress={onPress}
item={item}
onChange={onChange}
selectItem={selectItem}
/>
<View>
{hasSubList && expanded ? (
@@ -165,14 +218,27 @@ const _ListItem = ({
<View
style={{
flexDirection: "row"
flexDirection: "row",
alignItems: "center"
}}
>
{showSelectedIndicator ? (
<View
style={{
backgroundColor: colors.accent,
width: 7,
height: 7,
borderRadius: 100,
marginRight: 12
}}
/>
) : null}
{onAddSublistItem ? (
<IconButton
name={"plus"}
testID="add-item-icon"
color={colors}
color={colors.pri}
size={SIZE.xl}
onPress={() => {
onAddSublistItem(item);
@@ -205,7 +271,7 @@ const _ListItem = ({
style={{
width: "95%",
alignSelf: "flex-end",
maxHeight: 500
maxHeight: 250
}}
itemType={sublistItemType}
hasHeaderSearch={hasHeaderSearch}
@@ -214,6 +280,7 @@ const _ListItem = ({
item={item}
{...getSublistItemProps(item)}
index={index}
onChange={onChangeSubItem}
onScrollEnd={onScrollEnd}
/>
)}
@@ -223,11 +290,3 @@ const _ListItem = ({
</View>
);
};
export const ListItem = React.memo(_ListItem, (prev, next) => {
if (prev.selected === undefined) return false;
if (prev.isSelected !== next.isSelected) return false;
if (prev.selected !== next.selected) return false;
if (prev.intermediate !== next.intermediate) return false;
return true;
});

View File

@@ -16,23 +16,27 @@ 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 create, { State } from "zustand";
function isMobile() {
var match = window.matchMedia || window.msMatchMedia;
if (match) {
var mq = match("(max-width: 480px)");
return mq.matches;
}
return false;
type SelectionItemState = Record<
string,
"intermediate" | "selected" | "deselected"
>;
export interface SelectionStore extends State {
itemState: SelectionItemState;
setItemState: (state: SelectionItemState) => void;
multiSelect: boolean;
setMultiSelect: (multiSelect: boolean) => void;
}
function isTablet() {
var match = window.matchMedia || window.msMatchMedia;
if (match) {
var mq = match("(min-width: 480px) and (max-width: 1000px)");
return mq.matches;
}
return false;
}
export { isMobile, isTablet };
export const useItemSelectionStore = create<SelectionStore>((set) => ({
itemState: {},
setItemState: (itemState) => {
set({
itemState
});
},
multiSelect: false,
setMultiSelect: (multiSelect) => set({ multiSelect })
}));

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { RefObject, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { ActionSheetRef } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import {
eSendEvent,
@@ -31,7 +31,7 @@ import { Button } from "../../ui/button";
import Input from "../../ui/input";
type ChangeEmailProps = {
actionSheetRef: RefObject<ActionSheet>;
actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
};

View File

@@ -277,7 +277,6 @@ const ExportNotesSheet = ({ notes, update }) => {
});
});
}}
height={50}
/>
<Button
title="Share"
@@ -301,7 +300,6 @@ const ExportNotesSheet = ({ notes, update }) => {
}).catch(console.log);
}
}}
height={50}
/>
<Button
title="Export in another format"
@@ -317,7 +315,6 @@ const ExportNotesSheet = ({ notes, update }) => {
setResult(null);
setExporting(false);
}}
height={50}
/>
</>
)}

View File

@@ -48,7 +48,6 @@ export const ShareComponent = ({ uri, name, padding }) => {
});
});
}}
height={50}
/>
<Button
title="Share"
@@ -65,7 +64,6 @@ export const ShareComponent = ({ uri, name, padding }) => {
shareFile: true
}).catch(console.log);
}}
height={50}
/>
</View>
);

View File

@@ -196,7 +196,6 @@ For example:
onPress={onPress}
title={loading ? null : "Submit"}
loading={loading}
height={50}
width="100%"
type="accent"
/>

View File

@@ -17,51 +17,32 @@ 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 { ScrollView, View } from "react-native";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useTagStore } from "../../../stores/use-tag-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseTagsDialog, eOpenTagsDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useCallback } from "react";
const ManageTagsSheet = () => {
const ManageTagsSheet = (props) => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
const [note, setNote] = useState(props.note);
const allTags = useTagStore((state) => state.tags);
const [tags, setTags] = useState([]);
const [query, setQuery] = useState(null);
const inputRef = useRef();
const actionSheetRef = useRef();
const [focus, setFocus] = useState(false);
useEffect(() => {
eSubscribeEvent(eOpenTagsDialog, open);
eSubscribeEvent(eCloseTagsDialog, close);
return () => {
eUnSubscribeEvent(eOpenTagsDialog, open);
eUnSubscribeEvent(eCloseTagsDialog, close);
};
}, [open]);
useEffect(() => {
if (visible) {
sortTags();
}
}, [allTags, note, query, sortTags, visible]);
sortTags();
}, [allTags, note, query, sortTags]);
const sortTags = useCallback(() => {
let _tags = [...allTags];
@@ -88,26 +69,9 @@ const ManageTagsSheet = () => {
setTags(combinedTags);
}, [allTags, note, query]);
const open = useCallback(
(item) => {
setNote(item);
useTagStore.getState().setTags();
sortTags();
setVisible(true);
},
[sortTags]
);
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
setQuery(null);
actionSheetRef.current?.hide();
};
useTagStore.getState().setTags();
}, []);
const onSubmit = async () => {
let _query = query;
@@ -139,115 +103,98 @@ const ManageTagsSheet = () => {
});
}
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
Navigation.queueRoutesForUpdate();
};
return !visible ? null : (
<SheetWrapper
centered={false}
fwdRef={actionSheetRef}
onOpen={async () => {
await sleep(300);
inputRef.current?.focus();
}}
onClose={async () => {
setQuery(null);
setVisible(false);
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12,
minHeight: focus ? "100%" : "60%"
}}
>
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12,
minHeight: "60%"
<Input
button={{
icon: "magnify",
color: colors.accent,
size: SIZE.lg
}}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(db.tags.sanitize(v));
}}
onFocusInput={() => {
setFocus(true);
}}
onBlurInput={() => {
setFocus(false);
}}
onSubmit={onSubmit}
placeholder="Search or add a tag"
/>
<ScrollView
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
>
<Input
button={{
icon: "magnify",
color: colors.accent,
size: SIZE.lg
}}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(db.tags.sanitize(v));
}}
onSubmit={onSubmit}
height={50}
placeholder="Search or add a tag"
/>
{query && query !== tags[0]?.title ? (
<PressableButton
key={"query_item"}
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12
}}
onPress={onSubmit}
type="accent"
>
<Heading size={SIZE.sm} color={colors.light}>
Add {'"' + "#" + query + '"'}
</Heading>
<Icon name="plus" color={colors.light} size={SIZE.lg} />
</PressableButton>
) : null}
{!allTags || allTags.length === 0 ? (
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.icon}>
#
</Heading>
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
You do not have any tags.
</Paragraph>
</View>
) : null}
<ScrollView
nestedScrollEnabled
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
>
{query && query !== tags[0]?.title ? (
<PressableButton
key={"query_item"}
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12
}}
onPress={onSubmit}
type="accent"
>
<Heading size={SIZE.sm} color={colors.light}>
Add {'"' + "#" + query + '"'}
</Heading>
<Icon name="plus" color={colors.light} size={SIZE.lg} />
</PressableButton>
) : null}
{!allTags || allTags.length === 0 ? (
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.icon}>
#
</Heading>
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
You do not have any tags.
</Paragraph>
</View>
) : null}
{tags.map((item) => (
<TagItem
key={item.title}
tag={item}
note={note}
setNote={setNote}
/>
))}
</ScrollView>
</View>
</SheetWrapper>
{tags.map((item) => (
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
))}
</ScrollView>
</View>
);
};
ManageTagsSheet.present = (note) => {
presentSheet({
component: (ref) => {
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
}
});
};
export default ManageTagsSheet;
const TagItem = ({ tag, note, setNote }) => {
@@ -269,13 +216,7 @@ const TagItem = ({ tag, note, setNote }) => {
console.error(e);
}
setTimeout(() => {
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
}, 1);
};

View File

@@ -17,11 +17,11 @@ 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 { FlashList } from "@shopify/flash-list";
import { NotebookType, NoteType, TopicType } from "app/utils/types";
import React, { RefObject, useState } from "react";
import { Platform, useWindowDimensions, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import { db } from "../../../common/database";
import {
eSendEvent,
@@ -58,7 +58,7 @@ export const MoveNotes = ({
}: {
notebook: NotebookType;
selectedTopic?: TopicType;
fwdRef: RefObject<ActionSheet>;
fwdRef: RefObject<ActionSheetRef>;
}) => {
const colors = useThemeStore((state) => state.colors);
const [currentNotebook, setCurrentNotebook] = useState(notebook);
@@ -122,15 +122,7 @@ export const MoveNotes = ({
db.notebooks?.notebook(currentNotebook.id).data as NotebookType
);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook",
"Notebooks"
);
Navigation.queueRoutesForUpdate();
return true;
};
@@ -277,10 +269,6 @@ export const MoveNotes = ({
)}
<FlashList
nestedScrollEnabled
onMomentumScrollEnd={() => {
fwdRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
<View
style={{
@@ -322,15 +310,7 @@ export const MoveNotes = ({
},
...selectedNoteIds
);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook",
"Notebooks"
);
Navigation.queueRoutesForUpdate();
SearchService.updateAndSearch();
eSendEvent(eCloseSheet);
}}
@@ -345,7 +325,7 @@ export const MoveNotes = ({
MoveNotes.present = (notebook: NotebookType, topic: TopicType) => {
presentSheet({
component: (ref: RefObject<ActionSheet>) => (
component: (ref: RefObject<ActionSheetRef>) => (
<MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} />
)
});

View File

@@ -93,7 +93,13 @@ NewFeature.present = () => {
});
return;
}
if (version && version === getVersion()) return false;
if (!version || version === getVersion()) {
SettingsService.set({
version: getVersion()
});
return false;
}
SettingsService.set({
version: getVersion()
});

View File

@@ -18,22 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useAttachmentStore } from "../../../stores/use-attachment-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import {
eClosePublishNoteDialog,
eOpenPublishNoteDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { SIZE } from "../../../utils/size";
import DialogHeader from "../../dialog/dialog-header";
@@ -41,75 +33,38 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
let passwordValue = null;
const PublishNoteSheet = () => {
const PublishNoteSheet = ({ note: item, update }) => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const actionSheetRef = useRef();
const loading = useAttachmentStore((state) => state.loading);
const [selfDestruct, setSelfDestruct] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const [note, setNote] = useState(null);
const [note, setNote] = useState(item);
const [publishing, setPublishing] = useState(false);
const publishUrl =
note &&
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
const isPublished = note && db?.monographs.isPublished(note?.id);
const pwdInput = useRef();
useEffect(() => {
eSubscribeEvent(eOpenPublishNoteDialog, open);
eSubscribeEvent(eClosePublishNoteDialog, close);
return () => {
eUnSubscribeEvent(eOpenPublishNoteDialog, open);
eUnSubscribeEvent(eClosePublishNoteDialog, close);
};
}, []);
const open = (item) => {
if (!item) return;
setNote(item);
setPublishing(false);
setSelfDestruct(false);
setIsLocked(false);
setVisible(true);
passwordValue = null;
};
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
passwordValue = null;
actionSheetRef.current?.hide();
};
const passwordValue = useRef();
const publishNote = async () => {
if (publishing) return;
setPublishing(true);
setPublishLoading(true);
try {
if (note?.id) {
if (isLocked && !passwordValue) return;
await db.monographs.publish(note.id, {
selfDestruct: selfDestruct,
password: isLocked && passwordValue
password: isLocked && passwordValue.current
});
setNote(db.notes.note(note.id)?.data);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
setPublishLoading(false);
}
} catch (e) {
ToastEvent.show({
@@ -120,23 +75,22 @@ const PublishNoteSheet = () => {
});
}
setPublishing(false);
setPublishLoading(false);
};
const setPublishLoading = (value) => {
setPublishing(value);
};
const deletePublishedNote = async () => {
if (publishing) return;
setPublishing(true);
setPublishLoading(true);
try {
if (note?.id) {
await db.monographs.unpublish(note.id);
setNote(db.notes.note(note.id)?.data);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
setPublishLoading(false);
}
} catch (e) {
ToastEvent.show({
@@ -147,267 +101,281 @@ const PublishNoteSheet = () => {
});
}
actionSheetRef.current?.hide();
setPublishing(false);
setPublishLoading(false);
};
return !visible ? null : (
<SheetWrapper
centered={false}
fwdRef={actionSheetRef}
closeOnTouchBackdrop={!publishing}
gestureEnabled={!publishing}
onClose={async () => {
passwordValue = null;
setVisible(false);
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<DialogHeader
title={note?.title}
paragraph={`Anyone with the link${
isLocked ? " and password" : ""
} of the published note can view it.`}
/>
<DialogHeader
title={note?.title}
paragraph={`Anyone with the link${
isLocked ? " and password" : ""
} of the published note can view it.`}
/>
{publishing ? (
<View
{publishing ? (
<View
style={{
justifyContent: "center",
alignContent: "center",
height: 150,
width: "100%"
}}
>
<ActivityIndicator size={25} color={colors.accent} />
<Paragraph
style={{
justifyContent: "center",
alignContent: "center",
height: 150,
width: "100%"
textAlign: "center"
}}
>
<ActivityIndicator size={25} color={colors.accent} />
<Paragraph
Please wait...
{loading && loading.current && loading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
})`
: ""}
</Paragraph>
</View>
) : (
<>
{isPublished && (
<View
style={{
textAlign: "center"
flexDirection: "row",
alignItems: "center",
marginTop: 10,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
}}
>
Please wait...
{loading && loading.current && loading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
})`
: ""}
</Paragraph>
</View>
) : (
<>
{isPublished && (
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 15,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
width: "100%",
flexShrink: 1
}}
>
<View
<Heading size={SIZE.md}>Published at:</Heading>
<Paragraph size={SIZE.sm} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl, colors.accent);
} catch (e) {
console.error(e);
}
}}
size={SIZE.xs}
style={{
width: "100%",
flexShrink: 1
marginTop: 5,
color: colors.pri
}}
>
<Heading size={SIZE.sm}>Published at:</Heading>
<Paragraph size={SIZE.xs} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl, colors.accent);
} catch (e) {
console.error(e);
}
}}
size={SIZE.xs}
style={{
marginTop: 5,
color: colors.pri
}}
>
<Icon color={colors.accent} name="open-in-new" /> Open in
browser
</Paragraph>
</View>
<IconButton
onPress={() => {
Clipboard.setString(publishUrl);
ToastEvent.show({
heading: "Note publish url copied",
type: "success",
context: "local"
});
}}
color={colors.accent}
size={SIZE.lg}
name="content-copy"
/>
<Icon color={colors.accent} name="open-in-new" /> Open in
browser
</Paragraph>
</View>
)}
<Seperator />
<TouchableOpacity
<IconButton
onPress={() => {
Clipboard.setString(publishUrl);
ToastEvent.show({
heading: "Note publish url copied",
type: "success",
context: "local"
});
}}
color={colors.accent}
size={SIZE.lg}
name="content-copy"
/>
</View>
)}
<TouchableOpacity
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5,
marginTop: 10
}}
>
<IconButton
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 10
}}
>
<IconButton
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
color={isLocked ? colors.accent : colors.icon}
size={SIZE.lg}
name={
isLocked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Password protection</Heading>
<Paragraph>
Published note can only be viewed by someone with the
password.
</Paragraph>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<IconButton
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.lg}
name={
selfDestruct
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
</View>
</TouchableOpacity>
color={isLocked ? colors.accent : colors.icon}
size={SIZE.xl}
name={
isLocked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
alignSelf: "center",
marginTop: 10
flexShrink: 1
}}
>
{isLocked ? (
<>
<Input
fwdRef={pwdInput}
onChangeText={(value) => (passwordValue = value)}
blurOnSubmit
secureTextEntry
defaultValue={passwordValue}
placeholder="Enter Password"
/>
<Seperator half />
</>
) : null}
<Heading size={SIZE.md}>Password protection</Heading>
<Paragraph>
Published note can only be viewed by someone with the password.
</Paragraph>
</View>
</TouchableOpacity>
<Button
onPress={publishNote}
fontSize={SIZE.md}
width="100%"
style={{
marginTop: 10
}}
height={50}
type="accent"
title={isPublished ? "Update published note" : "Publish note"}
/>
<TouchableOpacity
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5
}}
>
<IconButton
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.xl}
name={
selfDestruct
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
</View>
</TouchableOpacity>
<View
style={{
width: "100%",
alignSelf: "center",
marginTop: 10
}}
>
{isLocked ? (
<>
<Input
fwdRef={pwdInput}
onChangeText={(value) => (passwordValue.current = value)}
blurOnSubmit
secureTextEntry
defaultValue={passwordValue.current}
placeholder="Enter Password"
/>
<Seperator half />
</>
) : null}
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "center"
}}
>
{isPublished && (
<>
<Seperator half />
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
width="100%"
height={50}
type="error"
title="Unpublish note"
title="Unpublish"
style={{
width: "49%"
}}
/>
</>
)}
<Seperator half />
<Button
onPress={publishNote}
fontSize={SIZE.md}
style={{
width: isPublished ? "49%" : 250,
borderRadius: isPublished ? 5 : 100
}}
type="accent"
title={isPublished ? "Update" : "Publish"}
/>
</View>
</>
)}
</View>
</>
)}
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 5,
textDecorationLine: "underline"
}}
onPress={async () => {
try {
await openLinkInBrowser(
"https://docs.notesnook.com/monographs/",
colors.accent
);
} catch (e) {
console.error(e);
}
}}
>
Learn more about Notesnook Monograph
</Paragraph>
</View>
</SheetWrapper>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10,
textDecorationLine: "underline"
}}
onPress={async () => {
try {
await openLinkInBrowser(
"https://docs.notesnook.com/monographs/",
colors.accent
);
} catch (e) {
console.error(e);
}
}}
>
Learn more about Notesnook Monograph
</Paragraph>
</View>
);
};
PublishNoteSheet.present = (note) => {
presentSheet({
component: (ref, close, update) => (
<PublishNoteSheet
actionSheetRef={ref}
close={close}
update={update}
note={note}
/>
)
});
};
export default PublishNoteSheet;

View File

@@ -96,7 +96,6 @@ const RateAppSheet = () => {
onPress={rateApp}
fontSize={SIZE.md}
width="100%"
height={50}
type="accent"
title="Rate now (It takes only a second)"
/>
@@ -121,14 +120,12 @@ const RateAppSheet = () => {
fontSize={SIZE.md}
type="error"
width="48%"
height={50}
title="Never"
/>
<Button
onPress={onClose}
fontSize={SIZE.md}
width="48%"
height={50}
type="grayBg"
title="Later"
/>

View File

@@ -298,7 +298,6 @@ class RecoveryKeySheet extends React.Component {
width="100%"
type="grayAccent"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
<Button
@@ -308,7 +307,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
fontSize={SIZE.md}
icon="qrcode"
height={50}
/>
<Seperator />
<Button
@@ -318,7 +316,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="text"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -329,7 +326,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="cloud"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -349,7 +345,6 @@ class RecoveryKeySheet extends React.Component {
<Button
title="I have saved the key."
width="100%"
height={50}
type="error"
fontSize={SIZE.md}
onPress={this.close}

View File

@@ -18,12 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
PresentSheetOptions,
presentSheet
presentSheet,
PresentSheetOptions
} from "../../../services/event-manager";
import { Reminder } from "../../../services/notifications";
import { useRelationStore } from "../../../stores/use-relation-store";
@@ -37,7 +38,7 @@ import { PressableButtonProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
type RelationsListProps = {
actionSheetRef: RefObject<ActionSheet>;
actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
item: { id: string; type: string };
@@ -62,8 +63,6 @@ const IconsByType = {
export const RelationsList = ({
actionSheetRef,
close,
update,
item,
referenceType,
relationType,
@@ -75,7 +74,6 @@ export const RelationsList = ({
const [items, setItems] = useState<Reminder[]>([]);
const colors = useThemeStore((state) => state.colors);
const hasNoRelations = !items || items.length === 0;
useEffect(() => {
setItems(
db.relations?.[relationType]?.(
@@ -123,6 +121,7 @@ export const RelationsList = ({
) : (
<List
listData={items}
ScrollComponent={FlashList}
loading={false}
type={referenceType}
headerProps={null}

View File

@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import dayjs from "dayjs";
import React, { RefObject } from "react";
import { ScrollView, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
@@ -36,7 +36,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheet>;
actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
reminder?: Reminder;

View File

@@ -17,14 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { RefObject, useRef, useState } from "react";
import {
Platform,
ScrollView,
TextInput,
useWindowDimensions,
View
} from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { Platform, TextInput, View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
presentSheet,
@@ -44,13 +38,13 @@ import Notifications, { Reminder } from "../../../services/notifications";
import PremiumService from "../../../services/premium";
import SettingsService from "../../../services/settings";
import { useRelationStore } from "../../../stores/use-relation-store";
import { ReminderTime } from "../../ui/reminder-time";
import Paragraph from "../../ui/typography/paragraph";
import { NoteType } from "../../../utils/types";
import { Dialog } from "../../dialog";
import { ReminderTime } from "../../ui/reminder-time";
import Paragraph from "../../ui/typography/paragraph";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheet>;
actionSheetRef: RefObject<ActionSheetRef>;
close?: (ctx?: string) => void;
update?: (options: PresentSheetOptions) => void;
reminder?: Reminder;
@@ -117,13 +111,15 @@ export default function ReminderSheet({
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [repeatFrequency, setRepeatFrequency] = useState(1);
const title = useRef<string | undefined>(reminder?.title);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const { height } = useWindowDimensions();
const referencedItem = reference
? (db.notes?.note(reference.id)?.data as NoteType)
: null;
const title = useRef<string | undefined>(
reminder?.title || referencedItem?.title
);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>();
const showDatePicker = () => {
setDatePickerVisibility(true);
@@ -134,9 +130,10 @@ export default function ReminderSheet({
};
const handleConfirm = (date: Date) => {
hideDatePicker();
setDate(date);
console.log(date);
timer.current = setTimeout(() => {
hideDatePicker();
setDate(date);
}, 50);
};
function nth(n: number) {
return (
@@ -211,15 +208,7 @@ export default function ReminderSheet({
});
}
Notifications.scheduleNotification(_reminder as Reminder);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"Notes",
"NotesPage",
"Reminders",
"Favorites",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
close?.();
} catch (e) {
@@ -233,13 +222,8 @@ export default function ReminderSheet({
paddingHorizontal: 12
}}
>
<Dialog context="local"/>
<ScrollView
onScrollEndDrag={() => actionSheetRef.current?.handleChildScrollEnd()}
style={{
maxHeight: height * 0.85
}}
>
<Dialog context="local" />
<ScrollView keyboardShouldPersistTaps="always">
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
@@ -249,7 +233,9 @@ export default function ReminderSheet({
/>
<Input
defaultValue={reminder ? reminder?.description : referencedItem?.headline}
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
placeholder="Add a quick note"
onChangeText={(text) => (details.current = text)}
containerStyle={{
@@ -440,6 +426,7 @@ export default function ReminderSheet({
<DatePicker
date={date}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm}
textColor={colors.night ? "#ffffff" : "#000000"}
fadeToColor={colors.bg}
@@ -561,16 +548,16 @@ export default function ReminderSheet({
alignSelf: "flex-start"
}}
/>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</ScrollView>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View>
);
}
@@ -582,6 +569,7 @@ ReminderSheet.present = (
) => {
presentSheet({
context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
component: (ref, close, update) => (
<ReminderSheet
actionSheetRef={ref}

View File

@@ -18,10 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { EVENTS } from "@notesnook/core/common";
import React, { createRef, useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import DocumentPicker from "react-native-document-picker";
import { FlatList } from "react-native-gesture-handler";
import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
@@ -35,7 +35,7 @@ import { initialize } from "../../../stores";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { sleep, timeConverter } from "../../../utils/time";
import { timeConverter } from "../../../utils/time";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
@@ -44,12 +44,18 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
const actionSheetRef = createRef();
let RNFetchBlob;
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
const [restoring, setRestoring] = useState(false);
const sheet = useRef();
useEffect(() => {
const open = async () => {
setVisible(true);
setTimeout(() => {
sheet.current?.show();
}, 1);
};
eSubscribeEvent(eOpenRestoreDialog, open);
eSubscribeEvent(eCloseRestoreDialog, close);
return () => {
@@ -58,21 +64,15 @@ const RestoreDataSheet = () => {
};
}, [close]);
const open = async () => {
setVisible(true);
await sleep(30);
actionSheetRef.current?.setModalVisible(true);
};
const close = useCallback(() => {
if (restoring) {
showIsWorking();
return;
}
actionSheetRef.current?.setModalVisible(false);
sheet.current?.hide();
setTimeout(() => {
setVisible(false);
}, 300);
}, 150);
}, [restoring]);
const showIsWorking = () => {
@@ -86,15 +86,19 @@ const RestoreDataSheet = () => {
return !visible ? null : (
<SheetWrapper
fwdRef={actionSheetRef}
fwdRef={sheet}
gestureEnabled={!restoring}
closeOnTouchBackdrop={!restoring}
onClose={close}
onClose={() => {
setVisible(false);
close();
}}
>
<RestoreDataComponent
close={close}
restoring={restoring}
setRestoring={setRestoring}
actionSheetRef={sheet}
/>
<Toast context="local" />
</SheetWrapper>
@@ -109,7 +113,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false);
const [progress, setProgress] = useState();
useEffect(() => {
const subscription = db.eventManager.subscribe(
EVENTS.migrationProgress,
@@ -123,7 +126,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}, []);
useEffect(() => {
checkBackups();
setTimeout(() => {
checkBackups();
}, 300);
}, []);
const restore = async (item) => {
@@ -362,10 +367,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View>
<Seperator half />
<FlatList
nestedScrollEnabled
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
!restoring ? (
loading ? (

View File

@@ -32,6 +32,7 @@ import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
const Sort = ({ type, screen }) => {
const colors = useThemeStore((state) => state.colors);
const isTopicSheet = screen === "TopicSheet";
const [groupOptions, setGroupOptions] = useState(
db.settings.getGroupOptions(type)
);
@@ -41,7 +42,7 @@ const Sort = ({ type, screen }) => {
layoutmanager.withSpringAnimation(600);
setGroupOptions(_groupOptions);
setTimeout(() => {
Navigation.queueRoutesForUpdate(screen);
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
eSendEvent("groupOptionsUpdate");
eSendEvent(refreshNotesPage);
}, 1);
@@ -52,6 +53,9 @@ const Sort = ({ type, screen }) => {
...groupOptions,
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);
};
@@ -100,7 +104,7 @@ const Sort = ({ type, screen }) => {
height={25}
iconPosition="right"
fontSize={SIZE.sm - 1}
type="grayBg"
type="transparent"
buttonType={{
text: colors.accent
}}
@@ -119,7 +123,7 @@ const Sort = ({ type, screen }) => {
flexDirection: "row",
justifyContent: "flex-start",
flexWrap: "wrap",
borderBottomWidth: 1,
borderBottomWidth: isTopicSheet ? 0 : 1,
borderBottomColor: colors.nav,
marginBottom: 12,
paddingHorizontal: 12,
@@ -166,6 +170,9 @@ const Sort = ({ type, screen }) => {
...groupOptions,
sortBy: type === "trash" ? "dateDeleted" : item
};
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);
}}
iconSize={SIZE.md}
@@ -175,64 +182,70 @@ const Sort = ({ type, screen }) => {
)}
</View>
<Heading
style={{
marginLeft: 12
}}
size={SIZE.lg}
>
Group by
</Heading>
<Seperator />
<View
style={{
borderRadius: 0,
flexDirection: "row",
flexWrap: "wrap",
paddingHorizontal: 12
}}
>
{Object.keys(GROUP).map((item) => (
<Button
key={item}
testID={"btn-" + item}
type={groupOptions.groupBy === GROUP[item] ? "grayBg" : "gray"}
buttonType={{
text:
groupOptions.groupBy === GROUP[item]
? colors.accent
: colors.icon
}}
onPress={async () => {
let _groupOptions = {
...groupOptions,
groupBy: GROUP[item]
};
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "asc";
} else {
if (groupOptions.sortBy === "title") {
_groupOptions.sortBy = "dateEdited";
_groupOptions.sortDirection = "desc";
}
}
updateGroupOptions(_groupOptions);
}}
height={40}
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
title={item.slice(0, 1).toUpperCase() + item.slice(1, item.length)}
{isTopicSheet ? null : (
<>
<Heading
style={{
paddingHorizontal: 8,
marginBottom: 10,
marginRight: 10
marginLeft: 12
}}
/>
))}
</View>
size={SIZE.lg}
>
Group by
</Heading>
<Seperator />
<View
style={{
borderRadius: 0,
flexDirection: "row",
flexWrap: "wrap",
paddingHorizontal: 12
}}
>
{Object.keys(GROUP).map((item) => (
<Button
key={item}
testID={"btn-" + item}
type={groupOptions.groupBy === GROUP[item] ? "grayBg" : "gray"}
buttonType={{
text:
groupOptions.groupBy === GROUP[item]
? colors.accent
: colors.icon
}}
onPress={async () => {
let _groupOptions = {
...groupOptions,
groupBy: GROUP[item]
};
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "asc";
} else {
if (groupOptions.sortBy === "title") {
_groupOptions.sortBy = "dateEdited";
_groupOptions.sortDirection = "desc";
}
}
updateGroupOptions(_groupOptions);
}}
height={40}
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
title={
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
}
style={{
paddingHorizontal: 8,
marginBottom: 10,
marginRight: 10
}}
/>
))}
</View>
</>
)}
</View>
);
};

View File

@@ -0,0 +1,555 @@
/*
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 qclone from "qclone";
import React, {
createContext,
RefObject,
useCallback,
useContext,
useEffect,
useRef,
useState
} from "react";
import { RefreshControl, View } from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
} from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { IconButton } from "../../../components/ui/icon-button";
import { PressableButton } from "../../../components/ui/pressable";
import Paragraph from "../../../components/ui/typography/paragraph";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
presentSheet
} from "../../../services/event-manager";
import useNavigationStore, {
NotebookScreenParams
} from "../../../stores/use-navigation-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import {
eOnNewTopicAdded,
eOnTopicSheetUpdate,
eOpenAddTopicDialog
} from "../../../utils/events";
import { normalize, SIZE } from "../../../utils/size";
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
import { groupArray } from "@notesnook/core/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, history } from "../../../utils";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Properties } from "../../properties";
import Sort from "../sort";
type ConfigItem = { id: string; type: string };
class TopicSheetConfig {
static storageKey: "$$sp";
static makeId(item: ConfigItem) {
return `${TopicSheetConfig.storageKey}:${item.type}:${item.id}`;
}
static get(item: ConfigItem) {
return MMKV.getInt(TopicSheetConfig.makeId(item)) || 0;
}
static set(item: ConfigItem, index = 0) {
MMKV.setInt(TopicSheetConfig.makeId(item), index);
}
}
export const TopicsSheet = () => {
const [collapsed, setCollapsed] = useState(false);
const currentScreen = useNavigationStore((state) => state.currentScreen);
const canShow =
currentScreen.name === "Notebook" || currentScreen.name === "TopicNotes";
const [notebook, setNotebook] = useState(
canShow
? db.notebooks?.notebook(
currentScreen?.notebookId || currentScreen?.id || ""
)?.data
: null
);
const [selection, setSelection] = useState<TopicType[]>([]);
const [enabled, setEnabled] = useState(false);
const colors = useThemeStore((state) => state.colors);
const ref = useRef<ActionSheetRef>(null);
const isTopic = currentScreen.name === "TopicNotes";
const [topics, setTopics] = useState(
notebook
? qclone(
groupArray(notebook.topics, db.settings?.getGroupOptions("topics"))
)
: []
);
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions("topics")
);
const onRequestUpdate = React.useCallback(
(data?: NotebookScreenParams) => {
if (!canShow) return;
if (!data) data = { item: notebook } as NotebookScreenParams;
const _notebook = db.notebooks?.notebook(data.item?.id)
?.data as NotebookType;
if (_notebook) {
setNotebook(_notebook);
setTopics(
qclone(
groupArray(_notebook.topics, db.settings?.getGroupOptions("topics"))
)
);
}
},
[canShow, notebook]
);
const onUpdate = useCallback(() => {
setGroupOptions({ ...(db.settings?.getGroupOptions("topics") as any) });
onRequestUpdate();
}, [onRequestUpdate]);
useEffect(() => {
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, [onUpdate]);
useEffect(() => {
const onTopicUpdate = () => {
setTimeout(() => {
onRequestUpdate();
}, 1);
};
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
return () => {
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
};
}, [onRequestUpdate]);
const PLACEHOLDER_DATA = {
heading: "Topics",
paragraph: "You have not added any topics yet.",
button: "Add first topic",
action: () => {
eSendEvent(eOpenAddTopicDialog, { notebookId: notebook.id });
},
loading: "Loading notebook topics"
};
const renderTopic = ({
item,
index
}: {
item: TopicType | GroupHeader;
index: number;
}) =>
(item as GroupHeader).type === "header" ? null : (
<TopicItem sheetRef={ref} item={item as TopicType} index={index} />
);
const selectionContext = {
selection: selection,
enabled,
setEnabled,
toggleSelection: (item: TopicType) => {
setSelection((state) => {
const selection = [...state];
const index = selection.findIndex(
(selected) => selected.id === item.id
);
if (index > -1) {
selection.splice(index, 1);
if (selection.length === 0) {
setEnabled(false);
}
return selection;
}
selection.push(item);
return selection;
});
}
};
useEffect(() => {
if (canShow) {
setTimeout(() => {
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
const notebook = db.notebooks?.notebook(id as string)?.data;
const snapPoint = isTopic
? 0
: TopicSheetConfig.get({
type: isTopic ? "topic" : "notebook",
id: currentScreen.id as string
});
if (ref.current?.isOpen()) {
ref.current?.snapToIndex(snapPoint);
} else {
ref.current?.show(snapPoint);
}
if (notebook) {
onRequestUpdate({
item: notebook
} as any);
}
}, 300);
} else {
ref.current?.hide();
}
}, [
canShow,
currentScreen?.id,
currentScreen.name,
currentScreen?.notebookId,
onRequestUpdate,
isTopic
]);
return (
<ActionSheet
ref={ref}
isModal={false}
containerStyle={{
maxHeight: 300,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
backgroundColor: colors.bg,
borderWidth: 1,
borderColor: colors.nav,
borderBottomWidth: 0
}}
openAnimationConfig={{
friction: 10
}}
onSnapIndexChange={(index) => {
setCollapsed(index === 0);
TopicSheetConfig.set(
{
type: isTopic ? "topic" : "notebook",
id: currentScreen.id as string
},
index
);
}}
closable={!canShow}
elevation={10}
indicatorStyle={{
width: 100,
backgroundColor: colors.nav
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [25, 100]}
initialSnapIndex={1}
backgroundInteractionEnabled
gestureEnabled
>
<View
style={{
position: "absolute",
right: 12,
marginTop: -80
}}
>
<PressableButton
testID={notesnook.buttons.add}
type="accent"
accentColor={"accent"}
accentText="light"
onPress={openEditor}
customStyle={{
borderRadius: 100
}}
>
<View
style={{
alignItems: "center",
justifyContent: "center",
height: normalize(60),
width: normalize(60)
}}
>
<Icon name="plus" color="white" size={SIZE.xxl} />
</View>
</PressableButton>
</View>
<View
style={{
maxHeight: 300,
height: 300,
width: "100%"
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 12,
alignItems: "center"
}}
>
<Paragraph size={SIZE.xs} color={colors.icon}>
TOPICS
</Paragraph>
<View
style={{
flexDirection: "row"
}}
>
{enabled ? (
<IconButton
customStyle={{
marginLeft: 10
}}
onPress={async () => {
//@ts-ignore
history.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();
history.selectedItemsList = [];
setEnabled(false);
setSelection([]);
},
positiveType: "errorShade"
});
return;
}}
color={colors.pri}
tooltipText="Move to trash"
tooltipPosition={1}
name="delete"
size={22}
/>
) : (
<>
<IconButton
name={
groupOptions?.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
onPress={() => {
presentSheet({
component: <Sort screen="TopicSheet" type="topics" />
});
}}
testID="group-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
testID="add-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
<IconButton
name={collapsed ? "chevron-up" : "chevron-down"}
onPress={() => {
if (ref.current?.currentSnapIndex() !== 0) {
setCollapsed(true);
ref.current?.snapToIndex(0);
} else {
setCollapsed(false);
ref.current?.snapToIndex(1);
}
}}
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
</>
)}
</View>
</View>
<SelectionContext.Provider value={selectionContext}>
<FlatList
data={topics}
style={{
width: "100%"
}}
refreshControl={
<RefreshControl
refreshing={false}
onRefresh={() => {
onRequestUpdate();
}}
colors={[colors.accent]}
progressBackgroundColor={colors.bg}
/>
}
keyExtractor={(item) => (item as TopicType).id}
renderItem={renderTopic}
ListEmptyComponent={
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
height: 200
}}
>
<Paragraph color={colors.icon}>No topics</Paragraph>
</View>
}
ListFooterComponent={<View style={{ height: 50 }} />}
/>
</SelectionContext.Provider>
</View>
</ActionSheet>
);
};
const SelectionContext = createContext<{
selection: TopicType[];
enabled: boolean;
setEnabled: (value: boolean) => void;
toggleSelection: (item: TopicType) => void;
}>({
selection: [],
enabled: false,
setEnabled: (_value: boolean) => {},
toggleSelection: (_item: TopicType) => {}
});
const useSelection = () => useContext(SelectionContext);
const TopicItem = ({
item,
index,
sheetRef
}: {
item: TopicType;
index: number;
sheetRef: RefObject<ActionSheetRef>;
}) => {
const screen = useNavigationStore((state) => state.currentScreen);
const colors = useThemeStore((state) => state.colors);
const selection = useSelection();
const isSelected =
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
const isFocused = screen.id === item.id;
const notesCount = getTotalNotes(item);
return (
<PressableButton
type={isSelected || isFocused ? "grayBg" : "transparent"}
onLongPress={() => {
if (selection.enabled) return;
selection.setEnabled(true);
selection.toggleSelection(item);
}}
testID={`topic-sheet-item-${index}`}
onPress={() => {
if (selection.enabled) {
selection.toggleSelection(item);
return;
}
TopicNotes.navigate(item, true);
}}
customStyle={{
justifyContent: "space-between",
width: "100%",
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 12,
borderRadius: 0
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
{selection.enabled ? (
<IconButton
size={SIZE.lg}
color={isSelected ? colors.accent : colors.icon}
name={
isSelected
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
) : null}
<Paragraph size={SIZE.sm}>
{item.title}{" "}
{notesCount ? (
<Paragraph size={SIZE.xs} color={colors.icon}>
{notesCount}
</Paragraph>
) : null}
</Paragraph>
</View>
<IconButton
name="dots-horizontal"
customStyle={{
width: 40,
height: 40
}}
testID={notesnook.ids.notebook.menu}
onPress={() => {
Properties.present(item);
}}
left={0}
right={0}
bottom={0}
top={0}
color={colors.pri}
size={SIZE.xl}
/>
</PressableButton>
);
};

View File

@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { Linking, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import deviceInfoModule from "react-native-device-info";
import { ScrollView } from "react-native-gesture-handler";
import { useThemeStore } from "../../../stores/use-theme-store";
import { STORE_LINK } from "../../../utils/constants";
import { SIZE } from "../../../utils/size";
@@ -148,9 +148,6 @@ export const Update = ({ version: appVersion, fwdRef }) => {
<Seperator />
<ScrollView
nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
fwdRef?.current?.handleChildScrollEnd();
}}
style={{
width: "100%"
}}

View File

@@ -164,7 +164,6 @@ export const PinItem = React.memo(
}}
fontSize={SIZE.md}
width="95%"
height={50}
customStyle={{
marginBottom: 30
}}

View File

@@ -92,7 +92,7 @@ const Input = ({
button,
onBlurInput,
onPress,
height = 50,
height = 45,
fontSize = SIZE.md,
onFocusInput,
buttons,

View File

@@ -37,7 +37,9 @@ const SheetWrapper = ({
onHasReachedTop,
keyboardMode,
overlay,
overlayOpacity = 0.3
overlayOpacity = 0.3,
enableGesturesInScrollView = false,
bottomPadding = true
}) => {
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -59,8 +61,8 @@ const SheetWrapper = ({
zIndex: 10,
paddingTop: 5,
paddingBottom: 0,
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
alignSelf: "center",
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0
@@ -84,7 +86,8 @@ const SheetWrapper = ({
backdrop: "sheet-backdrop"
}}
indicatorStyle={{
width: 100
width: 100,
backgroundColor: colors.nav
}}
drawUnderStatusBar={false}
containerStyle={style}
@@ -98,8 +101,9 @@ const SheetWrapper = ({
indicatorColor={colors.nav}
onOpen={_onOpen}
keyboardDismissMode="none"
enableGesturesInScrollView={enableGesturesInScrollView}
defaultOverlayOpacity={overlayOpacity}
overlayColor={pitchBlack ? "#585858" : "#000000"}
overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
keyboardShouldPersistTaps="always"
ExtraOverlayComponent={
<>
@@ -116,14 +120,16 @@ const SheetWrapper = ({
>
<BouncingView>
{children}
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
</BouncingView>
</ActionSheet>
);

View File

@@ -46,6 +46,7 @@ export default function Tag({
marginLeft: 2,
marginTop: -10,
height: 20,
justifyContent: "center",
...style
}}
>

View File

@@ -19,17 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Improved editor performance",
body: "The editor perfomance is now much better especially with images"
},
{
title: "Sortable task lists",
body: "Now you can sort tasks by checked/unchecked in editor"
},
{
title: "Improved outline lists",
body: "Rewritten outline lists to be more performant and less buggy"
}
];
export const features: FeatureType[] = [];

View File

@@ -22,10 +22,15 @@ import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native";
import Share from "react-native-share";
import { db } from "../common/database";
import { AttachmentDialog } from "../components/attachments";
import { presentDialog } from "../components/dialog/functions";
import NoteHistory from "../components/note-history";
import { AddNotebookSheet } from "../components/sheets/add-notebook";
import MoveNoteSheet from "../components/sheets/add-to";
import ExportNotesSheet from "../components/sheets/export-notes";
import { MoveNotes } from "../components/sheets/move-notes/movenote";
import PublishNoteSheet from "../components/sheets/publish-note";
import { RelationsList } from "../components/sheets/relations-list/index";
import ReminderSheet from "../components/sheets/reminder";
import {
eSendEvent,
@@ -40,6 +45,7 @@ import Notifications from "../services/notifications";
import { useEditorStore } from "../stores/use-editor-store";
import { useMenuStore } from "../stores/use-menu-store";
import useNavigationStore from "../stores/use-navigation-store";
import { useRelationStore } from "../stores/use-relation-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useTagStore } from "../stores/use-tag-store";
import { useThemeStore } from "../stores/use-theme-store";
@@ -47,17 +53,12 @@ import { useUserStore } from "../stores/use-user-store";
import { toTXT } from "../utils";
import { toggleDarkMode } from "../utils/color-scheme/utils";
import {
eOpenAddNotebookDialog,
eOnTopicSheetUpdate,
eOpenAddTopicDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenMoveNoteDialog,
eOpenPublishNoteDialog
eOpenLoginDialog
} from "../utils/events";
import { deleteItems } from "../utils/functions";
import { sleep } from "../utils/time";
import { RelationsList } from "../components/sheets/relations-list/index";
import { useRelationStore } from "../stores/use-relation-store";
export const useActions = ({ close = () => null, item }) => {
const colors = useThemeStore((state) => state.colors);
@@ -105,6 +106,15 @@ export const useActions = ({ close = () => null, item }) => {
);
};
const isNoteInNotebook = () => {
const currentScreen = useNavigationStore.getState().currentScreen;
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
return !!db.relations
.to(item, "notebook")
.find((notebook) => notebook.id === currentScreen.id);
};
const onUpdate = useCallback(
async (type) => {
if (type === "unpin") {
@@ -129,25 +139,16 @@ export const useActions = ({ close = () => null, item }) => {
}
function addTo() {
close();
clearSelection(true);
setSelectedItem(item);
setTimeout(() => {
eSendEvent(eOpenMoveNoteDialog, item);
}, 300);
MoveNoteSheet.present(item);
}
async function addToFavorites() {
if (!item.id) return;
close();
await db.notes.note(item.id).favorite();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
Navigation.queueRoutesForUpdate();
}
async function pinItem() {
@@ -155,14 +156,7 @@ export const useActions = ({ close = () => null, item }) => {
close();
let type = item.type;
await db[`${type}s`][type](item.id).pin();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebooks"
);
Navigation.queueRoutesForUpdate();
}
async function pinToNotifications() {
@@ -176,9 +170,17 @@ export const useActions = ({ close = () => null, item }) => {
checkNotifPinned();
return;
}
if (item.locked) return;
let html = await db.notes.note(item.id).content();
let text = await toTXT(item);
if (item.locked) {
ToastEvent.show({
heading: "Note is locked",
type: "error",
message: "Locked notes cannot be pinned to notifications",
context: "local"
});
return;
}
let text = await toTXT(item, false);
let html = text.replace(/\n/g, "<br />");
Notifications.displayNotification({
title: item.title,
message: item.headline || text,
@@ -197,15 +199,7 @@ export const useActions = ({ close = () => null, item }) => {
if (!checkNoteSynced()) return;
close();
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebooks",
"Trash"
);
Navigation.queueRoutesForUpdate();
let type = item.type === "trash" ? item.itemType : item.type;
ToastEvent.show({
heading:
@@ -270,9 +264,7 @@ export const useActions = ({ close = () => null, item }) => {
});
return;
}
close();
await sleep(300);
eSendEvent(eOpenPublishNoteDialog, item);
PublishNoteSheet.present(item);
}
const checkNoteSynced = () => {
@@ -323,13 +315,7 @@ export const useActions = ({ close = () => null, item }) => {
let note = db.notes.note(item.id).data;
if (note.locked) {
close();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
Navigation.queueRoutesForUpdate();
}
} catch (e) {
close();
@@ -395,16 +381,11 @@ export const useActions = ({ close = () => null, item }) => {
positivePress: async (value) => {
if (!value || value === "" || value.trimStart().length == 0) return;
await db.tags.rename(item.id, db.tags.sanitize(value));
useTagStore.getState().setTags();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Tags"
);
setImmediate(() => {
useTagStore.getState().setTags();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate();
});
},
input: true,
defaultValue: alias,
@@ -447,24 +428,16 @@ export const useActions = ({ close = () => null, item }) => {
? "This reminder will be removed"
: "This tag will be removed from all notes.",
positivePress: async () => {
const routes = [];
routes.push(
"TaggedNotes",
"ColoredNotes",
"Notes",
"NotesPage",
"Reminders",
"Favorites"
);
if (item.type === "reminder") {
await db.reminders.remove(item.id);
} else {
await db.tags.remove(item.id);
useTagStore.getState().setTags();
routes.push("Tags");
}
Navigation.queueRoutesForUpdate(...routes);
useRelationStore.getState().update();
setImmediate(() => {
useTagStore.getState().setTags();
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
});
},
positiveText: "Delete",
positiveType: "errorShade"
@@ -502,15 +475,16 @@ export const useActions = ({ close = () => null, item }) => {
},
item.id
);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
Navigation.queueRoutesForUpdate();
eSendEvent(eOnTopicSheetUpdate);
close();
}
async function removeNoteFromNotebook() {
const currentScreen = useNavigationStore.getState().currentScreen;
if (currentScreen.name !== "Notebook") return;
await db.relations.unlink({ type: "notebook", id: currentScreen.id }, item);
Navigation.queueRoutesForUpdate();
close();
}
@@ -525,12 +499,14 @@ export const useActions = ({ close = () => null, item }) => {
negativeText: "Cancel",
positivePress: async () => {
await db.trash.delete(item.id);
Navigation.queueRoutesForUpdate("Trash");
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
type: "success",
context: "local"
setImmediate(() => {
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
});
},
positiveType: "errorShade"
@@ -538,35 +514,32 @@ export const useActions = ({ close = () => null, item }) => {
}
async function openHistory() {
close();
await sleep(300);
presentSheet({
component: (ref) => <NoteHistory fwdRef={ref} note={item} />
});
}
async function showAttachments() {
close();
await sleep(300);
eSendEvent(eOpenAttachmentsDialog, item);
AttachmentDialog.present();
}
async function exportNote() {
close();
await sleep(300);
if (item.locked) {
ToastEvent.show({
heading: "Note is locked",
type: "error",
message: "Locked notes cannot be exported",
context: "local"
});
return;
}
ExportNotesSheet.present([item]);
}
async function toggleLocalOnly() {
if (!checkNoteSynced() || !user) return;
db.notes.note(item.id).localOnly();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
Navigation.queueRoutesForUpdate();
close();
}
@@ -577,26 +550,14 @@ export const useActions = ({ close = () => null, item }) => {
useEditorStore.getState().setReadonly(current);
// tiny.call(EditorWebView, tiny.toogleReadMode(current ? 'readonly' : 'design'));
}
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
Navigation.queueRoutesForUpdate();
close();
};
const duplicateNote = async () => {
if (!checkNoteSynced()) return;
await db.notes.note(item.id).duplicate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
Navigation.queueRoutesForUpdate();
close();
};
const actions = [
@@ -647,8 +608,6 @@ export const useActions = ({ close = () => null, item }) => {
title: "Add notes",
icon: "plus",
func: async () => {
close();
await sleep(500);
MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item);
}
},
@@ -677,7 +636,7 @@ export const useActions = ({ close = () => null, item }) => {
id: "pin-to-notifications",
title:
notifPinned !== null
? "Unpin from Notifications"
? "Unpin from notifications"
: "Pin to notifications",
icon: "message-badge-outline",
on: notifPinned !== null,
@@ -689,9 +648,7 @@ export const useActions = ({ close = () => null, item }) => {
title: "Edit notebook",
icon: "square-edit-outline",
func: async () => {
close();
await sleep(300);
eSendEvent(eOpenAddNotebookDialog, item);
AddNotebookSheet.present(item);
}
},
{
@@ -777,8 +734,6 @@ export const useActions = ({ close = () => null, item }) => {
title: "Edit reminder",
icon: "pencil",
func: async () => {
close();
await sleep(300);
ReminderSheet.present(item);
},
close: false
@@ -789,7 +744,6 @@ export const useActions = ({ close = () => null, item }) => {
title: "Reminders",
icon: "clock-outline",
func: async () => {
close();
RelationsList.present({
reference: item,
referenceType: "reminder",
@@ -831,15 +785,7 @@ export const useActions = ({ close = () => null, item }) => {
});
Notifications.scheduleNotification(item);
useRelationStore.getState().update();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"NotesPage",
"Reminders"
);
Navigation.queueRoutesForUpdate();
}
},
{
@@ -849,6 +795,13 @@ export const useActions = ({ close = () => null, item }) => {
icon: "minus-circle-outline",
func: removeNoteFromTopic
},
{
id: "remove-from-notebook",
title: "Remove from notebook",
hidden: !isNoteInNotebook(),
icon: "minus-circle-outline",
func: removeNoteFromNotebook
},
{
id: "trash",
title:

View File

@@ -402,11 +402,9 @@ export const useAppEvents = () => {
const checkAutoBackup = useCallback(async () => {
if (verify || syncing) {
console.log("backup is waiting");
refValues.current.backupDidWait = true;
return;
}
console.log("backup running immediate");
const user = await db.user.getUser();
if (PremiumService.get() && user) {
if (SettingsService.get().reminder === "off") {
@@ -427,7 +425,6 @@ export const useAppEvents = () => {
useEffect(() => {
if (!verify && !syncing && refValues.current.backupDidWait) {
console.log("backup run after wait");
refValues.current.backupDidWait = false;
checkAutoBackup();
}

View File

@@ -24,6 +24,7 @@ import { SafeAreaView } from "react-native";
import Container from "../components/container";
import DelayLayout from "../components/delay-layout";
import Intro from "../components/intro";
import { TopicsSheet } from "../components/sheets/topic-sheet";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { hideAllTooltips } from "../hooks/use-tooltip";
import Favorites from "../screens/favorites";
@@ -198,6 +199,7 @@ const _NavigationStack = () => {
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
<Tabs />
</NavigationContainer>
<TopicsSheet />
</Container>
);
};

View File

@@ -69,6 +69,7 @@ import {
import { editorRef, tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time";
import { NavigationStack } from "./navigation-stack";
import changeNavigationBarColor from "react-native-navigation-bar-color";
const _TabsHolder = () => {
const colors = useThemeStore((state) => state.colors);
@@ -235,14 +236,17 @@ const _TabsHolder = () => {
let needsUpdate = current !== deviceMode;
if (fullscreen && current !== "mobile") {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
// Runs after size is set via state.
setTimeout(() => {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
}, 1);
} else {
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
editorRef.current?.setNativeProps({
@@ -281,10 +285,8 @@ const _TabsHolder = () => {
!editorState().movedAway &&
useEditorStore.getState().currentEditingNote
) {
console.log("editor");
tabBarRef.current?.goToIndex(2, false);
} else {
console.log("home");
tabBarRef.current?.goToIndex(1, false);
}
break;
@@ -385,6 +387,10 @@ const _TabsHolder = () => {
};
}, []);
useEffect(() => {
changeNavigationBarColor(colors.bg, !colors.night, true);
}, [colors.night, colors.bg]);
return (
<View
onLayout={_onLayout}

View File

@@ -13,7 +13,7 @@
"html-to-text": "8.1.0",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "^0.7.2",
"react-native-actions-sheet": "0.9.0-alpha.18",
"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",
@@ -28,7 +28,6 @@
"zustand": "^3.6.0",
"fflate": "^0.7.3",
"timeago.js": "4.0.2"
},
"sideEffects": false
}

View File

@@ -28,12 +28,12 @@ import { Button } from "../../components/ui/button";
import { IconButton } from "../../components/ui/icon-button";
import Paragraph from "../../components/ui/typography/paragraph";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { eClearEditor } from "../../utils/events";
import { SIZE } from "../../utils/size";
@@ -42,7 +42,9 @@ const EditorOverlay = ({ editorId = "", editor }) => {
const colors = useThemeStore((state) => state.colors);
const [error, setError] = useState(false);
const opacity = useSharedValue(1);
const translateValue = useSharedValue(6000);
const translateValue = useSharedValue(0);
const deviceMode = useSettingStore((state) => state.deviceMode);
const isTablet = deviceMode !== "mobile";
const insets = useGlobalSafeAreaInsets();
const isDefaultEditor = editorId === "";
const timers = useRef({
@@ -50,6 +52,9 @@ const EditorOverlay = ({ editorId = "", editor }) => {
error: 0,
closing: 0
});
const loadingState = useRef({
startTime: 0
});
const clearTimers = () => {
clearTimeout(timers.current.loading);
@@ -62,6 +67,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
editorState().overlay = true;
clearTimers();
if (_loading) {
loadingState.current.startTime = Date.now();
opacity.value = 1;
translateValue.value = 0;
timers.current.error = setTimeout(() => {
@@ -69,7 +75,14 @@ const EditorOverlay = ({ editorId = "", editor }) => {
}, 15 * 1000);
} else {
clearTimers();
setTimeout(() => {
const timeDiffSinceLoadStarted =
Date.now() - loadingState.current.startTime > 300 ? 300 : 0;
if (!timeDiffSinceLoadStarted) {
setError(false);
editorState().overlay = false;
opacity.value = 0;
translateValue.value = 6000;
} else {
setError(false);
editorState().overlay = false;
opacity.value = withTiming(0, {
@@ -78,19 +91,24 @@ const EditorOverlay = ({ editorId = "", editor }) => {
setTimeout(() => {
translateValue.value = 6000;
}, 500);
}, 100);
}
}
},
[opacity, translateValue]
);
useEffect(() => {
setTimeout(() => {
if (!loadingState.current.startTime) {
translateValue.value = 6000;
}
}, 1000);
eSubscribeEvent("loadingNote" + editorId, load);
return () => {
clearTimers();
eUnSubscribeEvent("loadingNote" + editorId, load);
};
}, [editorId, load]);
}, [editorId, load, translateValue]);
const animatedStyle = useAnimatedStyle(() => {
return {
@@ -140,7 +158,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
paddingRight: 12
}}
>
{DDS.isTablet() ? (
{isTablet ? (
<View />
) : (
<IconButton
@@ -184,7 +202,9 @@ const EditorOverlay = ({ editorId = "", editor }) => {
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 10,
marginTop: 10
marginTop: 5,
borderWidth: 1,
borderColor: colors.border
}}
>
<Paragraph color={colors.icon} size={13}>

View File

@@ -129,7 +129,6 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
};
setInsets = async (insets: EdgeInsets) => {
logger.info("setInsets", insets);
await this.doAsync(`
if (typeof safeAreaController !== "undefined") {
safeAreaController.update(${JSON.stringify(insets)})
@@ -229,7 +228,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
await this.doAsync(
`editor && editor.commands.updateImage(${JSON.stringify({
hash
})},${JSON.stringify({ src, hash, preventUpdate: true })})`
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
);
};

View File

@@ -34,5 +34,6 @@ export const EventTypes = {
fullscreen: "editor-event:fullscreen",
link: "editor-event:link",
contentchange: "editor-event:content-change",
reminders: "editor-event:reminders"
reminders: "editor-event:reminders",
previewAttachment: "editor-event:preview-attachment"
};

View File

@@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import React from "react";
import { Platform, View } from "react-native";
import DocumentPicker from "react-native-document-picker";
import { launchCamera, launchImageLibrary } from "react-native-image-picker";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { db } from "../../../common/database";
import { compressToBase64 } from "../../../common/filesystem/compress";
import { AttachmentItem } from "../../../components/attachments/attachment-item";
import {
eSendEvent,
@@ -32,9 +33,7 @@ import {
} from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
import { eCloseSheet } from "../../../utils/events";
import { sleep } from "../../../utils/time";
import { editorController, editorState } from "./utils";
import { compressToBase64 } from "../../../common/filesystem/compress";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -149,8 +148,6 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchCamera(
{
includeBase64: true,
@@ -172,8 +169,6 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchImageLibrary(
{
includeBase64: true,
@@ -207,7 +202,7 @@ const pick = async (options) => {
return;
}
if (options?.type.startsWith("image") || options?.type === "camera") {
if (options.type === "image") {
if (options.type.startsWith("image")) {
gallery(options);
} else {
camera(options);
@@ -250,7 +245,10 @@ const handleImageResponse = async (response, options) => {
if (isPng || isJpeg) {
b64 =
`data:${image.type};base64, ` +
(await compressToBase64(image.uri, isPng ? "PNG" : "JPEG"));
(await compressToBase64(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
isPng ? "PNG" : "JPEG"
));
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
@@ -259,7 +257,7 @@ const handleImageResponse = async (response, options) => {
hash: hash,
type: image.type,
title: fileName,
src: b64,
dataurl: b64,
size: image.fileSize,
filename: fileName
});

View File

@@ -29,6 +29,7 @@ import {
} from "react-native";
import { WebViewMessageEvent } from "react-native-webview";
import { db } from "../../../common/database";
import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list";
import ReminderSheet from "../../../components/sheets/reminder";
import useKeyboard from "../../../hooks/use-keyboard";
@@ -51,8 +52,7 @@ import {
eOpenFullscreenEditor,
eOpenLoginDialog,
eOpenPremiumDialog,
eOpenPublishNoteDialog,
eOpenTagsDialog
eOpenPublishNoteDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
@@ -189,12 +189,7 @@ export const useEditorEvents = (
setImmediate(() => {
useEditorStore.getState().setCurrentlyEditingNote(null);
setTimeout(() => {
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
}, 500);
});
editorState().currentlyEditing = false;
@@ -273,12 +268,28 @@ export const useEditorEvents = (
(event: WebViewMessageEvent) => {
const data = event.nativeEvent.data;
const editorMessage = JSON.parse(data) as EditorMessage;
if (editorMessage.type === EventTypes.content) {
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value as string,
forSessionId: editorMessage.sessionId
});
} else if (editorMessage.type === EventTypes.title) {
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string,
forSessionId: editorMessage.sessionId
});
}
if (
editorMessage.sessionId !== editor.sessionId &&
editorMessage.type !== EditorEvents.status
) {
return;
}
switch (editorMessage.type) {
case EventTypes.logger:
logger.info("[WEBVIEW LOG]", editorMessage.value);
@@ -286,21 +297,8 @@ export const useEditorEvents = (
case EventTypes.contentchange:
editor.onContentChanged();
break;
case EventTypes.content:
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value as string
});
break;
case EventTypes.selection:
break;
case EventTypes.title:
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string
});
break;
case EventTypes.reminders:
if (!editor.note.current) {
ToastEvent.show({
@@ -326,7 +324,7 @@ export const useEditorEvents = (
});
return;
}
eSendEvent(eOpenTagsDialog, editor.note.current);
ManageTagsSheet.present(editor.note.current);
break;
case EventTypes.tag:
if (editorMessage.value) {
@@ -337,13 +335,7 @@ export const useEditorEvents = (
.then(async () => {
useTagStore.getState().setTags();
await editor.commands.setTags(editor.note.current);
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes",
"Tags"
);
Navigation.queueRoutesForUpdate();
});
}
break;
@@ -380,6 +372,10 @@ export const useEditorEvents = (
case EventTypes.link:
openLinkInBrowser(editorMessage.value as string);
break;
case EventTypes.previewAttachment:
eSendEvent("ImagePreview", editorMessage.value);
break;
default:
break;
}

View File

@@ -76,8 +76,8 @@ export const useEditor = (
const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0);
const lock = useRef(false);
const attachedImages = useRef<{ [name: string]: any }>({});
const loadedImages = useRef<{ [name: string]: any }>({});
const loadedImages = useRef<{ [name: string]: boolean }>({});
const lockedSessionId = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T) =>
@@ -153,20 +153,19 @@ export const useEditor = (
);
const reset = useCallback(
async (resetState = true) => {
async (resetState = true, resetContent = true) => {
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
currentNote.current = null;
attachedImages.current = [];
loadedImages.current = [];
loadedImages.current = {};
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
useEditorStore.getState().setReadonly(false);
postMessage(EditorEvents.title, "");
resetContent && postMessage(EditorEvents.title, "");
lastContentChangeTime.current = 0;
await commands.clearContent();
await commands.clearTags();
resetContent && (await commands.clearContent());
resetContent && (await commands.clearTags());
if (resetState) {
isDefaultEditor &&
useEditorStore.getState().setCurrentlyEditingNote(null);
@@ -241,9 +240,15 @@ export const useEditor = (
if (
useEditorStore.getState().currentEditingNote !== id &&
isDefaultEditor
isDefaultEditor &&
state.current.currentlyEditing
) {
setTimeout(() => {
if (
(currentNote.current?.id && currentNote.current?.id !== id) ||
!state.current.currentlyEditing
)
return;
id && useEditorStore.getState().setCurrentlyEditingNote(id);
});
}
@@ -264,12 +269,7 @@ export const useEditor = (
currentNote.current?.headline?.slice(0, 200) !==
note.headline?.slice(0, 200)
) {
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
}
}
@@ -295,32 +295,38 @@ export const useEditor = (
}
}, []);
const getImagesToLoad = () => {
const getMediaToLoad = (previousContent?: string) => {
if (!currentNote.current?.id) return [];
const currentImages = [
...(db.attachments?.ofNote(currentNote.current?.id, "images") || []),
...(db.attachments?.ofNote(currentNote.current?.id, "webclips") || [])
];
if (!currentImages || currentImages?.length === 0) return [];
const imagesToLoad: any[] = [];
for (const image of currentImages) {
if (!loadedImages.current[image.metadata.hash]) {
loadedImages.current[image.metadata.hash] = false;
imagesToLoad.push(image);
const previousAttachments =
previousContent?.matchAll(/data-hash="(.+?)"/gm) || [];
const attachments =
currentContent.current?.data?.matchAll(/data-hash="(.+?)"/gm) || [];
const media: string[] = [];
const oldMatches = Array.from(previousAttachments).map((match) => match[1]);
const matches = Array.from(attachments).map((match) => match[1]);
for (let i = 0; i < matches.length; i++) {
const currentHash = matches[i];
const oldHash = oldMatches[i];
if (currentHash !== oldHash) {
media.push(currentHash);
loadedImages.current[currentHash] = false;
}
attachedImages.current[image.metadata.hash] = image;
}
return imagesToLoad;
return media;
};
const markImageLoaded = (hash: string) => {
const attachment = attachedImages.current[hash];
if (attachment) {
const attachment = loadedImages.current[hash];
if (typeof attachment === "boolean") {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback(() => {
const loadImages = useCallback((previousContent?: string) => {
if (!currentNote.current?.id) return;
const timerId = "loading-images";
clearTimeout(timers.current[timerId]);
@@ -333,12 +339,12 @@ export const useEditor = (
true
);
} else {
const images = getImagesToLoad();
if (images.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, images);
const media = getMediaToLoad(previousContent);
if (media.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, media);
}
}
}, 100);
}, 1000);
}, []);
const loadNote = useCallback(
@@ -363,16 +369,26 @@ export const useEditor = (
useEditorStore.getState().setReadonly(false);
} else {
if (!item.forced && currentNote.current?.id === item.id) return;
state.current.movedAway = false;
state.current.currentlyEditing = true;
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
overlay(true, item);
currentNote.current && (await reset(false));
currentNote.current && (await reset(false, false));
await loadContent(item as NoteType);
if (
!currentContent.current?.data ||
currentContent.current?.data.length < 50000
) {
overlay(false);
} else {
overlay(true);
}
lastContentChangeTime.current = item.dateEdited;
const nextSessionId = makeSessionId(item as NoteType);
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(timeConverter(item.dateEdited), "Saved");
await postMessage(EditorEvents.title, item.title);
@@ -380,6 +396,11 @@ export const useEditor = (
useEditorStore.getState().setReadonly(item.readonly);
await commands.setTags(currentNote.current);
commands.setSettings();
setTimeout(() => {
if (lockedSessionId.current === nextSessionId) {
lockedSessionId.current = undefined;
}
}, 300);
overlay(false);
loadImages();
}
@@ -422,6 +443,8 @@ export const useEditor = (
lock.current = true;
const previousContent = currentContent.current?.data;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -452,13 +475,14 @@ export const useEditor = (
}
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
}
lock.current = false;
if (data.type === "tiptap") {
loadImages();
loadImages(previousContent);
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages();
loadImages(previousContent);
},
true
);
@@ -483,14 +507,16 @@ export const useEditor = (
({
title,
content,
type
type,
forSessionId
}: {
title?: string;
content?: string;
type: string;
forSessionId: string;
}) => {
if (lock.current || lockedSessionId.current === forSessionId) return;
lastContentChangeTime.current = Date.now();
if (lock.current) return;
if (type === EditorEvents.content) {
currentContent.current = {
data: content,
@@ -498,22 +524,27 @@ export const useEditor = (
noteId: currentNote.current?.id as string
};
}
const noteIdFromSessionId =
!forSessionId || forSessionId.startsWith("session")
? null
: forSessionId.split("_")[0];
const noteId = noteIdFromSessionId || currentNote.current?.id;
const params = {
title,
data: content,
type: "tiptap",
sessionId,
id: currentNote.current?.id,
sessionId: forSessionId,
id: noteId,
sessionHistoryId: sessionHistoryId.current
};
withTimer(
currentNote.current?.id || "newnote",
noteId || "newnote",
() => {
if (
currentNote.current &&
!params.id &&
params.sessionId === sessionId
params.sessionId === forSessionId
) {
params.id = currentNote.current?.id;
}
@@ -523,10 +554,10 @@ export const useEditor = (
}
saveNote(params);
},
500
150
);
},
[sessionId, withTimer, onChange, saveNote]
[withTimer, onChange, saveNote]
);
const restoreEditorState = useCallback(async () => {

View File

@@ -65,7 +65,6 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1
});
return (
<DelayLayout wait={loading} delay={500}>
<List

View File

@@ -16,18 +16,15 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { groupArray } from "@notesnook/core/utils/grouping";
import qclone from "qclone";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
@@ -36,28 +33,29 @@ import SearchService from "../../services/search";
import useNavigationStore, {
NotebookScreenParams
} from "../../stores/use-navigation-store";
import {
eOnNewTopicAdded,
eOpenAddNotebookDialog,
eOpenAddTopicDialog
} from "../../utils/events";
import { eOnNewTopicAdded } from "../../utils/events";
import { NotebookType } from "../../utils/types";
import { openEditor, setOnFirstSave } from "../notes/common";
const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [topics, setTopics] = useState(
const [notes, setNotes] = useState(
groupArray(
qclone(route?.params.item?.topics) || [],
db.settings?.getGroupOptions("topics")
db.relations?.from(route.params.item, "note") || [],
db.settings?.getGroupOptions("notes")
)
);
const params = useRef<NotebookScreenParams>(route?.params);
useNavigationFocus(navigation, {
onFocus: () => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
syncWithNavigation();
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
useNavigationStore.getState().setButtonAction(openEditor);
return false;
},
onBlur: () => false
onBlur: () => {
setOnFirstSave(null);
return false;
}
});
const syncWithNavigation = React.useCallback(() => {
@@ -70,6 +68,10 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
},
params.current?.canGoBack
);
setOnFirstSave({
type: "notebook",
id: params.current.item.id
});
SearchService.prepareSearch = prepareSearch;
}, [route.name]);
@@ -82,11 +84,9 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
?.data as NotebookType;
if (notebook) {
params.current.item = notebook;
setTopics(
groupArray(
qclone(notebook.topics),
db.settings?.getGroupOptions("topics")
)
const notes = db.relations?.from(notebook, "note");
setNotes(
groupArray(notes || [], db.settings?.getGroupOptions("notes"))
);
syncWithNavigation();
}
@@ -102,60 +102,61 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
return () => {
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
};
}, [onRequestUpdate, topics]);
}, [onRequestUpdate]);
useEffect(() => {
return () => {
setOnFirstSave(null);
};
}, []);
const prepareSearch = () => {
SearchService.update({
placeholder: `Search in "${params.current.title}"`,
type: "topics",
type: "notes",
title: params.current.title,
get: () => {
const notebook = db.notebooks?.notebook(params?.current?.item?.id)
?.data as NotebookType;
return notebook?.topics;
return db.relations?.from(notebook, "note");
}
});
};
const onPressFloatingButton = () => {
const n = params.current.item;
eSendEvent(eOpenAddTopicDialog, { notebookId: n.id });
};
const PLACEHOLDER_DATA = {
heading: params.current.item?.title,
paragraph: "You have not added any topics yet.",
button: "Add first topic",
action: onPressFloatingButton,
loading: "Loading notebook topics"
paragraph: "You have not added any notes yet.",
button: "Add your first note",
action: openEditor,
loading: "Loading notebook notes"
};
return (
<DelayLayout>
<List
listData={topics}
type="topics"
refreshCallback={() => {
onRequestUpdate();
}}
screen="Notebook"
headerProps={{
heading: params.current.title
}}
loading={false}
ListHeader={
<NotebookHeader
onEditNotebook={() => {
eSendEvent(eOpenAddNotebookDialog, params.current.item);
}}
notebook={params.current.item}
/>
}
placeholderData={PLACEHOLDER_DATA}
/>
<FloatingButton title="Add new topic" onPress={onPressFloatingButton} />
</DelayLayout>
<>
<DelayLayout>
<List
listData={notes}
type="notes"
refreshCallback={() => {
onRequestUpdate();
}}
screen="Notebook"
headerProps={{
heading: params.current.title
}}
loading={false}
ListHeader={
<NotebookHeader
onEditNotebook={() => {
AddNotebookSheet.present(params.current.item);
}}
notebook={params.current.item}
/>
}
placeholderData={PLACEHOLDER_DATA}
/>
</DelayLayout>
</>
);
};

View File

@@ -18,11 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Config } from "react-native-config";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { AddNotebookEvent } from "../../components/dialog-provider/recievers";
import List from "../../components/list";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { Walkthrough } from "../../components/walkthroughs";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
@@ -30,10 +31,9 @@ import SearchService from "../../services/search";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotebookStore } from "../../stores/use-notebook-store";
import { Config } from "react-native-config";
const onPressFloatingButton = () => {
AddNotebookEvent();
AddNotebookSheet.present();
};
const prepareSearch = () => {
@@ -83,7 +83,7 @@ export const Notebooks = ({
});
return (
<DelayLayout>
<DelayLayout delay={1}>
<List
listData={notebooks}
type="notebooks"

View File

@@ -24,7 +24,7 @@ import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { NotesScreenParams } from "../../stores/use-navigation-store";
import { useTagStore } from "../../stores/use-tag-store";
import { eOnLoadNote } from "../../utils/events";
import { eOnLoadNote, eOnTopicSheetUpdate } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { TopicType } from "../../utils/types";
@@ -81,12 +81,22 @@ export const setOnFirstSave = (
editorState().onNoteCreated = null;
return;
}
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
setTimeout(() => {
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
}, 0);
};
async function onNoteCreated(id: string, params: FirstSaveData) {
if (!params) return;
switch (params.type) {
case "notebook": {
await db.relations?.add(
{ type: "notebook", id: params.id },
{ type: "note", id: id }
);
editorState().onNoteCreated = null;
break;
}
case "topic": {
if (!params.notebook) break;
await db.notes?.addToNotebook(
@@ -97,40 +107,18 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
id
);
editorState().onNoteCreated = null;
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
eSendEvent(eOnTopicSheetUpdate);
break;
}
case "tag": {
await db.notes?.note(id).tag(params.id);
editorState().onNoteCreated = null;
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
useTagStore.getState().setTags();
break;
}
case "color": {
await db.notes?.note(id).color(params.color);
editorState().onNoteCreated = null;
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
useMenuStore.getState().setColorNotes();
break;
}
@@ -138,4 +126,5 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
break;
}
}
Navigation.queueRoutesForUpdate();
}

View File

@@ -18,9 +18,13 @@ 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 { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import List from "../../components/list";
import { IconButton } from "../../components/ui/icon-button";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSubscribeEvent,
@@ -34,7 +38,9 @@ import useNavigationStore, {
RouteName
} from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { SIZE } from "../../utils/size";
import { NoteType, TopicType } from "../../utils/types";
import Notebook from "../notebook/index";
import {
getAlias,
openEditor,
@@ -98,7 +104,11 @@ const NotesPage = ({
const [loadingNotes, setLoadingNotes] = useState(false);
const alias = getAlias(params.current);
const isMonograph = route.name === "Monographs";
const notebook =
route.name === "TopicNotes" && (params.current.item as TopicType).notebookId
? db.notebooks?.notebook((params.current.item as TopicType).notebookId)
?.data
: null;
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
@@ -171,11 +181,13 @@ const NotesPage = ({
if (isNew) setLoadingNotes(true);
const notes = get(params.current, true) as NoteType[];
if (
(item.type === "tag" || item.type === "color") &&
(!notes || notes.length === 0)
((item.type === "tag" || item.type === "color") &&
(!notes || notes.length === 0)) ||
(item.type === "topic" && !notes)
) {
return Navigation.goBack();
}
if (notes.length === 0) setLoadingNotes(false);
setNotes(notes);
syncWithNavigation();
} catch (e) {
@@ -187,7 +199,7 @@ const NotesPage = ({
useEffect(() => {
if (loadingNotes) {
setTimeout(() => setLoadingNotes(false), 300);
setTimeout(() => setLoadingNotes(false), 50);
}
}, [loadingNotes, notes]);
@@ -208,6 +220,49 @@ const NotesPage = ({
}
wait={loading || loadingNotes}
>
{route.name === "TopicNotes" ? (
<View
style={{
width: "100%",
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center"
// borderBottomWidth: 1,
// borderBottomColor: colors.nav
}}
>
<Paragraph
onPress={() => {
Navigation.navigate(
{
name: "Notebooks"
},
{}
);
}}
size={SIZE.xs}
>
Notebooks
</Paragraph>
{notebook ? (
<>
<IconButton
name="chevron-right"
size={14}
customStyle={{ width: 25, height: 25 }}
/>
<Paragraph
onPress={() => {
Notebook.navigate(notebook, true);
}}
size={SIZE.xs}
>
{notebook.title}
</Paragraph>
</>
) : null}
</View>
) : null}
<List
listData={notes}
type="notes"
@@ -224,7 +279,9 @@ const NotesPage = ({
placeholderData={placeholderData}
/>
{notes?.length > 0 || (isFocused && !isMonograph) ? (
{!isMonograph &&
route.name !== "TopicNotes" &&
(notes?.length > 0 || isFocused) ? (
<FloatingButton title="Create a note" onPress={onPressFloatingButton} />
) : null}
</DelayLayout>

View File

@@ -58,22 +58,28 @@ export const TopicNotes = ({
route
}: NavigationProps<"TopicNotes">) => {
return (
<NotesPage
navigation={navigation}
route={route}
get={TopicNotes.get}
placeholderData={PLACEHOLDER_DATA}
onPressFloatingButton={openEditor}
rightButtons={headerRightButtons}
canGoBack={route.params.canGoBack}
focusControl={true}
/>
<>
<NotesPage
navigation={navigation}
route={route}
get={TopicNotes.get}
placeholderData={PLACEHOLDER_DATA}
onPressFloatingButton={openEditor}
rightButtons={headerRightButtons}
canGoBack={route.params.canGoBack}
focusControl={true}
/>
</>
);
};
TopicNotes.get = (params: NotesScreenParams, grouped = true) => {
const { id, notebookId } = params.item as TopicType;
const notes = db.notebooks?.notebook(notebookId)?.topics.topic(id)?.all || [];
const topic = db.notebooks?.notebook(notebookId)?.topics.topic(id);
if (!topic) {
return null;
}
const notes = topic?.all || [];
return grouped
? groupArray(notes, db.settings?.getGroupOptions("notes"))
: notes;

View File

@@ -25,6 +25,7 @@ import { ConfigureToolbar } from "./editor/configure-toolbar";
import { Licenses } from "./licenses";
import SoundPicker from "./sound-picker";
import { Subscription } from "./subscription";
import { TrashIntervalSelector } from "./trash-interval-selector";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
homeselector: <HomagePageSelector />,
@@ -33,5 +34,6 @@ export const components: { [name: string]: ReactElement } = {
configuretoolbar: <ConfigureToolbar />,
"debug-logs": <DebugLogs />,
"sound-picker": <SoundPicker />,
licenses: <Licenses />
licenses: <Licenses />,
"trash-interval-selector": <TrashIntervalSelector />
};

View File

@@ -106,12 +106,6 @@ export const useDragState = create<DragState>(
return;
}
const preset = toolbarConfig?.preset as DragState["preset"];
logger.info(
"DragState",
"Init user toolbar config",
preset,
toolbarConfig?.config
);
set({
preset: preset,
data:

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Animated, { FadeInDown, FadeOutDown } from "react-native-reanimated";
import Animated, { FadeInDown } from "react-native-reanimated";
import DelayLayout from "../../components/delay-layout";
import BaseDialog from "../../components/dialog/base-dialog";
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
@@ -75,57 +75,55 @@ const Home = ({
return (
<DelayLayout delay={300} type="settings">
<View>
{loading && (
//@ts-ignore // Migrate to typescript required.
<BaseDialog animated={false} bounce={false} visible={true}>
{loading && (
//@ts-ignore // Migrate to typescript required.
<BaseDialog animated={false} bounce={false} visible={true}>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.bg,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading color={colors.pri} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.icon}>
Please wait while we log out and clear app data.
</Paragraph>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.bg,
justifyContent: "center",
alignItems: "center"
flexDirection: "row",
width: 100,
marginTop: 15
}}
>
<Heading color={colors.pri} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.icon}>
Please wait while we log out and clear app data.
</Paragraph>
<View
style={{
flexDirection: "row",
width: 100,
marginTop: 15
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.nav}
color={colors.accent}
borderWidth={0}
/>
</View>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.nav}
color={colors.accent}
borderWidth={0}
/>
</View>
</BaseDialog>
)}
</View>
</BaseDialog>
)}
<Animated.FlatList
entering={FadeInDown}
exiting={FadeOutDown}
data={settingsGroups}
keyExtractor={keyExtractor}
ListFooterComponent={<View style={{ height: 200 }} />}
renderItem={renderItem}
/>
</View>
<Animated.FlatList
entering={FadeInDown}
data={settingsGroups}
windowSize={1}
keyExtractor={keyExtractor}
ListFooterComponent={<View style={{ height: 200 }} />}
renderItem={renderItem}
/>
</DelayLayout>
);
};

View File

@@ -42,12 +42,12 @@ export const SectionGroup = ({ item }: { item: SettingSection }) => {
color={colors.accent}
size={SIZE.xs}
>
{item.name.toUpperCase()}
{(item.name as string).toUpperCase()}
</Heading>
) : null}
{item.sections?.map((item) => (
<SectionItem key={item.name} item={item} />
<SectionItem key={item.name as string} item={item} />
))}
</View>
);

View File

@@ -169,6 +169,14 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
}
item.inputProperties?.onSubmitEditing?.(e);
}}
onChangeText={(text) => {
if (text) {
SettingsService.set({
[item.property as string]: text
});
}
item.inputProperties?.onSubmitEditing?.(text as any);
}}
containerStyle={{ marginTop: 12 }}
fwdRef={inputRef}
onLayout={() => {

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import notifee from "@notifee/react-native";
import dayjs from "dayjs";
import React from "react";
import { Linking, Platform } from "react-native";
@@ -25,6 +26,7 @@ import * as RNIap from "react-native-iap";
import { enabled } from "react-native-privacy-snapshot";
import { db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import { AttachmentDialog } from "../../components/attachments";
import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions";
import { ChangeEmail } from "../../components/sheets/change-email";
@@ -56,7 +58,6 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import {
eCloseSheet,
eCloseSimpleDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
@@ -68,7 +69,6 @@ import { useDragState } from "./editor/state";
import { verifyUser } from "./functions";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import notifee from "@notifee/react-native";
type User = any;
@@ -149,7 +149,7 @@ export const settingsGroups: SettingSection[] = [
name: "Manage attachments",
icon: "attachment",
modifer: () => {
eSendEvent(eOpenAttachmentsDialog);
AttachmentDialog.present();
},
description: "Manage all attachments in one place."
},
@@ -523,6 +523,14 @@ export const settingsGroups: SettingSection[] = [
name: "Homepage",
description: "Default screen to open on app startup",
component: "homeselector"
},
{
id: "clear-trash-interval",
type: "component",
name: "Clear trash interval",
description:
"Select the duration after which trash items will be cleared",
component: "trash-interval-selector"
}
]
}
@@ -951,9 +959,7 @@ export const settingsGroups: SettingSection[] = [
onChange: () => {
ToastEvent.show({
heading: "Line spacing changed",
type: "success",
message:
"Close and reopen the current opened note or restart the app for changes to take affect."
type: "success"
});
}
}
@@ -1019,11 +1025,21 @@ export const settingsGroups: SettingSection[] = [
id: "join-telegram",
name: "Join our Telegram group",
description: "We are on telegram, let's talk",
// icon: 'telegram',
modifer: () => {
Linking.openURL("https://t.me/notesnook").catch(console.log);
}
},
{
id: "join-mastodom",
name: "Follow us on Mastodon",
description: "We are on mastodom",
icon: "mastodon",
modifer: () => {
Linking.openURL("https://fosstodon.org/@notesnook").catch(
console.log
);
}
},
{
id: "join-twitter",
name: "Follow us on twitter",

View File

@@ -0,0 +1,117 @@
/*
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, { useRef, useState } from "react";
import { View } from "react-native";
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { PressableButton } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import PremiumService from "../../services/premium";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
export const TrashIntervalSelector = () => {
const colors = useThemeStore((state) => state.colors);
const [trashInterval, setTrashInterval] = useState(
db.settings.getTrashCleanupInterval()
);
const menuRef = useRef();
const [width, setWidth] = useState(0);
const onChange = (item) => {
menuRef.current?.hide();
setTrashInterval(item);
db.settings.setTrashCleanupInterval(item);
};
return (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>
{trashInterval === -1 ? "Never" : trashInterval + " days"}
</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{[-1, 7, 30, 365].map((item) => (
<MenuItem
key={item.toString()}
onPress={async () => {
if (item === -1) {
await PremiumService.verify(() => {
onChange(item);
});
return;
}
onChange(item);
}}
style={{
backgroundColor:
trashInterval === item ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: trashInterval === item ? colors.accent : colors.pri
}}
>
{item === -1 ? "Never" : item + " days"}
</MenuItem>
))}
</Menu>
</View>
);
};

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 Clipboard from "@react-native-clipboard/clipboard";
import EventManager from "@notesnook/core/utils/event-manager";
import Clipboard from "@react-native-clipboard/clipboard";
import { RefObject } from "react";
import ActionSheet from "react-native-actions-sheet";
import { ActionSheetRef } from "react-native-actions-sheet";
import Config from "react-native-config";
import {
eCloseSheet,
@@ -97,7 +97,7 @@ export type PresentSheetOptions = {
component:
| JSX.Element
| ((
ref: RefObject<ActionSheet>,
ref: RefObject<ActionSheetRef>,
close?: (ctx?: string) => void,
update?: (props: PresentSheetOptions) => void
) => JSX.Element);
@@ -114,6 +114,8 @@ export type PresentSheetOptions = {
actionsArray: SheetAction[];
learnMore: string;
learnMorePress: () => void;
enableGesturesInScrollView?: boolean;
noBottomPadding?: boolean;
};
export function presentSheet(data: Partial<PresentSheetOptions>) {

View File

@@ -35,6 +35,7 @@ import { eOnNewTopicAdded } from "../utils/events";
import { rootNavigatorRef, tabBarRef } from "../utils/global-refs";
import { eSendEvent } from "./event-manager";
import SettingsService from "./settings";
import SearchService from "./search";
/**
* Routes that should be updated on focus
@@ -86,7 +87,8 @@ const routeUpdateFunctions: {
ColoredNotes: (params) => eSendEvent("ColoredNotes", params),
TopicNotes: (params) => eSendEvent("TopicNotes", params),
Monographs: (params) => eSendEvent("Monographs", params),
Reminders: () => useReminderStore.getState().setReminders()
Reminders: () => useReminderStore.getState().setReminders(),
Search: () => SearchService.updateAndSearch()
};
function clearRouteFromQueue(routeName: RouteName) {
@@ -106,10 +108,14 @@ function routeNeedsUpdate(routeName: RouteName, callback: () => void) {
}
}
function queueRoutesForUpdate(...routes: RouteName[]) {
function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
const routes =
routesToUpdate?.length > 0
? routesToUpdate
: (Object.keys(routeNames) as (keyof RouteParams)[]);
const currentScreen = useNavigationStore.getState().currentScreen;
if (routes.indexOf(currentScreen.name) > -1) {
routeUpdateFunctions[currentScreen.name]();
routeUpdateFunctions[currentScreen.name]?.();
clearRouteFromQueue(currentScreen.name);
// Remove focused screen from queue
routes.splice(routes.indexOf(currentScreen.name), 1);
@@ -125,7 +131,7 @@ function navigate<T extends RouteName>(
useNavigationStore.getState().update(screen, !!params?.canGoBack);
if (screen.name === "Notebook") routeUpdateFunctions["Notebook"](params);
if (screen.name.endsWith("Notes") && screen.name !== "Notes")
routeUpdateFunctions[screen.name](params);
routeUpdateFunctions[screen.name]?.(params);
//@ts-ignore Not sure how to fix this for now ignore it.
rootNavigatorRef.current?.navigate<RouteName>(screen.name, params);
}

View File

@@ -367,7 +367,8 @@ async function scheduleNotification(
function loadNote(id: string, jump: boolean) {
if (!id || id === "notesnook_note_input") return;
const note = db.notes?.note(id).data;
const note = db.notes?.note(id)?.data;
if (!note) return;
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
}

View File

@@ -26,7 +26,7 @@ let searchInformation = {
placeholder: "Search in all notes",
data: [],
type: "notes",
get: () => null
get: () => []
};
let keyword = null;

View File

@@ -145,23 +145,23 @@ export const useTip = (
const tips: TTip[] = [
{
text: "You can swipe left anywhere in the app to start a new note",
text: "You can swipe left anywhere in the app to start a new note.",
contexts: ["notes", "first-note"]
},
{
text: "Long press on any item in list to open quick actions menu.",
text: "Long press on any item in list to enter multi-select mode.",
contexts: ["notes", "notebook", "notebook", "tags", "topics"]
},
{
text: "Monographs enable you to share your notes in a secure and private way",
text: "Monographs enable you to share your notes in a secure and private way.",
contexts: ["monographs"]
},
{
text: "Monographs can be encrypted with a secret key and shared with anyone",
text: "Monographs can be encrypted with a secret key and shared with anyone.",
contexts: ["monographs"]
},
{
text: "Frequently accessed notebooks can be pinned to Side Menu so that they are easily accessible",
text: "You can pin frequently used Notebooks to the Side Menu to quickly access them.",
contexts: ["notebook", "notebooks"]
},
{
@@ -173,15 +173,15 @@ const tips: TTip[] = [
contexts: ["notebook", "topics"]
},
{
text: "Items in trash are kept for 7 days after which they are permanently deleted.",
contexts: ["trash"]
},
{
text: "Mark important notes by adding them to favorites",
text: "Mark important notes by adding them to favorites.",
contexts: ["notes"]
},
{
text: "Have to scroll down a lot to open a note you are working on? Pin it to top from properties.",
text: "Are you scrolling a lot to find a specific note? Pin it to the top from Note properties.",
contexts: ["notes"]
},
{
text: "You can view & restore older versions of any note by going to its properties -> History.",
contexts: ["notes"]
}
];

View File

@@ -19,22 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { db } from "../common/database";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
import { useFavoriteStore } from "./use-favorite-store";
import { useMenuStore } from "./use-menu-store";
import { RouteName } from "./use-navigation-store";
import { useNotebookStore } from "./use-notebook-store";
import { useNoteStore } from "./use-notes-store";
import { useRelationStore } from "./use-relation-store";
import { useReminderStore } from "./use-reminder-store";
import { useTagStore } from "./use-tag-store";
import { useTrashStore } from "./use-trash-store";
import { useReminderStore } from "./use-reminder-store";
import Notifications from "../services/notifications";
import { useRelationStore } from "./use-relation-store";
export function initAfterSync() {
useMenuStore.getState().setColorNotes();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate(
...(Object.keys(Navigation.routeUpdateFunctions) as unknown as RouteName[])
);
Navigation.queueRoutesForUpdate();
// Whenever sync completes, try to reschedule
// any new/updated reminders.
Notifications.setupReminders(true);

View File

@@ -104,7 +104,7 @@ export const COLOR_SCHEME_PITCH_BLACK = {
light: "#ffffff",
transGray: "#ffffff10",
border: "#383838",
placeholder: "#404040"
placeholder: "#606060"
};
export const COLOR_SCHEME_DARK = {

View File

@@ -158,3 +158,5 @@ export const eCloseAnnouncementDialog = "604";
export const eOpenLoading = "605";
export const eCloseLoading = "606";
export const eOnTopicSheetUpdate = "607";

View File

@@ -25,26 +25,35 @@ import SearchService from "../services/search";
import { useSelectionStore } from "../stores/use-selection-store";
import { useMenuStore } from "../stores/use-menu-store";
import { db } from "../common/database";
import { eClearEditor } from "./events";
import { eClearEditor, eOnTopicSheetUpdate } from "./events";
import { useRelationStore } from "../stores/use-relation-store";
import { presentDialog } from "../components/dialog/functions";
function deleteNotesConfirmDialog(items, type, context) {
function deleteConfirmDialog(items, type, context) {
return new Promise((resolve) => {
presentDialog({
title: "Delete Contained Notes?",
paragraph: `Do you want to delete notes within ${
items.length > 1 ? `these ${type}s` : `this ${type}`
title: `Delete ${
items.length > 1 ? `${items.length} ${type}s` : `${type}`
}?`,
positiveText: "Yes",
negativeText: "No",
positivePress: () => {
resolve(true);
positiveText: "Delete",
negativeText: "Cancel",
positivePress: (value) => {
setTimeout(() => {
resolve({ delete: true, deleteNotes: value });
});
},
onClose: () => {
resolve(false);
setTimeout(() => {
resolve({ delete: false });
});
},
context: context
context: context,
check: {
info: `Move all notes in ${
items.length > 1 ? `these ${type}s` : `this ${type}`
} to trash`,
type: "transparent"
}
});
});
}
@@ -107,53 +116,52 @@ export const deleteItems = async (item, context) => {
}
if (topics?.length > 0) {
const deleteNotes = await deleteNotesConfirmDialog(
topics,
"topic",
context
);
for (const topic of topics) {
if (deleteNotes) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
}
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
type: "success"
});
}
if (notebooks?.length > 0) {
const deleteNotes = await deleteNotesConfirmDialog(
notebooks,
"notebook",
context
);
let ids = notebooks.map((i) => i.id);
if (deleteNotes) {
for (let id of ids) {
const topics = db.notebooks.notebook(id).topics.all;
for (let topic of topics) {
const result = await deleteConfirmDialog(topics, "topic", context);
if (result.delete) {
for (const topic of topics) {
if (result.deleteNotes) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
}
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
type: "success"
});
}
await db.notebooks.delete(...ids);
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
}
Navigation.queueRoutesForUpdate(...routesForUpdate);
if (notebooks?.length > 0) {
const result = await deleteConfirmDialog(notebooks, "notebook", context);
if (result.delete) {
let ids = notebooks.map((i) => i.id);
if (result.deleteNotes) {
for (let id of ids) {
const notebook = db.notebooks.notebook(id);
const topics = notebook.topics.all;
for (let topic of topics) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
const notes = db.relations.from(notebook.data, "note");
await db.notes.delete(...notes.map((note) => note.id));
}
}
await db.notebooks.delete(...ids);
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
}
}
Navigation.queueRoutesForUpdate();
let msgPart = history.selectedItemsList.length === 1 ? " item" : " items";
let message = history.selectedItemsList.length + msgPart + " moved to trash.";
@@ -177,7 +185,7 @@ export const deleteItems = async (item, context) => {
}
await db.trash.restore(...ids);
Navigation.queueRoutesForUpdate(routesForUpdate);
Navigation.queueRoutesForUpdate();
useMenuStore.getState().setMenuPins();
useMenuStore.getState().setColorNotes();
ToastEvent.hide();
@@ -186,11 +194,12 @@ export const deleteItems = async (item, context) => {
});
}
history.selectedItemsList = [];
Navigation.queueRoutesForUpdate("Trash");
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().clearSelection(true);
useMenuStore.getState().setMenuPins();
useMenuStore.getState().setColorNotes();
SearchService.updateAndSearch();
eSendEvent(eOnTopicSheetUpdate);
};
export const openLinkInBrowser = async (link) => {

View File

@@ -145,19 +145,12 @@ export function getTotalNotes(item) {
return db.notebooks.notebook(item.id)?.totalNotes || 0;
}
export async function toTXT(note, notitle) {
export async function toTXT(note, template = true) {
let text;
if (note.locked) {
text = note.content.data;
text = await db.notes.note(note.id).export("txt", note.content, template);
} else {
text = await db.notes.note(note.id).content();
}
htmlToText = htmlToText || require("html-to-text");
text = htmlToText.convert(text, {
selectors: [{ selector: "img", format: "skip" }]
});
if (!notitle) {
text = `${note.title}\n \n ${text}`;
text = await db.notes.note(note.id).export("txt", undefined, template);
}
return text;
}

View File

@@ -55,12 +55,13 @@ describe("NOTE TESTS", () => {
await prepare();
let note = await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-Favorite");
await tapById("icon-favorite");
await visibleById("icon-star");
await navigate("Favorites");
await visibleByText(note.body);
await sleep(500);
await tapById(notesnook.listitem.menu);
await tapById("icon-Favorite");
await tapById("icon-favorite");
await expect(element(by.text(note.body))).not.toBeVisible();
await navigate("Notes");
});
@@ -69,11 +70,11 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-Pin");
await tapById("icon-pin");
await visibleByText("Pinned");
await visibleById("icon-pinned");
await tapById(notesnook.listitem.menu);
await tapById("icon-Pin");
await tapById("icon-pin");
expect(element(by.id("icon-pinned"))).not.toBeVisible();
});
@@ -81,11 +82,12 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-PinToNotif");
await visibleByText("Unpin from Notifications");
await tapById("icon-pin-to-notifications");
await visibleByText("Unpin from notifications");
await sleep(500);
await tapById("icon-PinToNotif");
await visibleByText("Pin to Notifications");
await tapById("icon-pin-to-notifications");
await sleep(500);
await visibleByText("Pin to notifications");
});
// it("Copy note", async () => {
@@ -100,7 +102,7 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-Export");
await tapById("icon-export");
await visibleByText("PDF");
});
@@ -122,7 +124,7 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-Delete");
await tapById("icon-delete");
await navigate("Trash");
await tapById(notesnook.listitem.menu);
await tapByText("Restore note");

View File

@@ -29,7 +29,8 @@ import {
navigate,
elementByText,
sleep,
notVisibleByText
notVisibleByText,
visibleById
} from "./utils";
async function createNotebook(
@@ -128,11 +129,11 @@ describe("NOTEBOOKS", () => {
await device.pressBack();
await sleep(500);
await tapByText("Notebook 1");
await tapById(notesnook.buttons.add);
await tapById("add-topic-button");
await elementById("input-title").typeText("Topic");
await tapByText("Add");
await sleep(500);
await visibleByText("Topic");
await visibleById("topic-sheet-item-0");
});
it("Edit topic", async () => {
@@ -145,12 +146,11 @@ describe("NOTEBOOKS", () => {
await sleep(500);
await tapByText("Notebook 1");
await sleep(300);
await visibleByText("Topic");
await visibleById("topic-sheet-item-0");
await tapById(notesnook.ids.notebook.menu);
await tapByText("Edit topic");
await elementById("input-title").typeText(" (edited)");
await tapByText("Save");
await visibleByText("Topic (edited)");
});
it("Add new note to topic", async () => {
@@ -175,12 +175,11 @@ describe("NOTEBOOKS", () => {
await tapByText("Topic");
let note = await createNote();
await elementByText(note.body).longPress();
await tapByText("Select");
await tapById("select-minus");
await notVisibleById(note.title);
});
it.only("Add/Remove note to notebook from home", async () => {
it("Add/Remove note to notebook from home", async () => {
await prepare();
await navigate("Notebooks");
await sleep(500);
@@ -192,31 +191,31 @@ describe("NOTEBOOKS", () => {
await createNote();
console.log("ADD TO A SINGLE TOPIC");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapById("icon-notebooks");
await sleep(500);
await tapByText("Notebook 1");
await tapByText("Topic");
await tapByText("Save");
await sleep(300);
await visibleByText("Notebook 1 Topic");
await visibleByText("Topic");
console.log("MOVE FROM ONE TOPIC TO ANOTHER");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapById("icon-notebooks");
await tapByText("Notebook 1");
await tapByText("Topic 2");
await tapByText("Save");
await visibleByText("Notebook 1 Topic 2");
await visibleByText("Topic 2");
console.log("REMOVE FROM TOPIC");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapById("icon-notebooks");
await tapByText("Notebook 1");
await tapByText("Topic 2");
await tapByText("Save");
await sleep(300);
await notVisibleByText("Notebook 1 Topic 2");
await notVisibleByText("Topic 2");
console.log("MOVE TO MULTIPLE TOPICS");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapById("icon-notebooks");
await tapByText("Notebook 1");
await elementByText("Topic").longPress();
await visibleByText("Reset selection");

View File

@@ -17,13 +17,15 @@ 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 { notesnook } from "../test.ids";
import {
tapById,
visibleByText,
createNote,
prepare,
elementById,
sleep
sleep,
tapByText
} from "./utils";
describe("Search", () => {
@@ -34,6 +36,10 @@ describe("Search", () => {
await sleep(300);
await elementById("search-input").typeText("n");
await sleep(1000);
await tapByText(note.body);
await sleep(1000);
await device.pressBack();
await device.pressBack();
await visibleByText(note.body);
});
});

View File

@@ -164,7 +164,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2037
versionCode 2042
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 23 KiB

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