Compare commits

...

357 Commits

Author SHA1 Message Date
Abdullah Atta
abb89ecbc3 web: bump version to 2.4.10 2023-04-27 15:15:46 +05:00
Abdullah Atta
aa168bec5e desktop: revert "desktop: remove custom protocol interception for https requests" 2023-04-27 15:14:30 +05:00
Abdullah Atta
f9116b22db web: bump version to 2.4.9 2023-04-27 13:16:01 +05:00
Abdullah Atta
5b957f5f0d web: fix Can't resolve './network.worker' error 2023-04-27 13:08:13 +05:00
Abdullah Atta
a578aae3dc web: wait for internet before running sync on device wake 2023-04-27 13:08:13 +05:00
Abdullah Atta
43e5d2fa2d editor: fix table properties tooltip text 2023-04-27 13:08:03 +05:00
Abdullah Atta
b58d034716 theme: use outline instead of boxShadow for input border 2023-04-27 12:59:26 +05:00
Abdullah Atta
3b77d79997 web: prevent pro users from resubscribing 2023-04-27 12:59:26 +05:00
Abdullah Atta
ef4c9af7dc web: rename setters from onXYZ to xyz 2023-04-27 12:59:26 +05:00
ammarahm-ed
8ba3cf172e mobile: release v2.4.14 2023-04-27 12:49:02 +05:00
ammarahm-ed
3e446ce9dd mobile: fix crash on get search items 2023-04-27 11:27:48 +05:00
ammarahm-ed
394aa98713 mobile: remove usage of lazy imports 2023-04-27 10:59:04 +05:00
Muhammad Ali
a2ac882a05 desktop: remove custom protocol interception for https requests (#2426) 2023-04-27 10:13:37 +05:00
ammarahm-ed
cae36487f9 mobile: fix notebook not updated on note 2023-04-27 09:38:44 +05:00
ammarahm-ed
fade5eddbf mobile: replace newline with paragraph in generated html 2023-04-27 08:32:09 +05:00
ammarahm-ed
66b2ffce31 mobile: fix webpack config 2023-04-27 08:32:09 +05:00
ammarahm-ed
13cd2d4daa mobile: refactor 2023-04-27 08:18:25 +05:00
ammarahm-ed
8442faeeb1 mobile: close editor on navigate to topic 2023-04-26 20:06:40 +05:00
Ammar Ahmed
6d18383b22 mobile: sync notes created from quick note/notification notes (#2428)
* mobile: sync quick notes

* Update apps/mobile/app/services/notifications.ts

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>

* mobile: ensure full db init when sync is required

---------

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-04-26 15:15:08 +05:00
ammarahm-ed
f9bcfe2f7a mobile: obfuscate short emails 2023-04-26 15:02:56 +05:00
ammarahm-ed
3621f95dbc web: obfuscate short emails 2023-04-26 15:02:56 +05:00
ammarahm-ed
536dead44d mobile: always load notes on init 2023-04-26 14:59:05 +05:00
ammarahm-ed
39d74e80a9 mobile: ensure that overlay gets hidden when entering foreground 2023-04-26 14:59:05 +05:00
ammarahm-ed
46e03dd248 mobile: only show app lock for main activity 2023-04-26 14:59:05 +05:00
ammarahm-ed
152fee2c29 mobile: reload editor if rendered view lost 2023-04-26 14:59:05 +05:00
ammarahm-ed
492a4d16b2 mobile: fix note options ui 2023-04-26 14:58:37 +05:00
ammarahm-ed
3d7c1fa1e7 mobile: fix configure toolbar sheet close with swipe 2023-04-26 14:58:21 +05:00
ammarahm-ed
69bb0e3656 editor: fix toolbar dropdown buttons size on mobile 2023-04-26 14:54:30 +05:00
ammarahm-ed
4875cfa108 core: duplicate note without content 2023-04-26 14:47:52 +05:00
ammarahm-ed
a28beb62c6 core: deep clone note when duplicating 2023-04-26 14:46:24 +05:00
Abdullah Atta
21c8960146 web: fix "Unauthorized" error on subscription cancel 2023-04-26 11:39:48 +05:00
Abdullah Atta
2ba638b123 web: fix font family on input field labels 2023-04-25 14:47:29 +05:00
ammarahm-ed
8548940cb9 mobile: refactor 2023-04-25 10:18:20 +05:00
Elmin Didic
909bdfa0cf docs: fix grammar & other typos in CONTRIBUTING.md (#2384)
Signed-off-by: Elmin Didic <35606145+ElminD@users.noreply.github.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-04-25 10:13:56 +05:00
ammarahm-ed
bfb3c56abe mobile: remove unused patch file 2023-04-18 02:10:16 +05:00
ammarahm-ed
e06836f701 mobile: fix icon color in dark mode 2023-04-18 02:04:55 +05:00
ammarahm-ed
bc16cb79f2 mobile: fix failing tests 2023-04-18 01:54:51 +05:00
ammarahm-ed
176c46e3f2 mobile: update word count on exit 2023-04-18 01:05:14 +05:00
Abdullah Atta
af65055ce9 web: bump version to 2.4.8 2023-04-18 00:55:37 +05:00
ammarahm-ed
ad23ff19de mobile: update release notes 2023-04-18 00:55:24 +05:00
ammarahm-ed
cf65fc4f57 editor: make drag handle size dynamic 2023-04-18 00:50:41 +05:00
ammarahm-ed
9ab83e7fbb editor: reduce size of task list header 2023-04-18 00:50:20 +05:00
ammarahm-ed
1acf2aadba editor: remove top level font family on headings 2023-04-18 00:32:17 +05:00
Abdullah Atta
06833bcac9 editor: make sure editor font settings apply to all nodes 2023-04-18 00:30:40 +05:00
Ammar Ahmed
3bf6b5293a feat: Change default font size & font family in editor 2023-04-17 23:45:28 +05:00
ammarahm-ed
c40d33df37 mobile: ensure editor reloads properly 2023-04-17 23:32:28 +05:00
ammarahm-ed
fe609f8275 mobile: release v2.4.13 2023-04-17 23:08:29 +05:00
Abdullah Atta
3566f47f2d editor: all toolbars should scroll if buttons overflow 2023-04-17 22:48:43 +05:00
ammarahm-ed
4b5526dffd mobile: fix text exports 2023-04-17 22:43:56 +05:00
ammarahm-ed
4977390394 mobile: return if some days are already selected 2023-04-17 22:41:12 +05:00
ammarahm-ed
87804de452 mobile: auto select current day when repeat mode is selected 2023-04-17 22:41:12 +05:00
Ammar Ahmed
bc55d2b476 mobile: Fix toast shown forever sometimes (#2362) 2023-04-17 22:40:43 +05:00
Abdullah Atta
3ff34a5c82 core: use official html-to-text lib instead of fork 2023-04-17 18:09:29 +05:00
Abdullah Atta
7e01e90e22 core: do not clean relations on removing reminders 2023-04-17 17:25:03 +05:00
Abdullah Atta
d568be6536 editor: fix crash while editing links on mobile 2023-04-17 17:04:24 +05:00
ammarahm-ed
734e79604f mobile: use getFontById from editor 2023-04-16 01:51:49 +05:00
Abdullah Atta
227a962042 core: fix rename tag/color test 2023-04-16 01:04:02 +05:00
Abdullah Atta
a1640175e5 editor: remove unnecessary props from ToolProps 2023-04-16 01:01:01 +05:00
Abdullah Atta
7cf61570ef web: remove default-font component 2023-04-16 00:59:16 +05:00
Abdullah Atta
83762c668e web: refactor default font settings 2023-04-16 00:57:44 +05:00
Abdullah Atta
ecd4a08b1c editor: encapsulate font family utility 2023-04-16 00:57:30 +05:00
ammarahm-ed
7b66462300 mobile: fix crash when tap on notification and
reminder does not exist.
2023-04-15 23:50:24 +05:00
ammarahm-ed
41639fd325 editor: improve editor performance on mobile 2023-04-15 23:25:45 +05:00
ammarahm-ed
2fe82841ef editor: remove useIsKeyboardOpen 2023-04-15 23:25:45 +05:00
Ammar Ahmed
9b7af87611 mobile: fix db initialization in background (#2322)
* core: init collections separately

* mobile: fix db init in background

* mobile: don't call db.init on logout

* mobile: improve db init in app/share extension

* mobile: cleanup

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-04-15 23:23:04 +05:00
Muhammad Ali
d99ccbc243 editor: fix jumping text/bg color popup (#2306)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-04-15 17:53:01 +05:00
Abdulrehman-Jafer
0f191eba5c editor: fix code block highlighting not updating in some cases (#2267)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-04-15 17:50:03 +05:00
Ammar Ahmed
26657c7ee3 mobile: fix app font sizes (#2355) 2023-04-15 17:01:49 +05:00
Abdulrehman-Jafer
a12749eb82 core: disable input sanitization on color/tag rename (#2274)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-04-15 16:42:25 +05:00
Abdulrehman-Jafer
1a56d38116 web: migrate use-database.js to TypeScript (#2287)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-04-15 16:41:14 +05:00
Ammar Ahmed
0b9025af11 mobile: redesign reminder/notebook sheets (#2321) 2023-04-15 16:40:36 +05:00
Muhammad Ali
6f5a44cd11 web: do not show success toast if notebook failed to create (#2307)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-04-15 16:37:32 +05:00
Abdulrehman-Jafer
5e1cc307b1 web: migrate use-media-query.js to Typescript (#2326)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-04-15 16:35:03 +05:00
Ammar Ahmed
2197f1ec5e mobile: fix tag suggestions in share widget (#2354) 2023-04-15 16:31:59 +05:00
NedimHodzic
1da5fd3661 docs: fix grammar issues in mobile/web READMEs (#2345)
Signed-off-by: Nedim Hodzic hodzicnedim11@gmail.com
2023-04-15 05:22:18 +05:00
ammarahm-ed
49e79b52b2 core: add remote note if local note does not exist 2023-04-15 02:17:56 +05:00
ammarahm-ed
c9a657298c mobile: fix crash when data is null in list 2023-04-15 02:03:36 +05:00
Nick Thomas
cfd7caf16c Updated grammar readme
docs: Updated grammar readme
Signed-off-by: Nick Thomas nick02@iastate.edu

Signed-off-by: Nick Thomas <73144044+NickThomas20@users.noreply.github.com>
2023-04-14 06:42:57 +05:00
ammarahm-ed
9744679550 mobile: add support for setting default font-size & font-family 2023-04-08 01:53:46 +05:00
ammarahm-ed
9cdc502ce5 web: fix font family not applied when set on editor 2023-04-08 01:14:33 +05:00
Ammar Ahmed
300a104949 Merge branch 'master' into change-default-font
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-04-07 23:17:23 +05:00
ammarahm-ed
efad6520a2 mobile: release 2.4.12 2023-04-07 22:46:56 +05:00
ammarahm-ed
cc58351731 core: fix notes marked as local only get deleted
on sync
2023-04-07 22:40:10 +05:00
ammarahm-ed
cf2f4c4bbb mobile: fix tag not updated on list item on rename 2023-04-07 14:46:28 +05:00
Abdulrehman-Jafer
cc161b844c web: migrate use-is-user-premium to ts (#2297)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-04-07 14:10:52 +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
Muhammad Ali
df94f31998 web: added state base to default font-family and size 2023-03-22 08:38:21 +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
ammarahm-ed
2c6f205ea9 mobile: exclude deps with "android"
keyword on ios
2023-02-28 16:56:16 +05:00
ammarahm-ed
8154dd1830 mobile: release v2.4.5 2023-02-28 16:41:37 +05:00
ammarahm-ed
686674c855 mobile: fix fdroid build check 2023-02-28 16:32:45 +05:00
Abdullah Atta
8e5d931be1 web: bump version to 2.4.4 2023-02-28 16:18:48 +05:00
Abdullah Atta
46509a04b0 editor: all lists should have the same spacing 2023-02-28 16:18:48 +05:00
Ammar Ahmed
d6e189134c fix ci build (#2027) 2023-02-28 15:21:48 +05:00
ammarahm-ed
0a05780e4d mobile: do not apped date to file name
in bulk exports
2023-02-28 15:18:06 +05:00
Abdullah Atta
1671c99614 web: hide backup reminder on download backup 2023-02-28 14:47:52 +05:00
ammarahm-ed
b5c8afe8f7 mobile: handle same filename 2023-02-28 14:09:09 +05:00
Abdullah Atta
7e4ab8e14b web: sanitize filename for single note export 2023-02-28 14:09:09 +05:00
Abdullah Atta
35210ad02b web: remove unnecessary console.log stmt 2023-02-28 14:09:09 +05:00
Abdullah Atta
9f853f0b36 web: uniquify dup filenames when creating zip 2023-02-28 14:09:09 +05:00
Abdullah Atta
559698c946 web: don't replace non-ascii chars in filename sanitizer 2023-02-28 14:09:09 +05:00
ammarahm-ed
50c05efb48 mobile: fix platform check 2023-02-28 14:07:00 +05:00
Abdullah Atta
94b9d3590d web: add resistence to submenu closing
this takes care of accidentally closing sub menus when moving the mouse
over to it.
2023-02-28 13:45:06 +05:00
Abdullah Atta
092016f942 editor: do not build test files 2023-02-28 12:44:11 +05:00
Abdullah Atta
24c899e709 web: ensure that images get downloaded on realtime sync 2023-02-28 12:11:13 +05:00
Abdullah Atta
e53cc648f0 core: do not unsub all once events on publish
this was a stupid bug where publishing any type of event would
unsubscribe all events that had once set to true.
2023-02-28 12:11:13 +05:00
ammarahm-ed
4c541f3995 mobile: load compressed images in editor 2023-02-28 12:11:13 +05:00
ammarahm-ed
f56cbfc142 mobile: fix loading images in realtime sync 2023-02-28 12:11:13 +05:00
Ammar Ahmed
c21c4f1117 mobile: fix color-scheme not change on toggle (#2024)
system theme
2023-02-28 11:47:04 +05:00
Abdullah Atta
abf1f5c61c editor: add tests for backspace handling 2023-02-28 11:17:05 +05:00
Abdullah Atta
1d5b0793d0 editor: move list node types to a separate file to prevent require loop 2023-02-28 11:17:05 +05:00
Abdullah Atta
5ba72fdbd4 editor: adjust sub list indentation 2023-02-28 11:17:05 +05:00
alihamuh
c7d26d2444 editor: improve backspace handling with lists 2023-02-28 11:17:05 +05:00
Muhammad Ali
22c146e350 web: fix context menu crash due to incorrect topic references (#2016) 2023-02-28 10:19:26 +05:00
Ammar Ahmed
1cb0ebd4f8 mobile: fix scroll into view on ios (#2017) 2023-02-27 16:06:06 +05:00
Ammar Ahmed
eff2e475ff mobile: add oss licenses (#2018) 2023-02-27 16:05:47 +05:00
Abdullah Atta
aef475d8fc ci: fix workflow step name 2023-02-27 11:07:06 +05:00
Abdullah Atta
1ed2e55867 ci: add workflow for editor tests 2023-02-27 11:07:06 +05:00
Abdullah Atta
16ffec1f1e editor: set up integration tests 2023-02-27 11:07:06 +05:00
Abdullah Atta
912e97dc3c editor: add button to sort task list 2023-02-27 11:07:06 +05:00
Abdullah Atta
ca2f924417 ci: add config for DCO PR check 2023-02-27 11:05:06 +05:00
Muhammad Ali
468fba8fcf web: hide status bar in tablet mode (#2001) 2023-02-25 11:58:32 +05:00
Abdullah Atta
b948dcee22 editor: optimize clear all completed tasks action 2023-02-25 11:55:46 +05:00
Abdullah Atta
a633f4a3fe editor: task list should update when its items are toggled 2023-02-25 11:53:57 +05:00
Abdullah Atta
acb384c901 editor: fix Enter key not working on some keyboards in Android 2023-02-24 11:47:10 +05:00
Abdullah Atta
043228fe47 editor: fix link popup not opening on link focus 2023-02-24 09:20:35 +05:00
Muhammad Ali
f29e7936d1 editor: fix link colors in link hover popup (#1991)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-02-23 17:19:13 +05:00
Ammar Ahmed
d4a7b8d9c9 editor: add borders/scrolling to tagbar on mobile (#1997) 2023-02-23 17:07:44 +05:00
Muhammad Ali
fbabfe468a editor: adjust horizontal rule's vertical spacing (#1985) 2023-02-23 17:00:13 +05:00
Muhammad Ali
87c7eccee4 editor: preserve all whitespaces while setting content (#1978)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-02-23 16:34:46 +05:00
Ammar Ahmed
648eac5555 mobile: fix login 2fa sheet (#1996)
* mobile: fix login 2fa sheet not open inside modal

* mobile: disable buttons in loading state
2023-02-23 16:15:09 +05:00
alihamuh
aec1d601a3 web: added change default fontstyle feature in the settings 2023-01-18 13:23:39 +05:00
385 changed files with 22472 additions and 6851 deletions

View File

@@ -21,4 +21,7 @@ packages/editor/styles/
packages/editor/languages/
# editor mobile
packages/editor-mobile/build.bundle
packages/editor-mobile/build.bundle
# snapshots
tap-snapshots

4
.github/dco.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
allowRemediationCommits:
individual: true
require:
members: false

View File

@@ -50,7 +50,18 @@ jobs:
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Build apks for Github release
run: yarn release:android
@@ -91,15 +102,4 @@ jobs:
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFile: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk

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

57
.github/workflows/editor.tests.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Test @notesnook/editor
on:
workflow_dispatch:
push:
branches:
- "master"
paths:
- "packages/editor/**"
# re-run workflow if workflow file changes
- ".github/workflows/editor.tests.yml"
pull_request:
types:
- "ready_for_review"
- "opened"
- "synchronize"
- "reopened"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
- name: Install packages
run: |
npm i --ignore-scripts --prefer-offline --no-audit
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
- name: Setup environment
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

3
.gitignore vendored
View File

@@ -6,4 +6,5 @@ dist
nx-cloud.env
.idea
.eslintcache
.env.local
.env.local
.nyc_output

View File

@@ -8,9 +8,9 @@ Thank you so much for considering to contribute to Notesnook! If you have no ide
Ugh! Bugs!
> A bug is when software behaves in a way that you didn't expect, and the developer didn't intend.
> A bug is when software behaves in a way you didn't expect, which the developer didn't intend.
To help us understand what's going on, we first want to **make sure you're using the latest version of Notesnook**.
To help us understand what's happening, we first want to **make sure you're using the latest version of Notesnook**.
Once you've **confirmed that the bug still exists in the latest version**, you'll want to check to make sure it's not something we already know about in the [opened GitHub issues](https://github.com/streetwriters/notesnook/issues).
@@ -30,7 +30,7 @@ Before you open a new feature request, please make sure it's not a duplicate. **
### Helping out in the issue tracker
There are always new issues getting opened that need to be triaged, sorted & organized, so the developers can easily find the most critical and/or relevant bugs to fix. Any help in this regard is appreciated.
New issues are always opened that need to be triaged, sorted & organized, so the developers can easily find the most critical and/or relevant bugs to fix. Any help in this regard is appreciated.
In addition to this, you can help out in the following ways:
@@ -62,7 +62,7 @@ Once you are done, [open a new pull request](https://docs.github.com/en/pull-req
1. Fork [the repository](https://github.com/streetwriters/notesnook) and create your branch from `master` (you can name your branch anything).
2. Run `npm run bootstrap` in the repository root.
3. If youve fixed a bug or added code that should be tested, add tests!
3. If you've fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`npm run test`).
5. Format your code with prettier (`npm run prettier`).
6. Make sure your code lints (`npm run lint`). Tip: `npm run linc` to only check changed files.
@@ -131,11 +131,11 @@ We use an automatic code formatter called [Prettier](https://prettier.io/). Run
Then, our linter will catch most issues that may exist in your code. You can check the status of your code styling by simply running `npm run lint`.
However, there are still some styles that the linter cannot pick up. If you are unsure about something, looking at [Airbnbs Style Guide](https://github.com/airbnb/javascript) will guide you in the right direction.
However, there are still some styles that the linter cannot pick up. If you are unsure about something, looking at [Airbnb's Style Guide](https://github.com/airbnb/javascript) will guide you in the right direction.
## Git Branch Organization
Submit all changes directly to the [`master branch`](https://github.com/streetwriters/notesnook). We dont use separate branches for development or for upcoming releases. This requires us to always keep the `master` branch in a deployable state which means:
Submit all changes directly to the [`master branch`](https://github.com/streetwriters/notesnook). We don't use separate branches for development or for upcoming releases. This requires us to always keep the `master` branch in a deployable state which means:
1. All tests must be passing at all times
2. There should be as few breaking changes as possible

View File

@@ -10,11 +10,11 @@
## Overview
Notesnook is a free (as in speech) & open source note taking app focused on user privacy & ease of use. To ensure zero knowledge principles, Notesnook encrypts everything on your device using `XChaCha20-Poly1305` & `Argon2`.
Notesnook is a free (as in speech) & open-source note-taking app focused on user privacy & ease of use. To ensure zero knowledge principles, Notesnook encrypts everything on your device using `XChaCha20-Poly1305` & `Argon2`.
Notesnook is our **proof** that privacy does _not_ (always) have to come at the cost of convenience. Our goal is to provide users peace of mind & 100% confidence that their notes are safe and secure. The decision to go fully open source is one of the most crucial steps towards that.
Notesnook is our **proof** that privacy does _not_ (always) have to come at the cost of convenience. We aim to provide users peace of mind & 100% confidence that their notes are safe and secure. The decision to go fully open source is one of the most crucial steps towards that.
This repository contains all the code required to build & use the Notesnook web, desktop & mobile clients. If you are looking for a full features list or screenshots, please check the [website](https://notesnook.com/).
This repository contains all the code required to build & use the Notesnook web, desktop & mobile clients. If you are looking for a full feature list or screenshots, please check the [website](https://notesnook.com/).
## Developer guide

View File

@@ -19,9 +19,9 @@
## Build instructions
> **Before you start it is recommended that you read [the contributing guidelines](/CONTRIBUTING.md).**
> **Before you start, it is recommended that you read [the contributing guidelines](/CONTRIBUTING.md).**
### Setting up development environment
### Setting up the development environment
Requirements:
@@ -57,7 +57,7 @@ npm install
### Running the app on Android
[Setup an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) if you haven't already and then run the following command to start the app in the Emulator:
[Setup an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) if you haven't already, and then run the following command to start the app in the Emulator:
```bash
npm run start:android
@@ -78,11 +78,11 @@ npm run start:ios
## Developer guide
> This project is in a transition state between Javascript & Typescript. We are gradually porting everything over to Typescript so if you can help with that, it'd be great!
> This project is in a transition state between Javascript & Typescript. We are gradually porting everything over to Typescript, so if you can help with that, it'd be great!
### The tech stack
We try to keep the stack as lean as possible
We try to keep the stack as lean as possible:
1. React Native
2. Typescript/Javascript
@@ -95,17 +95,17 @@ We try to keep the stack as lean as possible
The app codebase is distributed over two primary directories. `native/` and `app/`.
- `native/`: Includes `android/` and `ios/` folders and everything related to react native core functionality like bundling, development and packaging. Any react-native dependency that has native code i.e android & ios folders, is installed here.
- `native/`: Includes `android/` and `ios/` folders and everything related to react native core functionality like bundling, development, and packaging. Any react-native dependency with native code, i.e., android & ios folders, is installed here.
- `app/`: Includes all the app code other than the native part. All JS only dependencies are installed here.
- `components/`: Each component serves a specific purpose in the app UI, for example the `Paragraph` component is used to render paragraphs in the app and a `Header` component is used to render a `header` on all screens.
- `common/`: Features that have integral role in app functionality, for example, notesnook core is initialized here.
- `app/`: Includes all the app code other than the native part. All JS-only dependencies are installed here.
- `components/`: Each component serves a specific purpose in the app UI. For example, the `Paragraph` component is used to render paragraphs in the app, and a `Header` component is used to render a `header` on all screens.
- `common/`: Features that are integral to the app's functionality. For example, the notesnook core is initialized here.
- `hooks/`: Hooks for different app logic
- `navigation/`: Includes app navigation specific code. Here the app navigation, editor & side menu are rendered side by side in fluid tabs.
- `navigation/`: Includes app navigation-specific code. Here the app navigation, editor & side menu are rendered side by side in fluid tabs.
- `screens`: Navigator screens.
- `services`: Parts of code that do a specific function, for example, the `sync` service is responsibe for running Sync from anywhere in the app.
- `stores`: We use `zustand` for global state management in the app. There are multiple stores that provide the state for different parts of the app.
- `utils`: General purpose stuff such as constant values, utility functions etc.
- `services`: Parts of code that do a specific function. For example, the `sync` service runs Sync from anywhere in the app.
- `stores`: We use `zustand` for global state management in the app. Multiple stores provide the state for different parts of the app.
- `utils`: General purpose stuff such as constant values, utility functions, etc.
There are several other folders at the root:
@@ -115,11 +115,11 @@ There are several other folders at the root:
### Running the tests
When you are done making the required changes, you will need to run the tests to make sure you didn't break anything. We use Detox as the testing framework & the tests can be started as follows:
When you are done making the required changes, you must run the tests to ensure you didn't break anything. We use Detox as the testing framework & the tests can be started as follows:
### Android
To run the tests on android, you will need to create an emulator device on your system:
To run the tests on Android, you will need to create an emulator device on your system:
```
$ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_5_API_31 -d pixel --package "system-images;android-31;default;x86_64"
@@ -127,13 +127,13 @@ $ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_5_API_31 -d pixel --packa
If you face problems, follow the detailed guide in [Detox documentation](https://wix.github.io/Detox/docs/introduction/android-dev-env). Keep the emulator name set to `Pixel_5_API_31`.
Once you have created an emulator device, build the android apks
Once you have created an emulator device, build the Android apks:
```
npm run build:android
```
Finally run the tests
Finally, run the tests:
```
npm run test:android
@@ -141,9 +141,9 @@ npm run test:android
### iOS
To run e2e tests on iOS simulator, you must be on a Mac with XCode installed.
To run e2e tests on the iOS simulator, you must be on a Mac with XCode installed.
First install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils)
First, install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils):
```
brew tap wix/brew
@@ -156,7 +156,7 @@ Now build the iOS app for testing:
npm run build:ios
```
Finally run the tests:
Finally, run the tests:
```
npm run test:ios

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -68,14 +68,3 @@ db.host(
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
export async function loadDatabase() {
// if (!DB) {
// let module = await import(/* webpackChunkName: "notes-core" */ 'notes-core/api/index');
// DB = module.default;
// }
// db = new DB(Storage, Platform.OS === 'ios' ? EventSource : AndroidEventSource, filesystem);
// if (DOMParser) {
// await DOMParser.prepare();
// }
}

View File

@@ -0,0 +1,50 @@
/*
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 { Dimensions, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "rn-fetch-blob";
/**
* Scale down & compress images to screen width
* for loading in editor.
* @returns
*/
export async function compressToBase64(path, type) {
const { width, scale } = Dimensions.get("window");
const response = await ImageResizer.createResizedImage(
path,
width * scale,
9999,
type,
80,
0,
undefined,
true,
{
mode: "contain",
onlyScaleDown: true
}
);
const base64 = await RNFetchBlob.fs.readFile(
Platform.OS === "ios" ? response.uri?.replace("file://", "") : response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(console.log);
return base64;
}

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

@@ -21,6 +21,8 @@ import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { cacheDir, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
export async function readEncrypted(filename, key, cipherData) {
let path = `${cacheDir}/${filename}`;
@@ -29,6 +31,9 @@ export async function readEncrypted(filename, key, cipherData) {
if (!exists) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
let output = await Sodium.decryptFile(
key,
@@ -36,8 +41,20 @@ export async function readEncrypted(filename, key, cipherData) {
...cipherData,
hash: filename
},
cipherData.outputType === "base64" ? "base64" : "text"
cipherData.outputType === "base64"
? isPng || isJpeg
? "cache"
: "base64"
: "text"
);
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
const dCachePath = `${cacheDir}/${output}`;
output = await compressToBase64(
`file://${dCachePath}`,
isPng ? "PNG" : "JPEG"
);
}
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
@@ -46,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

@@ -148,7 +148,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
<Button
key={item.title}
title={item.title}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
type="gray"
onPress={() => onPress(item)}
width={null}

View File

@@ -67,7 +67,7 @@ export const Title = ({ text, style = {}, inline }) => {
right: 0
}}
iconSize={24}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={{
borderRadius: 100,
paddingVertical: 0,

View File

@@ -209,7 +209,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
>
<Paragraph
size={SIZE.xs + 1}
size={SIZE.xs}
style={{
marginRight: 10
}}
@@ -221,7 +221,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
style={{
marginRight: 10
}}
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
>
{formatBytes(attachment.length)}
@@ -232,7 +232,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
style={{
marginRight: 10
}}
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
>
{attachment.noteIds.length} note
@@ -248,7 +248,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
context: "local"
});
}}
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
>
{attachment.metadata.hash}
@@ -304,7 +304,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
key={item.id}
>
<Paragraph size={SIZE.xs + 1}>{item.title}</Paragraph>
<Paragraph size={SIZE.xs}>{item.title}</Paragraph>
</PressableButton>
))}
</>

View File

@@ -36,7 +36,11 @@ function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
/**
*
* @param {any} param0
* @returns
*/
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const colors = useThemeStore((state) => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
@@ -46,7 +50,6 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const encryptionProgress = useAttachmentStore(
(state) => state.encryptionProgress
);
const onPress = () => {
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
@@ -122,7 +125,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

@@ -26,7 +26,6 @@ import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
@@ -83,9 +82,6 @@ export const Login = ({ changeMode }) => {
<>
<ForgotPassword />
<SheetProvider context="two_factor_verify" />
{loading ? (
<BaseDialog transparent={true} visible={true} animation="fade" />
) : null}
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
@@ -134,6 +130,7 @@ export const Login = ({ changeMode }) => {
marginTop: 5
}}
onPress={() => {
if (loading) return;
changeMode(1);
}}
size={SIZE.md}
@@ -172,7 +169,7 @@ export const Login = ({ changeMode }) => {
errorMessage="Email is invalid"
placeholder="Enter your email"
defaultValue={email.current}
editable={step === LoginSteps.emailAuth}
editable={step === LoginSteps.emailAuth && !loading}
onSubmit={() => {
passwordInputRef.current?.focus();
}}
@@ -194,6 +191,7 @@ export const Login = ({ changeMode }) => {
autoCorrect={false}
placeholder="Password"
marginBottom={0}
editable={!loading}
defaultValue={password.current}
onSubmit={() => login()}
/>
@@ -205,6 +203,7 @@ export const Login = ({ changeMode }) => {
paddingHorizontal: 0
}}
onPress={() => {
if (loading) return;
SheetManager.show("forgotpassword_sheet", email.current);
}}
textStyle={{
@@ -228,7 +227,10 @@ export const Login = ({ changeMode }) => {
borderRadius: 100
}}
loading={loading}
onPress={login}
onPress={() => {
if (loading) return;
login();
}}
type="accent"
title={
loading
@@ -248,6 +250,7 @@ export const Login = ({ changeMode }) => {
marginTop: 10
}}
onPress={() => {
if (loading) return;
setStep(LoginSteps.emailAuth);
setLoading(false);
}}

View File

@@ -44,8 +44,10 @@ import Paragraph from "../ui/typography/paragraph";
import { LoginSteps, useLogin } from "./use-login";
function getObfuscatedEmail(email) {
if (!email) return null;
return email.replace(/(.{2})(.*)(?=@)/, function (gp1, gp2, gp3) {
if (!email) return "";
const [username, provider] = email.split("@");
if (username.length === 1) return `****@${provider}`;
return email.replace(/(.{1})(.*)(?=@)/, function (gp1, gp2, gp3) {
for (let i = 0; i < gp3.length; i++) {
gp2 += "*";
}

View File

@@ -30,7 +30,6 @@ import { useUserStore } from "../../stores/use-user-store";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
@@ -97,9 +96,6 @@ export const Signup = ({ changeMode, trial }) => {
return (
<>
{loading ? (
<BaseDialog transparent={true} visible={true} animation="fade" />
) : null}
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
@@ -148,6 +144,7 @@ export const Signup = ({ changeMode, trial }) => {
textAlign: "center"
}}
onPress={() => {
if (loading) return;
changeMode(0);
}}
size={SIZE.md}
@@ -235,7 +232,10 @@ export const Signup = ({ changeMode, trial }) => {
borderRadius: 100
}}
loading={loading}
onPress={signup}
onPress={() => {
if (loading) return;
signup();
}}
type="accent"
title={loading ? null : "Agree and continue"}
/>

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

@@ -86,7 +86,7 @@ export const ProFeatures = ({ count = 6 }) => {
await sleep(300);
eSendEvent(eOpenPremiumDialog);
}}
size={SIZE.xs + 1}
size={SIZE.xs}
style={{
textDecorationLine: "underline",
color: colors.icon

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

@@ -17,12 +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 notifee from "@notifee/react-native";
import React, { useCallback, useEffect, useRef } from "react";
import { Platform, View } from "react-native";
import RNBootSplash from "react-native-bootsplash";
import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
import { DatabaseLogger, db } from "../../common/database";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import { eSendEvent, presentSheet } from "../../services/event-manager";
@@ -35,7 +37,9 @@ import { useNoteStore } from "../../stores/use-notes-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { AndroidModule } from "../../utils";
import { eOpenAnnouncementDialog } from "../../utils/events";
import { getGithubVersion } from "../../utils/github-version";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { SVG } from "../auth/background";
@@ -50,10 +54,6 @@ import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import Config from "react-native-config";
import { getGithubVersion } from "../../utils/github-version";
import notifee from "@notifee/react-native";
const Launcher = React.memo(
function Launcher() {
@@ -70,7 +70,7 @@ const Launcher = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const dbInitCompleted = useRef(false);
const loadNotes = useCallback(async () => {
if (verifyUser) {
return;
@@ -81,28 +81,25 @@ const Launcher = React.memo(
initialize();
setImmediate(() => {
setLoading(false);
if (!dbInitCompleted.current) {
setImmediate(() => doAppLoadActions());
}
});
});
});
}, [doAppLoadActions, setLoading, verifyUser]);
}, [setLoading, verifyUser]);
const init = useCallback(async () => {
if (!dbInitCompleted.current) {
if (!db.isInitialized) {
await RNBootSplash.hide({ fade: true });
await loadDatabase();
DatabaseLogger.info("Initializing database");
await db.init();
dbInitCompleted.current = true;
}
if (db.migrations.required() && !verifyUser) {
presentSheet({
component: <Migrate />,
onClose: async () => {
await db.init();
if (!db.isInitialized) {
await db.init();
}
loadNotes();
},
disableClosing: true
@@ -121,9 +118,6 @@ const Launcher = React.memo(
if (!loading) {
doAppLoadActions();
}
return () => {
dbInitCompleted.current = false;
};
}, [doAppLoadActions, loading]);
const doAppLoadActions = useCallback(async () => {
@@ -151,7 +145,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"
@@ -199,6 +193,11 @@ const Launcher = React.memo(
const onUnlockBiometrics = useCallback(async () => {
if (!(await BiometricService.isBiometryAvailable())) return;
if (Platform.OS === "android") {
const activityName = await AndroidModule.getActivityName();
if (activityName !== "MainActivity") return;
}
let verified = await BiometricService.validateUser(
"Unlock to access your notes",
""

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

@@ -108,7 +108,7 @@ const ReminderItem = React.memo(
color={colors.errorText}
/>
<Paragraph
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
style={{ marginLeft: 5 }}
>
@@ -133,7 +133,7 @@ const ReminderItem = React.memo(
>
<Icon name="reload" size={SIZE.md} color={colors.accent} />
<Paragraph
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
style={{ marginLeft: 5 }}
>
@@ -147,7 +147,7 @@ const ReminderItem = React.memo(
<ReminderTime
reminder={item}
checkIsActive={false}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={{
justifyContent: "flex-start",
borderWidth: 0,

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}
@@ -222,12 +218,14 @@ const List = ({
}
/>
</Animated.View>
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === "Notes" ? "home" : type}
scrollRef={scrollRef}
/>
{listData ? (
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === "Notes" ? "home" : type}
scrollRef={scrollRef}
/>
) : null}
</>
);
};

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

@@ -143,7 +143,7 @@ export const Expiring = () => {
await sleep(300);
eSendEvent(eOpenPremiumDialog, promo);
}}
size={SIZE.xs + 1}
size={SIZE.xs}
style={{
textDecorationLine: "underline",
color: colors.icon,

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

@@ -62,10 +62,10 @@ export const DateMeta = ({ item }) => {
paddingVertical: 3
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
<Paragraph size={SIZE.xs} color={colors.icon}>
{getNameFromKey(key)}
</Paragraph>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
<Paragraph size={SIZE.xs} color={colors.icon}>
{timeConverter(item[key])}
</Paragraph>
</View>

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}
/>
) : 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,18 +108,19 @@ export const Items = ({ item, buttons, close }) => {
/>
);
const renderTopBarItem = ({ item }) => {
const renderTopBarItem = (item, index) => {
const isLast = index === topBarItems.length;
return (
<PressableButton
onPress={item.func}
key={item.id}
testID={"icon-" + item.id}
activeOpacity={1}
customStyle={{
alignItems: "center",
width: topBarItemWidth,
marginBottom: 10,
marginRight: 10,
backgroundColor: "transparent"
alignSelf: "flex-start",
marginRight: isLast ? 0 : 10,
paddingHorizontal: 0,
width: topBarItemWidth
}}
>
<PressableButton
@@ -149,11 +150,7 @@ export const Items = ({ item, buttons, close }) => {
/>
</PressableButton>
<Paragraph
size={SIZE.xxs + 1}
style={{ textAlign: "center" }}
textBreakStrategy="simple"
>
<Paragraph size={SIZE.xxs + 1} style={{ textAlign: "center" }}>
{item.title}
</Paragraph>
</PressableButton>
@@ -169,7 +166,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 +175,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
paddingVertical: 12
}}
renderItem={renderTopBarItem}
/>
>
{topBarItems.map(renderTopBarItem)}
</ScrollView>
<FlatList
data={bottomGridItems}
@@ -202,10 +197,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 +209,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

@@ -23,20 +23,33 @@ import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import Notebook from "../../screens/notebook";
import { TopicNotes } from "../../screens/notes/topic-notes";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import {
eSendEvent,
presentSheet,
ToastEvent
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useNotebookStore } from "../../stores/use-notebook-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import { eClearEditor } from "../../utils/events";
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 +58,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 +83,6 @@ export default function Notebooks({ note, close, full }) {
if (!item) return;
TopicNotes.navigate(item, true);
};
const renderItem = (item) => (
<View
key={item.id}
@@ -78,7 +97,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
@@ -97,6 +117,7 @@ export default function Notebooks({ note, close, full }) {
size={SIZE.sm}
onPress={() => {
navigateNotebook(item.id);
eSendEvent(eClearEditor);
close();
}}
>
@@ -119,6 +140,7 @@ export default function Notebooks({ note, close, full }) {
key={topic.id}
onPress={() => {
navigateTopic(topic.id, item.id);
eSendEvent(eClearEditor);
close();
}}
onLongPress={async () => {
@@ -130,13 +152,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",
@@ -146,7 +162,7 @@ export default function Notebooks({ note, close, full }) {
title={topic.title}
type="gray"
height={30}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
icon="bookmark-outline"
style={{
marginRight: 5,
@@ -160,7 +176,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%",
@@ -175,7 +191,7 @@ export default function Notebooks({ note, close, full }) {
{noteNotebooks.length > 1 && !full ? (
<Button
title={`See all linked notebooks`}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={{
alignSelf: "flex-end",
marginRight: 12,

View File

@@ -91,10 +91,10 @@ export const Synced = ({ item, close }) => {
console.error(e);
}
}}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
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
@@ -58,7 +57,7 @@ export const Tags = ({ item, close }) => {
icon="plus"
iconPosition="right"
height={30}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={{
marginRight: 5,
borderRadius: 100,
@@ -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,14 +101,13 @@ const TagItem = ({ tag, close }) => {
marginTop: 0,
backgroundColor: "transparent"
};
return (
<Button
onPress={onPress}
title={"#" + tag}
type="grayBg"
height={20}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={style}
textStyle={{
textDecorationLine: "underline"

View File

@@ -39,7 +39,7 @@ export const Topics = ({ item, close }) => {
height={30}
onPress={() => open(topic)}
icon="bookmark-outline"
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={{
marginRight: 5,
paddingHorizontal: 8,

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 { FlatList } from "react-native-actions-sheet";
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 { ph, pv, SIZE } from "../../../utils/size";
import { DDS } from "../../../services/device-detection";
import { ToastEvent, presentSheet } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { SIZE, ph, pv } 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 { 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 Heading from "../../ui/typography/heading";
import { MoveNotes } from "../move-notes/movenote";
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);
}
}
@@ -299,183 +245,166 @@ export class AddNotebookSheet extends React.Component {
willFocus && this.topicInputRef.current?.focus();
};
renderTopicItem = ({ 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={this.props.colors}
/>
);
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%" : "97%",
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}
>
<TextInput
ref={this.hiddenInput}
style={{
width: 1,
height: 1,
opacity: 0,
position: "absolute"
}}
blurOnSubmit={false}
/>
<View
style={{
maxHeight: DDS.isTab ? "90%" : "96%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: 12
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
alignItems: "center"
}}
>
<TextInput
ref={this.hiddenInput}
style={{
width: 1,
height: 1,
opacity: 0,
position: "absolute"
}}
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
}
});
}
}}
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 />
<Heading size={SIZE.lg}>
{notebook && notebook.dateCreated
? "Edit Notebook"
: "New Notebook"}
</Heading>
<Button
width="100%"
height={50}
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated
? "Save changes"
: "Create notebook"
}
title="Save"
type="accent"
height={40}
style={{
borderRadius: 100,
paddingHorizontal: 24
}}
fontSize={SIZE.md}
onPress={this.addNewNotebook}
/>
{/*
{Platform.OS === 'ios' && (
<View
style={{
height: 40
}}
/>
)} */}
</View>
<Toast context="local" />
</SheetWrapper>
<Seperator />
<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
}
});
}
}}
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()}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={
topics.length === 0 ? null : <View style={{ height: 50 }} />
}
renderItem={this.renderTopicItem}
/>
</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,45 @@ 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";
import { useRelationStore } from "../../../stores/use-relation-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 +114,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 +139,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 +199,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 +214,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 +244,7 @@ const MoveNoteComponent = ({ note }) => {
resetItemState(state);
}
}),
[multiSelect, resetItemState, updateItemState]
[resetItemState, updateItemState]
);
const getItemFromId = (id) => {
@@ -314,39 +257,47 @@ 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) {
await 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) {
await 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();
useRelationStore.getState().update();
actionSheetRef.current?.hide();
};
@@ -397,34 +348,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 +399,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 +410,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 +446,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,16 +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";
import Placeholder from "./index";
type SelectionItemState = Record<
string,
"intermediate" | "selected" | "deselected"
>;
function FavoritesPlaceholder() {
return (
<Placeholder
id="favorites"
title="Your favorites"
text="Notes you favorite will appear here."
/>
);
export interface SelectionStore extends State {
itemState: SelectionItemState;
setItemState: (state: SelectionItemState) => void;
multiSelect: boolean;
setMultiSelect: (multiSelect: boolean) => void;
}
export default FavoritesPlaceholder;
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;
};
@@ -166,11 +158,7 @@ export const MoveNotes = ({
{item.title}
</Paragraph>
{item.type == "note" && item.headline ? (
<Paragraph
numberOfLines={1}
color={colors.icon}
size={SIZE.xs + 1}
>
<Paragraph numberOfLines={1} color={colors.icon} size={SIZE.xs}>
{item.headline}
</Paragraph>
) : null}
@@ -277,10 +265,6 @@ export const MoveNotes = ({
)}
<FlashList
nestedScrollEnabled
onMomentumScrollEnd={() => {
fwdRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
<View
style={{
@@ -322,15 +306,7 @@ export const MoveNotes = ({
},
...selectedNoteIds
);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook",
"Notebooks"
);
Navigation.queueRoutesForUpdate();
SearchService.updateAndSearch();
eSendEvent(eCloseSheet);
}}
@@ -345,7 +321,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

@@ -43,8 +43,7 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import { QRCode } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
let RNFetchBlob;
import RNFetchBlob from "rn-fetch-blob";
class RecoveryKeySheet extends React.Component {
constructor(props) {
@@ -115,7 +114,6 @@ class RecoveryKeySheet extends React.Component {
this.svg.current?.toDataURL(async (data) => {
try {
let path;
RNFetchBlob = (await import("rn-fetch-blob")).default;
let fileName = "nn_" + this.user.email + "_recovery_key_qrcode";
fileName = sanitizeFilename(fileName, { replacement: "_" });
fileName = fileName + ".png";
@@ -151,7 +149,6 @@ class RecoveryKeySheet extends React.Component {
fileName = sanitizeFilename(fileName, { replacement: "_" });
fileName = fileName + ".txt";
RNFetchBlob = (await import("rn-fetch-blob")).default;
if (Platform.OS === "android") {
let file = await ScopedStorage.createDocument(
fileName,
@@ -298,7 +295,6 @@ class RecoveryKeySheet extends React.Component {
width="100%"
type="grayAccent"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
<Button
@@ -308,7 +304,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
fontSize={SIZE.md}
icon="qrcode"
height={50}
/>
<Seperator />
<Button
@@ -318,7 +313,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="text"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -329,7 +323,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="cloud"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -349,7 +342,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;
@@ -119,7 +119,7 @@ export default function ReminderNotify({
marginTop: 10
}}
>
<Paragraph size={SIZE.xs + 1}>Remind me in:</Paragraph>
<Paragraph size={SIZE.xs}>Remind me in:</Paragraph>
{QuickActions.map((item) => {
return (
<Button
@@ -127,7 +127,7 @@ export default function ReminderNotify({
key={item.title}
title={item.title}
height={30}
fontSize={SIZE.xs + 1}
fontSize={SIZE.xs}
style={{ marginLeft: 10, borderRadius: 100 }}
onPress={() => onSnooze(item.time)}
/>
@@ -150,7 +150,7 @@ export default function ReminderNotify({
<Paragraph
style={{
color: colors.icon,
fontSize: SIZE.xs + 1,
fontSize: SIZE.xs,
marginBottom: 10
}}
>

View File

@@ -17,19 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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,
PresentSheetOptions,
ToastEvent
ToastEvent,
presentSheet
} from "../../../services/event-manager";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
@@ -39,18 +33,20 @@ import Input from "../../ui/input";
import dayjs from "dayjs";
import DatePicker from "react-native-date-picker";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import Navigation from "../../../services/navigation";
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 Heading from "../../ui/typography/heading";
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 +113,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 +132,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 (
@@ -201,7 +200,8 @@ export default function ReminderSheet({
if (!_reminder) {
ToastEvent.show({
heading: "Failed to add a new reminder"
heading: "Failed to add a new reminder",
context: "local"
});
}
if (reference) {
@@ -211,57 +211,68 @@ 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) {
ToastEvent.error(e as Error);
ToastEvent.error(e as Error, undefined, "local");
}
}
return (
<View
style={{
paddingHorizontal: 12
paddingHorizontal: 12,
maxHeight: DDS.isTab ? "90%" : "99.99%"
}}
>
<Dialog context="local"/>
<ScrollView
onScrollEndDrag={() => actionSheetRef.current?.handleChildScrollEnd()}
<View
style={{
maxHeight: height * 0.85
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading size={SIZE.lg}>Set reminder</Heading>
<Button
title="Save"
type="accent"
height={40}
style={{
borderRadius: 100,
paddingHorizontal: 24
}}
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View>
<Dialog context="local" />
<ScrollView>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
placeholder="Remind me of..."
onChangeText={(text) => (title.current = text)}
containerStyle={{ borderWidth: 0, borderBottomWidth: 1 }}
wrapperStyle={{
marginTop: 10
}}
/>
<Input
defaultValue={reminder ? reminder?.description : referencedItem?.headline}
placeholder="Add a quick note"
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
placeholder="Add a short note"
onChangeText={(text) => (details.current = text)}
containerStyle={{
borderWidth: 0,
borderBottomWidth: 1,
maxHeight: 80,
marginTop: 10
maxHeight: 80
}}
multiline
textAlignVertical="top"
inputStyle={{
height: 80
minHeight: 80,
paddingVertical: 12
}}
height={80}
wrapperStyle={{
@@ -299,6 +310,16 @@ export default function ReminderSheet({
mode as keyof typeof ReminderModes
] as Reminder["mode"]
);
if (mode === "Repeat") {
setSelectedDays((days) => {
if (days.length > 0) return days;
if (days.indexOf(date.getDay()) > -1) {
return days;
}
days.push(date.getDay());
return [...days];
});
}
}}
/>
))}
@@ -440,11 +461,13 @@ export default function ReminderSheet({
<DatePicker
date={date}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm}
textColor={colors.night ? "#ffffff" : "#000000"}
fadeToColor={colors.bg}
theme={colors.night ? "dark" : "light"}
is24hourSource="locale"
androidVariant="nativeAndroid"
mode={reminderMode === ReminderModes.Repeat ? "time" : "datetime"}
/>
@@ -527,7 +550,7 @@ export default function ReminderSheet({
}}
>
<>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
<Paragraph size={SIZE.xs} color={colors.icon}>
{recurringMode === RecurringModes.Daily
? "Repeats daily " + `at ${dayjs(date).format("hh:mm A")}.`
: selectedDays.length === 7 &&
@@ -562,15 +585,6 @@ export default function ReminderSheet({
}}
/>
</ScrollView>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View>
);
}
@@ -582,6 +596,8 @@ ReminderSheet.present = (
) => {
presentSheet({
context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
noBottomPadding: 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,19 @@ 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;
import RNFetchBlob from "rn-fetch-blob";
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 +65,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 +87,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 +114,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 +127,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}, []);
useEffect(() => {
checkBackups();
setTimeout(() => {
checkBackups();
}, 300);
}, []);
const restore = async (item) => {
@@ -208,7 +214,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
return;
}
} else {
RNFetchBlob = (await import("rn-fetch-blob")).default;
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
@@ -362,10 +367,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View>
<Seperator half />
<FlatList
nestedScrollEnabled
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
!restoring ? (
loading ? (

View File

@@ -25,23 +25,22 @@ import Navigation from "../../../services/navigation";
import { useThemeStore } from "../../../stores/use-theme-store";
import { GROUP, SORT } from "../../../utils/constants";
import { refreshNotesPage } from "../../../utils/events";
import layoutmanager from "../../../utils/layout-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
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)
);
const updateGroupOptions = async (_groupOptions) => {
await db.settings.setGroupOptions(type, _groupOptions);
layoutmanager.withSpringAnimation(600);
setGroupOptions(_groupOptions);
setTimeout(() => {
Navigation.queueRoutesForUpdate(screen);
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
eSendEvent("groupOptionsUpdate");
eSendEvent(refreshNotesPage);
}, 1);
@@ -52,6 +51,9 @@ const Sort = ({ type, screen }) => {
...groupOptions,
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);
};
@@ -100,7 +102,7 @@ const Sort = ({ type, screen }) => {
height={25}
iconPosition="right"
fontSize={SIZE.sm - 1}
type="grayBg"
type="transparent"
buttonType={{
text: colors.accent
}}
@@ -119,7 +121,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 +168,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 +180,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,556 @@
/*
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";
import Heading from "../../ui/typography/heading";
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

@@ -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 { Keyboard, TouchableOpacity, View } from "react-native";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
@@ -34,24 +34,25 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
let toastMessages = [];
export const Toast = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
const [keyboard, setKeyboard] = useState(false);
const [data, setData] = useState({});
const insets = useGlobalSafeAreaInsets();
const hideTimeout = useRef();
const [visible, setVisible] = useState(false);
const toastMessages = useRef([]);
const showToastFunc = useCallback(
const show = useCallback(
async (data) => {
if (!data) return;
if (data.context !== context) return;
if (toastMessages.findIndex((m) => m.message === data.message) >= 0) {
if (
toastMessages.current.findIndex((m) => m.message === data.message) >= 0
) {
return;
}
toastMessages.push(data);
if (toastMessages?.length > 1) return;
toastMessages.current.push(data);
if (toastMessages.current?.length > 1) return;
setData(data);
setVisible(true);
@@ -59,16 +60,16 @@ export const Toast = ({ context = "global" }) => {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hideToastFunc();
hide();
}, data.duration);
},
[context, hideToastFunc]
[context, hide]
);
const showNext = useCallback(
const next = useCallback(
(data) => {
if (!data) {
hideToastFunc();
hide();
return;
}
setData(data);
@@ -76,27 +77,28 @@ export const Toast = ({ context = "global" }) => {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hideToastFunc();
hide();
}, data?.duration);
},
[hideToastFunc]
[hide]
);
const hideToastFunc = useCallback(() => {
const hide = useCallback(() => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
let msg = toastMessages.length > 1 ? toastMessages.shift() : null;
let msg =
toastMessages.current.length > 1 ? toastMessages.current.shift() : null;
if (msg) {
setVisible(false);
showNext(msg);
next(msg);
setTimeout(() => {
setVisible(true);
}, 300);
} else {
setVisible(false);
toastMessages.shift();
toastMessages.current.shift();
setTimeout(() => {
setData({});
if (hideTimeout.current) {
@@ -104,44 +106,22 @@ export const Toast = ({ context = "global" }) => {
}
}, 100);
}
}, [showNext]);
const _onKeyboardShow = () => {
setKeyboard(true);
};
const _onKeyboardHide = () => {
setKeyboard(false);
};
}, [next]);
useEffect(() => {
toastMessages = [];
let sub1 = Keyboard.addListener("keyboardDidShow", _onKeyboardShow);
let sub2 = Keyboard.addListener("keyboardDidHide", _onKeyboardHide);
eSubscribeEvent(eShowToast, showToastFunc);
eSubscribeEvent(eHideToast, hideToastFunc);
eSubscribeEvent(eShowToast, show);
eSubscribeEvent(eHideToast, hide);
return () => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
toastMessages = [];
sub1?.remove();
sub2?.remove();
eUnSubscribeEvent(eShowToast, showToastFunc);
eUnSubscribeEvent(eHideToast, hideToastFunc);
toastMessages.current = [];
eUnSubscribeEvent(eShowToast, show);
eUnSubscribeEvent(eHideToast, hide);
};
}, [hideToastFunc, keyboard, showToastFunc]);
}, [hide, show]);
return (
visible && (
<TouchableOpacity
onPress={() => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
hideToastFunc();
}}
onPress={hide}
activeOpacity={1}
style={{
width: DDS.isTab ? 400 : "100%",
@@ -209,7 +189,7 @@ export const Toast = ({ context = "global" }) => {
color={colors.pri}
size={SIZE.md}
onPress={() => {
hideToastFunc();
hide();
}}
>
{data.heading}
@@ -224,7 +204,7 @@ export const Toast = ({ context = "global" }) => {
paddingRight: 10
}}
onPress={() => {
hideToastFunc();
hide();
}}
>
{data.message}

View File

@@ -40,6 +40,8 @@ import { getElevation } from "../../../utils";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../icon-button";
import Paragraph from "../typography/paragraph";
import phone from "phone";
interface InputProps extends TextInputProps {
fwdRef?: RefObject<TextInput>;
validationType?:
@@ -92,7 +94,7 @@ const Input = ({
button,
onBlurInput,
onPress,
height = 50,
height = 45,
fontSize = SIZE.md,
onFocusInput,
buttons,
@@ -149,7 +151,6 @@ const Input = ({
isError = customValidator && value === customValidator();
break;
case "phonenumber": {
const { default: phone } = await import("phone");
const result = phone(value, {
strictDetection: true,
validateMobilePrefix: true

View File

@@ -65,7 +65,7 @@ export const Notice = ({
flexShrink: 1
}}
selectable={selectable}
size={isSmall ? SIZE.xs + 1 : SIZE.sm}
size={isSmall ? SIZE.xs : SIZE.sm}
>
{text}
</Paragraph>

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

@@ -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);
}
},
@@ -664,7 +623,7 @@ export const useActions = ({ close = () => null, item }) => {
},
{
id: "favorite",
title: !item.favorite ? "Favorite" : "Unfavorite",
title: item.favorite ? "Unfavorite" : "Favorite",
icon: item.favorite ? "star-off" : "star-outline",
func: addToFavorites,
close: false,
@@ -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);
}
},
{
@@ -744,7 +701,7 @@ export const useActions = ({ close = () => null, item }) => {
},
{
id: "read-only",
title: "Read only",
title: "Readonly",
icon: "pencil-lock",
func: toggleReadyOnlyMode,
on: item.readonly
@@ -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();
}
@@ -561,7 +558,7 @@ export const useAppEvents = () => {
let shareExtensionOpened = MMKV.getString("shareExtensionOpened");
if (notesAddedFromIntent) {
if (Platform.OS === "ios") {
await db.init();
await db.initCollections();
await db.notes.init();
}
useNoteStore.getState().setNotes();
@@ -574,8 +571,8 @@ export const useAppEvents = () => {
if (notesAddedFromIntent || shareExtensionOpened) {
let id = useEditorStore.getState().currentEditingNote;
let note = id && db.notes.note(id).data;
eSendEvent("loadingNote", note);
eSendEvent("webview_reset");
setTimeout(() => eSendEvent("loadingNote", note), 1);
MMKV.removeItem("shareExtensionOpened");
}
} catch (e) {

View File

@@ -17,35 +17,46 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState } from "react";
import { useState } from "react";
import { useAttachmentStore } from "../stores/use-attachment-store";
export const useAttachmentProgress = (attachment, encryption) => {
type AttachmentProgress = {
type: string;
value?: number;
percent?: string;
};
export const useAttachmentProgress = (
attachment: any,
encryption?: boolean
) => {
const progress = useAttachmentStore((state) => state.progress);
const [currentProgress, setCurrentProgress] = useState(
const [currentProgress, setCurrentProgress] = useState<
AttachmentProgress | undefined
>(
encryption
? {
type: "encrypt"
}
: null
: undefined
);
useEffect(() => {
let prog = progress[attachment.metadata.hash];
if (prog) {
let type = prog.type;
let loaded = prog.type === "download" ? prog.recieved : prog.sent;
prog = loaded / prog.total;
prog = (prog * 100).toFixed(0);
setCurrentProgress({
value: prog,
percent: prog + "%",
type: type
});
} else {
setCurrentProgress(null);
}
}, [attachment.metadata.hash, progress]);
const attachmentProgress = progress?.[attachment.metadata.hash];
if (attachmentProgress) {
const type = attachmentProgress.type;
const loaded =
attachmentProgress.type === "download"
? attachmentProgress.recieved
: attachmentProgress.sent;
const value = loaded / attachmentProgress.total;
setCurrentProgress({
value: value * 100,
percent: (value * 100).toFixed(0) + "%",
type: type
});
} else {
setCurrentProgress(undefined);
}
return [currentProgress, setCurrentProgress];
};

View File

@@ -1,61 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect } from "react";
import BiometicService from "../services/biometrics";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { db } from "../common/database";
const VaultStatusCache = {
exists: false,
biometryEnrolled: false,
isBiometryAvailable: false
};
export const useVaultStatus = () => {
const [vaultStatus, setVaultStatus] = React.useState(VaultStatusCache);
const checkVaultStatus = useCallback(() => {
db.vault.exists().then(async (exists) => {
let available = await BiometicService.isBiometryAvailable();
let fingerprint = await BiometicService.hasInternetCredentials();
if (
VaultStatusCache.exists === exists &&
VaultStatusCache.biometryEnrolled === fingerprint &&
VaultStatusCache.isBiometryAvailable === available
)
return;
setVaultStatus({
exists: exists,
biometryEnrolled: fingerprint,
isBiometryAvailable: available ? true : false
});
});
}, []);
useEffect(() => {
checkVaultStatus();
eSubscribeEvent("vaultUpdated", () => checkVaultStatus());
return () => {
eUnSubscribeEvent("vaultUpdated", () => checkVaultStatus());
};
}, [checkVaultStatus]);
return vaultStatus;
};

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

@@ -10,10 +10,10 @@
"buffer": "^6.0.3",
"dayjs": "^1.10.4",
"entities": "^3.0.1",
"html-to-text": "8.1.0",
"html-to-text": "9.0.5",
"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

@@ -24,6 +24,7 @@ import React, {
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from "react";
import { Platform, ViewStyle } from "react-native";
@@ -89,7 +90,7 @@ const Editor = React.memo(
noToolbar,
noHeader
});
const renderKey = useRef(`editor-0`);
useImperativeHandle(ref, () => ({
get: () => editor
}));
@@ -107,6 +108,7 @@ const Editor = React.memo(
attachmentType: string;
}) => {
if (groupId !== editor.note.current?.id) return;
editorController.current.markImageLoaded(hash);
if (attachmentType === "webclip") {
editor.commands.updateWebclip({
hash: hash,
@@ -123,6 +125,8 @@ const Editor = React.memo(
);
const onError = useCallback(() => {
renderKey.current =
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
editor.setLoading(true);
setTimeout(() => editor.setLoading(false), 10);
}, [editor]);
@@ -151,6 +155,7 @@ const Editor = React.memo(
testID={notesnook.editor.id}
ref={editor.ref}
onLoad={editor.onLoad}
key={renderKey.current}
onRenderProcessGone={onError}
nestedScrollEnabled
onError={onError}

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

@@ -48,6 +48,7 @@ const fn = (fn: string) => {
const id = randId("fn_");
return {
job: `(async () => {
if (typeof __PLATFORM__ === "undefined") __PLATFORM__ = "${Platform.OS}";
try {
let response = true;
${fn}
@@ -57,7 +58,7 @@ const fn = (fn: string) => {
if (DEV_MODE && typeof logger !== "undefined") logger('error', "webview: ", e.message, e.stack);
}
return true;
})();`,
})();true;`,
id: id
};
};
@@ -105,6 +106,7 @@ typeof globalThis.editorTitle !== "undefined" && editorTitle.current && editorTi
if (editorController.content) editorController.content.current = null;
editorController.onUpdate();
editorController.setTitle(null);
editorController.countWords(0);
typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",saved:""});
`
);
@@ -128,7 +130,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)})
@@ -228,7 +229,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 })})`
);
};
@@ -237,6 +238,10 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));'
);
};
keyboardShown = async (keyboardShown: boolean) => {
return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`);
};
//todo add replace image function
}

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,7 +33,6 @@ 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";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -124,6 +124,7 @@ const file = async (fileOptions) => {
});
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
editorController.current?.commands.insertAttachment({
hash: hash,
filename: file.name,
@@ -147,8 +148,6 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchCamera(
{
includeBase64: true,
@@ -170,8 +169,6 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchImageLibrary(
{
includeBase64: true,
@@ -205,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);
@@ -234,7 +231,7 @@ const handleImageResponse = async (response, options) => {
});
return;
}
const b64 = `data:${image.type};base64, ` + image.base64;
let b64 = `data:${image.type};base64, ` + image.base64;
const uri = decodeURI(image.uri);
const hash = await Sodium.hashFile({
uri: uri,
@@ -243,12 +240,24 @@ const handleImageResponse = async (response, options) => {
let fileName = image.originalFileName || image.fileName;
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
const isPng = /(png)/g.test(image.type);
const isJpeg = /(jpeg|jpg)/g.test(image.type);
if (isPng || isJpeg) {
b64 =
`data:${image.type};base64, ` +
(await compressToBase64(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
isPng ? "PNG" : "JPEG"
));
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
editorController.current?.commands.insertImage({
hash: hash,
type: image.type,
title: fileName,
src: b64,
dataurl: b64,
size: image.fileSize,
filename: fileName
});
@@ -289,7 +298,6 @@ async function attachFile(uri, hash, type, filename, options) {
encryptionInfo,
editorController.current?.note?.id
);
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
return true;
} catch (e) {

View File

@@ -48,6 +48,8 @@ export type Settings = {
keyboardShown?: boolean;
doubleSpacedLines?: boolean;
corsProxy: string;
fontSize: string;
fontFamily: string;
};
export type EditorProps = {

View File

@@ -25,19 +25,21 @@ import { useCallback, useEffect, useRef } from "react";
import {
BackHandler,
InteractionManager,
Keyboard,
KeyboardEventListener,
NativeEventSubscription
} 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";
import { DDS } from "../../../services/device-detection";
import {
ToastEvent,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useEditorStore } from "../../../stores/use-editor-store";
@@ -51,8 +53,7 @@ import {
eOpenFullscreenEditor,
eOpenLoginDialog,
eOpenPremiumDialog,
eOpenPublishNoteDialog,
eOpenTagsDialog
eOpenPublishNoteDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
@@ -135,8 +136,30 @@ export const useEditorEvents = (
const doubleSpacedLines = useSettingStore(
(state) => state.settings?.doubleSpacedLines
);
const defaultFontSize = useSettingStore(
(state) => state.settings.defaultFontSize
);
const defaultFontFamily = useSettingStore(
(state) => state.settings.defaultFontFamily
);
const tools = useDragState((state) => state.data);
const { keyboardShown } = useKeyboard();
useEffect(() => {
const handleKeyboardDidShow: KeyboardEventListener = () => {
editor.commands.keyboardShown(true);
};
const handleKeyboardDidHide: KeyboardEventListener = () => {
editor.commands.keyboardShown(false);
};
const subscriptions = [
Keyboard.addListener("keyboardDidShow", handleKeyboardDidShow),
Keyboard.addListener("keyboardDidHide", handleKeyboardDidHide)
];
return () => {
subscriptions.forEach((subscription) => subscription.remove());
};
}, [editor.commands]);
useEffect(() => {
editor.commands.setSettings({
@@ -147,9 +170,10 @@ export const useEditorEvents = (
tools: tools || getDefaultPresets().default,
noHeader: noHeader,
noToolbar: readonly || editorPropReadonly || noToolbar,
keyboardShown: keyboardShown || false,
doubleSpacedLines: doubleSpacedLines,
corsProxy: corsProxy
corsProxy: corsProxy,
fontSize: defaultFontSize,
fontFamily: defaultFontFamily
});
}, [
fullscreen,
@@ -160,12 +184,13 @@ export const useEditorEvents = (
deviceMode,
tools,
editor.commands,
keyboardShown,
doubleSpacedLines,
editorPropReadonly,
noHeader,
noToolbar,
corsProxy
corsProxy,
defaultFontSize,
defaultFontFamily
]);
const onBackPress = useCallback(async () => {
@@ -189,12 +214,7 @@ export const useEditorEvents = (
setImmediate(() => {
useEditorStore.getState().setCurrentlyEditingNote(null);
setTimeout(() => {
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
}, 500);
});
editorState().currentlyEditing = false;
@@ -273,12 +293,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 +322,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 +349,7 @@ export const useEditorEvents = (
});
return;
}
eSendEvent(eOpenTagsDialog, editor.note.current);
ManageTagsSheet.present(editor.note.current);
break;
case EventTypes.tag:
if (editorMessage.value) {
@@ -337,13 +360,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 +397,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

@@ -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 { EVENTS } from "@notesnook/core/common";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import WebView from "react-native-webview";
import { db } from "../../../common/database";
@@ -29,6 +30,7 @@ import {
openVault
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings";
import { TipManager } from "../../../services/tip-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useNoteStore } from "../../../stores/use-notes-store";
@@ -41,17 +43,15 @@ import { NoteType } from "../../../utils/types";
import Commands from "./commands";
import { Content, EditorState, Note, SavePayload } from "./types";
import {
EditorEvents,
clearAppState,
defaultState,
EditorEvents,
getAppState,
isContentInvalid,
isEditorLoaded,
makeSessionId,
post
} from "./utils";
import { EVENTS } from "@notesnook/core/common";
import SettingsService from "../../../services/settings";
export const useEditor = (
editorId = "",
@@ -76,6 +76,8 @@ export const useEditor = (
const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0);
const lock = useRef(false);
const loadedImages = useRef<{ [name: string]: boolean }>({});
const lockedSessionId = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T) =>
@@ -118,29 +120,9 @@ export const useEditor = (
[editorId]
);
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
overlay(true);
setLoading(true);
}
}, [overlay]);
useEffect(() => {
state.current.saveCount = 0;
async () => {
await commands.setSessionId(sessionIdRef.current);
if (sessionIdRef.current) {
if (!state.current?.ready) return;
await onReady();
}
};
}, [sessionId, loading, commands, onReady]);
useEffect(() => {
if (loading) {
setLoading(false);
}
}, [loading]);
if (loading) {
setLoading(false);
}
const withTimer = useCallback(
(id: string, fn: () => void, duration: number) => {
@@ -151,17 +133,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;
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);
@@ -236,9 +220,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);
});
}
@@ -259,12 +249,7 @@ export const useEditor = (
currentNote.current?.headline?.slice(0, 200) !==
note.headline?.slice(0, 200)
) {
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes"
);
Navigation.queueRoutesForUpdate();
}
}
@@ -290,6 +275,58 @@ export const useEditor = (
}
}, []);
const getMediaToLoad = (previousContent?: string) => {
if (!currentNote.current?.id) return [];
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;
}
}
return media;
};
const markImageLoaded = (hash: string) => {
const attachment = loadedImages.current[hash];
if (typeof attachment === "boolean") {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback((previousContent?: string) => {
if (!currentNote.current?.id) return;
const timerId = "loading-images";
clearTimeout(timers.current[timerId]);
timers.current[timerId] = setTimeout(() => {
if (!currentNote.current?.id) return;
if (currentNote.current?.content?.isPreview) {
db.content?.downloadMedia(
currentNote.current?.id,
currentNote.current.content,
true
);
} else {
const media = getMediaToLoad(previousContent);
if (media.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, media);
}
}
}, 1000);
}, []);
const loadNote = useCallback(
async (
item: Omit<NoteType, "type"> & {
@@ -312,16 +349,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);
@@ -329,29 +376,26 @@ 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();
}
},
[commands, isDefaultEditor, loadContent, overlay, postMessage, reset]
[
commands,
isDefaultEditor,
loadContent,
loadImages,
overlay,
postMessage,
reset
]
);
const loadImages = () => {
if (!currentNote.current?.id) return;
setTimeout(() => {
if (!currentNote.current?.id) return;
if (currentNote.current?.content?.isPreview) {
db.content?.downloadMedia(
currentNote.current?.id,
currentNote.current.content,
true
);
} else {
db.attachments?.downloadMedia(currentNote.current?.id);
}
}, 300);
};
const lockNoteWithVault = useCallback((note: NoteType) => {
eSendEvent(eClearEditor);
openVault({
@@ -378,6 +422,9 @@ export const useEditor = (
return;
lock.current = true;
const previousContent = currentContent.current?.data;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -408,16 +455,20 @@ export const useEditor = (
}
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
}
db.eventManager.subscribe(
EVENTS.syncCompleted,
async () => {
loadImages();
},
true
);
lock.current = false;
if (data.type === "tiptap") {
loadImages(previousContent);
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages(previousContent);
},
true
);
}
},
[commands, postMessage, lockNoteWithVault]
[loadImages, lockNoteWithVault, postMessage, commands]
);
useEffect(() => {
@@ -436,14 +487,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,
@@ -451,22 +504,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;
}
@@ -476,10 +534,10 @@ export const useEditor = (
}
saveNote(params);
},
500
150
);
},
[sessionId, withTimer, onChange, saveNote]
[withTimer, onChange, saveNote]
);
const restoreEditorState = useCallback(async () => {
@@ -489,6 +547,7 @@ export const useEditor = (
state.current.isRestoringState = true;
state.current.currentlyEditing = true;
state.current.movedAway = false;
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1, false);
}
@@ -508,10 +567,6 @@ export const useEditor = (
state.current.isRestoringState = false;
}, [loadNote, overlay]);
useEffect(() => {
isDefaultEditor && restoreEditorState();
}, [isDefaultEditor, restoreEditorState]);
useEffect(() => {
eSubscribeEvent(eOnLoadNote + editorId, loadNote);
return () => {
@@ -519,6 +574,29 @@ export const useEditor = (
};
}, [editorId, loadNote, restoreEditorState, isDefaultEditor]);
const onContentChanged = () => {
lastContentChangeTime.current = Date.now();
};
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
eSendEvent("webview_reset");
} else {
isDefaultEditor && restoreEditorState();
}
}, [isDefaultEditor, restoreEditorState]);
useEffect(() => {
state.current.saveCount = 0;
(async () => {
await commands.setSessionId(sessionIdRef.current);
if (sessionIdRef.current) {
if (!state.current?.ready) return;
await onReady();
}
})();
}, [sessionId, loading, commands, onReady]);
const onLoad = useCallback(async () => {
state.current.ready = true;
onReady();
@@ -542,10 +620,6 @@ export const useEditor = (
loadNote
]);
const onContentChanged = () => {
lastContentChangeTime.current = Date.now();
};
return {
ref: editorRef,
onLoad,
@@ -560,6 +634,8 @@ export const useEditor = (
onReady,
saveContent,
onContentChanged,
editorId: editorId
editorId: editorId,
markImageLoaded,
overlay
};
};

View File

@@ -51,6 +51,7 @@ export const EditorWrapper = ({ width }) => {
if (editorState().movedAway) return;
if (state === "active") {
editorController.current.onReady();
editorController.current.overlay(false);
}
};

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,62 @@ 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;
if (!notebook) return [];
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>

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