Compare commits

...

246 Commits

Author SHA1 Message Date
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
ammarahm-ed
fe8ba8ca90 mobile: show error if mfa info not recieved 2023-02-23 15:49:53 +05:00
ammarahm-ed
8fa4b5017c mobile: hide keyboard on ios on app lock screen 2023-02-23 15:49:53 +05:00
Abdullah Atta
d2f39df3bf web: make tests more reliable 2023-02-23 15:14:49 +05:00
Abdullah Atta
2ada289e74 editor: update patch for @tiptap/extension-link 2023-02-23 13:13:27 +05:00
Abdullah Atta
1edf416461 editor: update patch for @tiptap/core 2023-02-23 13:13:27 +05:00
Abdullah Atta
6fae73b612 editor: increase force update debounce timeout to 200ms 2023-02-23 13:13:27 +05:00
Abdullah Atta
ec898748b1 editor: preserve font family in persisted selection 2023-02-23 13:13:27 +05:00
Abdullah Atta
5be67823ce editor: optimize selection updates in codeblock 2023-02-23 13:13:27 +05:00
Abdullah Atta
fd868aec95 web: debounce selection updates in editor 2023-02-23 13:13:27 +05:00
Abdullah Atta
33bf5e26ff editor: only show table toolbars when table is selected 2023-02-23 13:13:27 +05:00
Abdullah Atta
08363f95e0 editor: update tiptap packages 2023-02-23 13:13:27 +05:00
Abdullah Atta
dc62291fba editor: only update node views if attributes change 2023-02-23 13:13:27 +05:00
Abdullah Atta
43034cf3a3 editor: show gradient overlay on image for better contrast 2023-02-23 13:13:27 +05:00
Abdullah Atta
88f047c312 editor: reduce no. of nested nodes in task list for perf 2023-02-23 13:13:27 +05:00
Abdullah Atta
46a635d4f4 editor: migrate to vanilla js outline list 2023-02-23 13:13:27 +05:00
Ammar Ahmed
882217be63 web: Fix sync settings toggles on/off text (#1995)
* web: fix sync toggles text
2023-02-23 13:11:19 +05:00
Ammar Ahmed
87a2650480 mobile: fix background events (#1994) 2023-02-23 13:05:15 +05:00
ammarahm-ed
c41806bd70 editor: fix build errors 2023-02-23 11:02:10 +05:00
ammarahm-ed
ae7a6ba88c mobile: fix fdroid app version code 2023-02-23 09:50:11 +05:00
Abdullah Atta
df78b31527 web: fix invalid submenu position due to element id conflict (#1977) 2023-02-21 09:41:21 +05:00
Abdullah Atta
4040c62dda editor: focus search input on pressing ctrl+f 2023-02-20 17:37:25 +05:00
Abdullah Atta
34bf9d5b45 web: close publish popup on focus lost 2023-02-20 17:26:09 +05:00
Abdullah Atta
80cd37fe4f web: fix sub menus position 2023-02-20 16:48:08 +05:00
Abdullah Atta
21a7dbad53 web: show no menu title when menu opened on 1 item 2023-02-20 16:45:03 +05:00
Abdullah Atta
c2b010d057 web: fix spacing around input in search view
Closes #1961
2023-02-20 16:38:15 +05:00
Abdullah Atta
82b01851c7 web: fix blank email input on session expiry screen 2023-02-20 16:29:06 +05:00
Muhammad Ali
0a81f0a09a editor: fix vertical spacing between nested lists (#1928)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-02-20 16:03:00 +05:00
Muhammad Ali
e06cbb39b0 editor: make link uneditable in readonly mode (#1968) 2023-02-20 15:25:01 +05:00
Abdullah Atta
b52cc0c590 web: do not allow non-string ETag header in attachments upload 2023-02-20 15:23:53 +05:00
Abdullah Atta
82376c8c47 web: make app loading animation smoother 2023-02-20 15:23:53 +05:00
Abdullah Atta
e0b191b9a0 web: get rid of editor-placeholder (always show editor by default) 2023-02-20 15:23:53 +05:00
Abdullah Atta
6ef6259629 web: improve ui of note unlock screen 2023-02-20 15:23:53 +05:00
Abdullah Atta
52d08f9392 web: fix multi-selection in manage attachments dialog 2023-02-20 15:23:53 +05:00
Abdullah Atta
6812d3552a web: fix svg logo in splashscreen 2023-02-20 15:23:52 +05:00
Abdullah Atta
68ebb132cd global: update packages to ensure we bundle only 1 version of @mdi/js 2023-02-20 15:23:52 +05:00
ammarahm-ed
203aa4824d mobile: do not use process.env 2023-02-20 14:54:44 +05:00
ammarahm-ed
047707dd35 mobile: build notifee from source 2023-02-20 12:35:03 +05:00
ammarahm-ed
7c7645c0c6 ci: fix release workflow 2023-02-15 14:02:14 +05:00
ammarahm-ed
2d4ca15e10 mobile: release v2.4.4 2023-02-15 13:57:07 +05:00
Abdullah Atta
9ca831713c docs: update contributing guidelines 2023-02-15 10:18:35 +05:00
Ammar Ahmed
56008c15c5 mobile: remove recovery code input maxLength (#1922) 2023-02-15 10:18:30 +05:00
Ammar Ahmed
2a2811ec54 mobile: fix cannot read property of title (#1921) 2023-02-15 10:17:55 +05:00
Abdullah Atta
bb0c48f26a web: bump version to 2.4.3 2023-02-14 12:45:17 +05:00
Abdullah Atta
2f815fcec3 web: use word counter from editor 2023-02-14 12:36:59 +05:00
ammarahm-ed
cc446f99ee mobile: use word-counter from editor 2023-02-14 12:36:59 +05:00
ammarahm-ed
9e83e67a0b editor: add word-counter 2023-02-14 12:36:59 +05:00
ammarahm-ed
bf589da7b8 mobile: bump version to 2.4.3 2023-02-14 12:15:15 +05:00
ammarahm-ed
21b278d42e desktop: fix app not quitting when closeToSystemTray
enabled
2023-02-14 12:12:58 +05:00
ammarahm-ed
2cd6a397dc desktop: fix tray icon size on macos 2023-02-14 12:12:26 +05:00
Abdullah Atta
b3aa56736e desktop: make tray icon bigger on linux & macos 2023-02-14 11:58:39 +05:00
Abdullah Atta
e52b72b0c6 desktop: show sys tray icon only if close/minimize to sys tray is on 2023-02-14 11:58:39 +05:00
Abdullah Atta
6fa82bbe2e desktop: show Open in browser menu item when a link is selected 2023-02-14 11:58:39 +05:00
Abdullah Atta
6b3c628e6b desktop: do not show native menu if its empty 2023-02-14 11:58:39 +05:00
Abdullah Atta
39e14d059e desktop: only show Copy menu item if there's a selection 2023-02-14 11:58:39 +05:00
Abdullah Atta
494d6633b5 desktop: make quit from dock menu work again on macOS 2023-02-14 11:58:39 +05:00
Abdullah Atta
690d405103 desktop: exit the app if window is closed when its fullscreen on macOS 2023-02-14 11:58:39 +05:00
Abdullah Atta
6a05048aed desktop: make fullscreen work again on macOS 2023-02-14 11:58:39 +05:00
Abdullah Atta
f75c010ecb desktop: save window state eagerly as soon as its updated 2023-02-14 11:58:39 +05:00
ammarahm-ed
f2c0c34c10 mobile: small ui fixes 2023-02-14 11:51:46 +05:00
ammarahm-ed
512517cc64 mobile: patch react-native-actions-sheet 2023-02-14 11:26:21 +05:00
Abdullah Atta
74f1d3cb74 desktop: fix start minimized on windows 2023-02-14 10:41:19 +05:00
Abdullah Atta
f99cbd011a desktop: fix styling of language selector dialog 2023-02-14 10:41:19 +05:00
ammarahm-ed
18d80cde42 mobile: fix wrong recovery code length 2023-02-14 10:26:17 +05:00
Abdullah Atta
6f2127abb8 desktop: prevent setting privacy mode on linux 2023-02-14 10:00:52 +05:00
GitStart
cb1c023815 web: clicking on Settings doesn't toggle the settings pane on/off (#1882)
Signed-off-by: Gitstart <gitstart@gitstart.com>
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2023-02-14 09:25:22 +05:00
Abdullah Atta
a9a6a00f87 web: fix export html test 2023-02-13 18:13:54 +05:00
Abdullah Atta
060952697a web: bring back title in context menu 2023-02-13 18:13:54 +05:00
Abdullah Atta
a0579278c9 web: fix all e2e tests 2023-02-13 18:13:54 +05:00
ammarahm-ed
678684e24b mobile: update e2e tests for notebook 2023-02-13 18:13:54 +05:00
ammarahm-ed
27e3d18987 mobile: improve move-to sheet ux 2023-02-13 18:13:54 +05:00
Abdullah Atta
7ffe7f1a0c web: make link to notebook much more ux friendly 2023-02-13 18:13:54 +05:00
Abdullah Atta
03c037da97 web: show linked notebooks in note's context menu 2023-02-13 18:13:54 +05:00
Abdullah Atta
b7f035b78e core: simplify total notes counting in notebooks 2023-02-13 18:13:54 +05:00
Abdullah Atta
66fe109eb9 core: add fn to remove all notebooks from a note 2023-02-13 18:13:54 +05:00
Abdullah Atta
f62cd63fbb web: add separators in context menus & reorganize menu items 2023-02-13 18:13:54 +05:00
Abdullah Atta
e1e980cbb2 web: improve ui of context menus to match app ui 2023-02-13 18:13:54 +05:00
Abdullah Atta
ca5543d6ea web: fix separators in context menu 2023-02-13 18:13:54 +05:00
Abdullah Atta
05cec14c9b web: migrate base dialog to typescript 2023-02-13 18:13:54 +05:00
Abdullah Atta
1123fe0383 web: fix 'doNotTrack' is not exported from '../utils/telemetry' error 2023-02-13 18:01:02 +05:00
Abdullah Atta
5855fe34ee web: remove unnecessary import 2023-02-13 17:48:24 +05:00
Abdullah Atta
030e57d0a9 web: remove tracking event when signup is skipped 2023-02-13 17:48:24 +05:00
Abdullah Atta
b6f922eb92 web: make telemetry opt-in by default 2023-02-13 17:48:24 +05:00
ammarahm-ed
2ab756a847 mobile: make telemetry opt-in 2023-02-13 17:48:24 +05:00
Abdullah Atta
3611bba1d4 Merge pull request #1889 from streetwriters/feature/ux
Minor UX improvements
2023-02-13 16:40:18 +05:00
ammarahm-ed
e07f32fb98 mobile: remove some buttons from editor header 2023-02-13 13:12:41 +05:00
ammarahm-ed
5ba065c8f1 mobile: fix share extension not launching on ios 2023-02-13 09:48:44 +05:00
Abdullah Atta
1fb7541d14 config: improve integration with vscode
this adds recommended extensions, a vscode workspace config, workspace
settings & launch configs for mobile & web apps.
2023-02-13 09:18:01 +05:00
Ammar Ahmed
0dc0ba21f9 Merge branch 'master' into feature/ux
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-02-09 16:09:52 +05:00
ammarahm-ed
e34f1f0b45 mobile: redesign properties sheet 2023-02-09 15:34:29 +05:00
ammarahm-ed
604d98f772 mobile: increase raduis of sheet border corners 2023-02-09 15:34:07 +05:00
ammarahm-ed
40eb2d083d mobile: add vertical margins to selection header 2023-02-09 15:33:49 +05:00
ammarahm-ed
3bca1ca795 mobile: remove action strip 2023-02-09 15:33:15 +05:00
Abdullah Atta
ae4afd5154 core: remove dir="ltr" from snapshots 2023-02-09 13:50:03 +05:00
Abdullah Atta
27e732665a web: remove dir="ltr" from html snapshots 2023-02-09 13:50:03 +05:00
Abdullah Atta
6415e89df9 web: allow newlines in item body 2023-02-09 13:47:33 +05:00
Abdullah Atta
4ef8785489 web: allow multi-line text in reminder description 2023-02-09 13:47:33 +05:00
Abdullah Atta
b7af3d92c9 web: set note title as default reminder title 2023-02-09 13:47:33 +05:00
ammarahm-ed
82a80269d3 mobile: prompt user to enable notifications if
notifications are explicitly disabled.
2023-02-09 13:47:33 +05:00
ammarahm-ed
6112eedcc4 mobile: remove no of lines limit from properties 2023-02-09 13:47:33 +05:00
ammarahm-ed
001bdc1dc9 mobile: fix padding of message card 2023-02-09 13:47:33 +05:00
ammarahm-ed
fb2de4245d mobile: fix sizing on indicators on reminder item 2023-02-09 13:47:33 +05:00
ammarahm-ed
9164d8f138 mobile: fix reminder item text wrapping 2023-02-09 13:47:33 +05:00
ammarahm-ed
fbcfbfc12c mobile: reminder improvements
1. Set default title/description
2. Multiline reminder description input
2023-02-09 13:47:33 +05:00
Abdullah Atta
fb8e3a2364 core: update showdown to 3.0.1.alpha.2 2023-02-09 13:24:18 +05:00
Abdullah Atta
375ef1c06d web: improve hover color contrast on dark theme 2023-02-09 11:11:41 +05:00
Abdullah Atta
a064254bcf web: improve item ui when it is focused 2023-02-09 10:08:15 +05:00
Abdullah Atta
95cf31703a web: change bg of all menus to gray-ish 2023-02-09 10:07:44 +05:00
Abdullah Atta
ff995c8d60 web: make editor properties opening/closing faster 2023-02-09 10:06:02 +05:00
Abdullah Atta
1f7b8ce1ed web: refactor note menu items definition 2023-02-09 10:05:43 +05:00
Abdullah Atta
f2608a1a8d web: add readonly menu item in context menu 2023-02-09 10:05:21 +05:00
Abdullah Atta
4e858f3230 web: scroll element into view when navigating using keyboard 2023-02-09 10:02:29 +05:00
Abdullah Atta
ba7daf9766 web: do not animate opening/closing of context menus 2023-02-09 10:01:42 +05:00
ammarahm-ed
d2fcd61541 mobile: reorganize actions in properties 2023-02-08 13:47:33 +05:00
Abdullah Atta
417be8dbf0 web: fetch announcements once on app startup 2023-02-06 13:25:15 +05:00
Abdullah Atta
9b145d15af editor: limit link width in popup to prevent overflow 2023-02-06 13:23:21 +05:00
Abdullah Atta
6b9596d0d1 editor: auto open link popup when link is selected 2023-02-06 13:23:21 +05:00
Abdullah Atta
fa1ad69be7 editor: add support for auto opening more tools popup 2023-02-06 13:23:21 +05:00
Abdullah Atta
1f29273561 editor: only auto close a popup if it is fully opened 2023-02-06 13:23:21 +05:00
Abdullah Atta
4c21d781c4 web: remove unnecessary console.log 2023-02-06 12:27:28 +05:00
Abdullah Atta
746fc2963e web: do not enforce telemetry setting causing it to reset 2023-02-06 10:51:16 +05:00
Muhammad Ali
289ef2a324 web: always give preference to text over files when pasting (#1869)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-02-06 10:41:32 +05:00
Abdullah Atta
91eaf1c71e editor: add rtl support in images 2023-02-06 10:10:30 +05:00
Abdullah Atta
c22bedbfcf editor: use css for propagating text direction to child nodes 2023-02-06 10:10:30 +05:00
Abdullah Atta
ef104e4126 editor: add rtl support in task lists 2023-02-06 10:10:30 +05:00
Abdullah Atta
0d884aa161 editor: add rtl support in tables 2023-02-06 10:10:30 +05:00
Abdullah Atta
192fa8fc24 editor: add rtl support in outline list 2023-02-06 10:10:30 +05:00
Abdullah Atta
0ae24d12d8 editor: add rtl support in embed 2023-02-06 10:10:30 +05:00
Abdullah Atta
6ecf2c8ac3 editor: get text direction from selected or parent node 2023-02-06 10:10:30 +05:00
Abdullah Atta
da7267de43 editor: add text direction attr in more nodes 2023-02-06 10:10:30 +05:00
alihamuh
f134184a43 editor: improve text direction support in lists 2023-02-06 10:10:30 +05:00
ammarahm-ed
650a34ca73 mobile: fix width of items in app lock sheet 2023-02-04 15:05:59 +05:00
Muhammad Ali
009afcd6aa web: add support for CJK characters in word counter (#1840)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-02-04 12:22:58 +05:00
Ammar Ahmed
05f71f39a1 mobile: improved multi-select (#1867)
- Fixed multi-selection header position on iOS
- Added support for select all on mobile
2023-02-04 11:35:26 +05:00
Muhammad Ali
d3a3dc9200 web: confirm before deleting a reminder (#1843)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-02-04 11:30:04 +05:00
Abdullah Atta
9df44b1f3d ci: update desktop workflow 2023-01-30 19:34:29 +05:00
ammarahm-ed
964180a183 mobile: fix glitch on app launch ios 2023-01-30 19:30:08 +05:00
Abdullah Atta
4ef51f4d8f web: bump version to v2.4.2 2023-01-30 19:14:38 +05:00
Abdullah Atta
ee39d940be web: only show spellchecker settings on desktop 2023-01-30 19:11:36 +05:00
Abdullah Atta
db60299c54 desktop: fix resources path for macos 2023-01-30 19:10:24 +05:00
Abdullah Atta
7d3c31efc1 desktop: fix invalid versions of some packages 2023-01-30 18:23:23 +05:00
Abdullah Atta
9e68ccb5a8 desktop: regenerate package-lock.json file 2023-01-30 16:06:50 +05:00
Abdullah Atta
f05fc861d5 web: recovery code input should accept text 2023-01-30 15:10:10 +05:00
Abdullah Atta
085408e5a3 web: revert accidental version increments in package-lock.json 2023-01-30 15:03:26 +05:00
Abdullah Atta
6442a08d6f web: reset watchpack version to 2.4.0 2023-01-30 14:57:21 +05:00
Abdullah Atta
f58239e102 desktop: use custom download url for spell checker dicts 2023-01-30 14:41:19 +05:00
Abdullah Atta
f4952aa770 web: bump version to 2.4.1 2023-01-30 13:44:29 +05:00
Abdullah Atta
5747253400 web: reorder & invert sync toggles in settings 2023-01-30 13:17:33 +05:00
Abdullah Atta
57c45a049f web: week starts from 0, month starts from 1
Previously we were starting both from 0. This caused issues where
selecting 16 would actually select 15.
2023-01-30 13:10:32 +05:00
Abdullah Atta
b3516310b0 web: reset selected days on switching recurring mode 2023-01-30 13:10:32 +05:00
Abdullah Atta
9438c3ed46 web: show more days in one row 2023-01-30 13:10:32 +05:00
Abdullah Atta
35d36d3ec6 web: add tests for editing a recurring reminder 2023-01-30 12:59:24 +05:00
Abdullah Atta
d852b5c9db web: do not reset recurring mode on reminder edit 2023-01-30 12:59:24 +05:00
Abdullah Atta
bbb40a2842 web: use fixed 5px gap in reminder list item footer 2023-01-30 12:01:24 +05:00
Abdullah Atta
e46963512e editor: fix crash when closing search in a different note 2023-01-28 13:51:33 +05:00
Abdullah Atta
1ca5511e78 web: refresh search on note switch 2023-01-28 13:51:19 +05:00
Abdullah Atta
b6e368d730 editor: remove old decorations before applying new ones 2023-01-28 13:51:05 +05:00
Abdullah Atta
94dfee53bc editor: handle backspace correctly inside a multiline list item 2023-01-28 11:55:34 +05:00
Abdullah Atta
8996708164 web: don't create new p tag at editor end if there is selection 2023-01-28 11:21:00 +05:00
Abdullah Atta
b1f79e338f editor: store image size on first load 2023-01-28 11:13:29 +05:00
Abdullah Atta
40387ca082 editor: use cors when downloading pasted images 2023-01-28 11:13:29 +05:00
Abdullah Atta
b02941d5aa editor: don't show download button for dataurl images 2023-01-28 11:13:29 +05:00
Abdullah Atta
fad31fbeb3 desktop: make tray icon bigger 2023-01-28 09:32:57 +05:00
Abdullah Atta
ca08fc0e9d desktop: do not set icons in dock menu 2023-01-28 09:32:57 +05:00
Abdullah Atta
7df64837fe desktop: use special icon for tray 2023-01-28 09:32:57 +05:00
Abdullah Atta
fa70df366c desktop: migrate theme config to typescript 2023-01-28 09:32:57 +05:00
Abdullah Atta
70d4e99dc1 desktop: style tray icons based on actual system theme 2023-01-28 09:32:57 +05:00
Abdullah Atta
49272212bc desktop: don't emit event when user changes theme 2023-01-28 09:32:57 +05:00
ammarahm-ed
736b2a17e9 desktop: fix desktop integration on mac os 2023-01-28 09:32:57 +05:00
Abdullah Atta
ade420cf87 config: add apps/web/desktop to lerna workspaces 2023-01-28 09:32:57 +05:00
Abdullah Atta
96ed46bd4a web: update package-lock.json 2023-01-28 09:32:57 +05:00
Abdullah Atta
e334698e3b desktop: add dock menu on macos 2023-01-28 09:32:57 +05:00
Abdullah Atta
82d97f019d desktop: patch electron-builder to support desktop actions on linux 2023-01-28 09:32:57 +05:00
Abdullah Atta
fb56206537 desktop: fix resources directory path in release build 2023-01-28 09:32:57 +05:00
Abdullah Atta
22fff296b5 desktop: resize app icon in tray menu to 16px 2023-01-28 09:32:57 +05:00
Abdullah Atta
954cb1da0d desktop: iconIndex is required for icons in jumplist 2023-01-28 09:32:57 +05:00
Abdullah Atta
8c25de2aaf desktop: extract png icon from ico file 2023-01-28 09:32:57 +05:00
Abdullah Atta
b362dca795 desktop: use ico icons on windows 2023-01-28 09:32:57 +05:00
Abdullah Atta
45c6daabe7 desktop: add icons for tray items & jumplist 2023-01-28 09:32:57 +05:00
Abdullah Atta
43c5c67bfa desktop: refactor to clean up desktop integration logic 2023-01-28 09:32:57 +05:00
Abdullah Atta
dde276f77e desktop: migrate window state to typescript 2023-01-28 09:32:57 +05:00
Abdullah Atta
60ba6ba6cf web: update package-lock.json 2023-01-28 09:32:57 +05:00
Abdullah Atta
f7a0938c38 desktop: improve integration with operating system 2023-01-28 09:32:57 +05:00
Abdullah Atta
4805e54ef3 desktop: global.win -> globalThis.window 2023-01-24 16:58:18 +05:00
Abdullah Atta
a67599ce83 desktop: add setting to choose spell checker languages 2023-01-24 16:58:18 +05:00
Abdullah Atta
cd9867273f desktop: add setting to toggle the spell checker 2023-01-24 16:58:18 +05:00
Abdullah Atta
a0898d9223 desktop: handle case when property is a boolean false when reading config 2023-01-24 16:58:18 +05:00
Abdullah Atta
11514051b6 desktop: refactor global.win -> globalThis.window 2023-01-24 16:58:18 +05:00
ammarahm-ed
0a1059e0a4 ci: add workflow for internal testing on android 2023-01-24 14:39:50 +05:00
ammarahm-ed
95ee494612 mobile: add release notes 2023-01-23 12:44:54 +05:00
ammarahm-ed
5c8c6f83b9 mobile: add logs when user gets logged out 2023-01-23 12:38:06 +05:00
ammarahm-ed
f1a7cd63a8 mobile: bump version to 2.4.2 2023-01-23 12:32:01 +05:00
ammarahm-ed
8b524319df mobile: disable background sync 2023-01-23 12:27:02 +05:00
Abdullah Atta
06aff69adb desktop: hide window thumbnail on Windows in privacy mode 2023-01-19 10:58:34 +05:00
Abdullah Atta
cc39fac590 desktop: use native electron notifications on desktop 2023-01-19 10:58:12 +05:00
Abdullah Atta
8385bd41ea core: do not connect sse or run sync if user is logged out 2023-01-19 10:57:27 +05:00
Abdullah Atta
77a3b771c0 desktop: auto hide menu bar (shows when Alt is pressed) 2023-01-19 10:57:27 +05:00
ammarahm-ed
7f69c5b268 mobile: reset badge count when entering from bg to fg 2023-01-18 10:06:28 +05:00
ammarahm-ed
215a692139 misc: fix changelog 2023-01-17 18:39:47 +05:00
Abdullah Atta
f1b986cef9 desktop: show app name on notifications in windows 2023-01-17 17:39:05 +05:00
ammarahm-ed
da0f86c006 mobile: release 2.4.1 2023-01-17 17:23:53 +05:00
ammarahm-ed
0a4e84b376 mobile: add background fetch ios 2023-01-17 17:12:08 +05:00
ammarahm-ed
f1f8a35ea5 mobile: add background sync 2023-01-17 17:12:08 +05:00
Ammar Ahmed
1ffb6b3c57 mobile: fix incorrect note loaded when (#1704)
note is very large
2023-01-17 17:08:59 +05:00
332 changed files with 49363 additions and 37097 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

@@ -0,0 +1,64 @@
name: Publish @notesnook/android-internal
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@master
with:
node-version: '16'
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: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: '11'
architecture: 'x64'
- name: Install node modules
run: |
npm ci
- name: Make Gradlew Executable
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Build unsigned app bundle
run: yarn release:android:bundle
- name: Sign app bundle for Playstore release
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFile: ${{steps.sign_app.outputs.signedReleaseFile}}
track: internal
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/

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
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

@@ -127,7 +127,9 @@ jobs:
path: ./apps/web/desktop/build
- name: Install packages
run: npm i
run: |
npm i
npm i dmg-license
working-directory: ./apps/web/desktop
- name: Install provisioning profile

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

@@ -0,0 +1,54 @@
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: 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

11
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-playwright.playwright",
"firefox-devtools.vscode-firefox-debug",
"msjsdiag.vscode-react-native"
]
}

72
.vscode/notesnook.code-workspace vendored Normal file
View File

@@ -0,0 +1,72 @@
{
"folders": [
{
"name": "✨ notesnook",
"path": ".."
},
{
"name": "🚀 @notesnook/mobile",
"path": "../apps/mobile"
},
{
"name": "🚀 @notesnook/web",
"path": "../apps/web"
},
{
"name": "🚀 @notesnook/desktop",
"path": "../apps/web/desktop"
},
{
"name": "🚀 @notesnook/web-clipper",
"path": "../extensions/web-clipper"
},
{
"name": "📦 @notesnook/clipper",
"path": "../packages/clipper"
},
{
"name": "📦 @notesnook/core",
"path": "../packages/core"
},
{
"name": "📦 @notesnook/crypto",
"path": "../packages/crypto"
},
{
"name": "📦 @notesnook/crypto-worker",
"path": "../packages/crypto-worker"
},
{
"name": "📦 @notesnook/editor",
"path": "../packages/editor"
},
{
"name": "📦 @notesnook/editor-mobile",
"path": "../packages/editor-mobile"
},
{
"name": "📦 @notesnook/logger",
"path": "../packages/logger"
},
{
"name": "📦 @notesnook/streamable-fs",
"path": "../packages/streamable-fs"
},
{
"name": "📦 @notesnook/theme",
"path": "../packages/theme"
}
],
"settings": {
"typescript.tsdk": "node_modules/typescript/lib",
"javascript.format.enable": true,
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "modificationsIfAvailable",
"eslint.packageManager": "npm",
"eslint.run": "onSave",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.execArgv": ["--cache"]
}
}

View File

@@ -1,3 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -28,12 +28,6 @@ If you've thought of a way that Notesnook could be better, we want to hear about
Before you open a new feature request, please make sure it's not a duplicate. **Duplicate feature requests & bug reports are closed immediately.**
### Improving documentation
All app related documentation can be found in the `docs/` directory. All the files in this directory are simple markdown files which you can edit either directly on GitHub, or in your favorite text editor.
If you find a mistake in the documentation or would like to contribute something new to the documentation, feel free to open a pull request; creating an issue for this is not required.
### 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.
@@ -91,7 +85,11 @@ The DCO is a declaration attached to every contribution made by every developer
Signed-off-by: Jane Smith <jane.smith@email.com>
```
You may type this line on your own when writing your commit messages. However, if your [user.name](http://user.name) and [user.email](http://user.email) are set in your git configs, you can use `-s` or `--signoff` to add the `Signed-off-by` line to the end of the commit message.
On VSCode, it looks like this:
![](resources/vscode-signed-off-commit.png)
You may type this line on your own when writing your commit messages. However, if your [user.name](http://user.name) and [user.email](http://user.email) are set in your git configs, you can use `-s` or `--signoff` to add the `Signed-off-by` line to the end of the commit message automatically.
#### Include `<scope>:` in your commit message

View File

@@ -25,4 +25,4 @@
"request": "attach"
}
]
}
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -16,7 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "react-native-gesture-handler";
import React, { useEffect } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
@@ -32,7 +32,6 @@ import { useUserStore } from "./stores/use-user-store";
import { View } from "react-native";
import { useState } from "react";
import NetInfo from "@react-native-community/netinfo";
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
reachabilityTest: (response) => {
@@ -51,6 +50,7 @@ const App = () => {
if (appLockMode && appLockMode !== "none") {
useUserStore.getState().setVerifyUser(true);
}
//BackgroundSync.start();
setInit(true);
setTimeout(async () => {
SettingsService.onFirstLaunch();

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

@@ -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);

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

@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef, useState } from "react";
import { Dimensions, View } from "react-native";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import umami from "../../common/analytics";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { ToastEvent } from "../../services/event-manager";
@@ -31,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";
@@ -79,7 +77,6 @@ export const Signup = ({ changeMode, trial }) => {
clearMessage();
setEmailVerifyMessage();
hideAuth();
umami.pageView("/account-created", "/welcome/signup");
await sleep(300);
if (trial) {
PremiumService.sheet(null, null, true);
@@ -99,9 +96,6 @@ export const Signup = ({ changeMode, trial }) => {
return (
<>
{loading ? (
<BaseDialog transparent={true} visible={true} animation="fade" />
) : null}
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
@@ -150,6 +144,7 @@ export const Signup = ({ changeMode, trial }) => {
textAlign: "center"
}}
onPress={() => {
if (loading) return;
changeMode(0);
}}
size={SIZE.md}
@@ -237,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

@@ -54,7 +54,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
app: "Enter the 6 digit code from your authenticator app to continue logging in",
sms: "Enter the 6 digit code sent to your phone number to continue logging in",
email: "Enter the 6 digit code sent to your email to continue logging in",
recoveryCode: "Enter the 8 digit recovery code to continue logging in"
recoveryCode: "Enter the recovery code to continue logging in"
};
const secondaryMethodsText = {
@@ -65,9 +65,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
};
const onNext = async () => {
const length = currentMethod.method === "recoveryCode" ? 8 : 6;
if (!code.current || code.current.length !== length) return;
if (!code.current || code.current.length < 6) return;
setLoading(true);
inputRef.current?.blur();
await onMfaLogin(
@@ -202,22 +200,26 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
<>
<Input
placeholder={
currentMethod.method === "recoveryCode" ? "xxxxxxxx" : "xxxxxx"
currentMethod.method === "recoveryCode"
? "xxxxx-xxxxx"
: "xxxxxx"
}
testID={"input.totp"}
maxLength={currentMethod.method === "recoveryCode" ? 8 : 6}
maxLength={
currentMethod.method === "recoveryCode" ? undefined : 6
}
fwdRef={inputRef}
textAlign="center"
onChangeText={(value) => {
code.current = value;
onNext();
//onNext();
}}
inputStyle={{
fontSize: SIZE.lg,
height: 60,
textAlign: "center",
letterSpacing: 10,
width: null
width: 250
}}
keyboardType={
currentMethod.method === "recoveryCode" ? "default" : "numeric"
@@ -300,7 +302,9 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
TwoFactorVerification.present = (onMfaLogin, data, context) => {
presentSheet({
component: <TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />,
component: () => (
<TwoFactorVerification onMfaLogin={onMfaLogin} mfaInfo={data} />
),
context: context || "two_factor_verify",
onClose: () => {
onMfaLogin();

View File

@@ -75,6 +75,7 @@ export const useLogin = (onFinishLogin) => {
mfa.code,
mfa.method
);
if (success) {
setStep(LoginSteps.passwordAuth);
setLoading(false);
@@ -93,6 +94,8 @@ export const useLogin = (onFinishLogin) => {
}
}
}, mfaInfo);
} else {
finishWithError(new Error("Unable to send 2FA code"));
}
break;
}

View File

@@ -18,7 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { KeyboardAvoidingView, Platform, SafeAreaView } from "react-native";
import { KeyboardAvoidingView, Platform, View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { useSettingStore } from "../../stores/use-setting-store";
import { Header } from "../header";
@@ -28,6 +29,7 @@ export const Container = ({ children }) => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const insets = useGlobalSafeAreaInsets();
return (
<KeyboardAvoidingView
behavior="padding"
@@ -36,10 +38,12 @@ export const Container = ({ children }) => {
flex: 1
}}
>
<SafeAreaView
<View
style={{
flex: 1,
overflow: "hidden"
overflow: "hidden",
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
}}
>
{!introCompleted ? null : (
@@ -50,7 +54,7 @@ export const Container = ({ children }) => {
)}
{children}
</SafeAreaView>
</View>
</KeyboardAvoidingView>
);
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Text, View } from "react-native";
import { Text, View, ViewStyle } from "react-native";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
@@ -41,6 +41,7 @@ type DialogHeaderProps = {
padding?: number;
centered?: boolean;
titlePart?: string;
style?: ViewStyle;
};
const DialogHeader = ({
@@ -50,7 +51,8 @@ const DialogHeader = ({
paragraphColor,
padding,
centered,
titlePart
titlePart,
style
}: DialogHeaderProps) => {
const colors = useThemeStore((state) => state.colors);
@@ -62,7 +64,8 @@ const DialogHeader = ({
alignItems: "center",
justifyContent: "space-between",
minHeight: 50,
paddingHorizontal: padding
paddingHorizontal: padding,
...style
}}
>
<View

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect } from "react";
import React from "react";
import {
Dimensions,
Image,
@@ -26,7 +26,6 @@ import {
View
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import umami from "../../common/analytics";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import { DDS } from "../../services/device-detection";
@@ -50,9 +49,6 @@ export const SVG_Z =
const Intro = ({ navigation }) => {
const colors = useThemeStore((state) => state.colors);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const isTelemetryEnabled = useSettingStore(
(state) => state.settings.telemetry
);
@@ -64,12 +60,6 @@ const Intro = ({ navigation }) => {
}
});
useEffect(() => {
if (!introCompleted) {
umami.pageView("/welcome", "", []);
}
}, [introCompleted]);
return (
<View
testID="notesnook.splashscreen"
@@ -171,8 +161,6 @@ const Intro = ({ navigation }) => {
navigation.navigate("AppLock", {
welcome: true
});
umami.pageView("/home", "/welcome");
}}
style={{
paddingHorizontal: 24,

File diff suppressed because one or more lines are too long

View File

@@ -25,10 +25,7 @@ import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import {
eSendEvent,
presentSheet
} from "../../services/event-manager";
import { eSendEvent, presentSheet } from "../../services/event-manager";
import { setRateAppMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
@@ -55,6 +52,8 @@ 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() {
@@ -133,6 +132,7 @@ const Launcher = React.memo(
eSendEvent("session_expired");
return;
}
notifee.setBadgeCount(0);
await useMessageStore.getState().setAnnouncement();
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;

View File

@@ -56,7 +56,6 @@ const ReminderItem = React.memo(
>
<View
style={{
flexWrap: "wrap",
flexShrink: 1,
opacity: item.disabled ? 0.5 : 1
}}
@@ -94,13 +93,13 @@ const ReminderItem = React.memo(
backgroundColor: colors.nav,
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 5,
paddingVertical: 3,
paddingHorizontal: 6,
alignItems: "center",
marginTop: 5,
justifyContent: "flex-start",
alignSelf: "flex-start",
marginRight: 10
marginRight: 10,
height: 30
}}
>
<Icon
@@ -123,13 +122,13 @@ const ReminderItem = React.memo(
backgroundColor: colors.nav,
borderRadius: 5,
flexDirection: "row",
paddingHorizontal: 5,
paddingVertical: 3,
paddingHorizontal: 6,
alignItems: "center",
marginTop: 5,
justifyContent: "flex-start",
alignSelf: "flex-start",
marginRight: 10
marginRight: 10,
height: 30
}}
>
<Icon name="reload" size={SIZE.md} color={colors.accent} />

View File

@@ -1,326 +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 Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Animated, { SlideInUp, SlideOutDown } from "react-native-reanimated";
import { db } from "../../../common/database";
import { openVault, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { dWidth, getElevation, toTXT } from "../../../utils";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
export const ActionStrip = ({ note, setActionStrip }) => {
const colors = useThemeStore((state) => state.colors);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
const setSelectionMode = useSelectionStore((state) => state.setSelectionMode);
const [isPinnedToMenu, setIsPinnedToMenu] = useState(false);
const [width, setWidth] = useState(dWidth - 16);
useEffect(() => {
if (note.type === "note") return;
setIsPinnedToMenu(db.shortcuts.exists(note.id));
}, [note.id, note.type]);
const updateNotes = () => {
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
};
const actions = [
{
title: "Pin " + note.type,
icon: note.pinned ? "pin-off" : "pin",
visible: note.type === "note" || note.type === "notebook",
onPress: async () => {
if (!note.id) return;
if (note.type === "note") {
await db.notes.note(note.id).pin();
} else {
await db.notebooks.notebook(note.id).pin();
setNotebooks();
}
updateNotes();
setActionStrip(false);
}
},
{
title: "Add to favorites",
icon: note.favorite ? "star-off" : "star",
onPress: async () => {
if (!note.id) return;
if (note.type === "note") {
await db.notes.note(note.id).favorite();
} else {
await db.notebooks.notebook(note.id).favorite();
}
updateNotes();
setActionStrip(false);
},
visible: note.type === "note",
color: !note.favorite ? "orange" : null
},
{
title: isPinnedToMenu
? "Remove Shortcut from Menu"
: "Add Shortcut to Menu",
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
onPress: async () => {
try {
if (isPinnedToMenu) {
await db.shortcuts.remove(note.id);
ToastEvent.show({
heading: "Shortcut removed from menu",
type: "success"
});
} else {
if (note.type === "topic") {
await db.shortcuts.add({
item: {
type: "topic",
id: note.id,
notebookId: note.notebookId
}
});
} else {
await db.shortcuts.add({
item: {
type: "notebook",
id: note.id
}
});
}
ToastEvent.show({
heading: "Shortcut added to menu",
type: "success"
});
}
setIsPinnedToMenu(db.shortcuts.exists(note.id));
setMenuPins();
setActionStrip(false);
} catch (e) {
console.error(e);
}
},
visible: note.type !== "note" && note.type !== "reminder"
},
{
title: "Copy Note",
icon: "content-copy",
visible: note.type === "note",
onPress: async () => {
if (note.locked) {
openVault({
copyNote: true,
novault: true,
locked: true,
item: note,
title: "Copy note",
description: "Unlock note to copy to clipboard."
});
} else {
let text = await toTXT(note);
text = `${note.title}\n \n ${text}`;
Clipboard.setString(text);
ToastEvent.show({
heading: "Note copied to clipboard",
type: "success"
});
}
setActionStrip(false);
}
},
{
title: "Restore " + note.itemType,
icon: "delete-restore",
onPress: async () => {
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Trash",
"Notebooks"
);
ToastEvent.show({
heading:
note.type === "note"
? "Note restored from trash"
: "Notebook restored from trash",
type: "success"
});
setActionStrip(false);
},
visible: note.type === "trash"
},
{
title: "Delete" + note.itemType,
icon: "delete",
visible: note.type === "trash",
onPress: () => {
presentDialog({
title: "Permanent delete",
paragraph: `Are you sure you want to delete this ${note.itemType} permanantly from trash?`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
await db.trash.delete(note.id);
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
},
positiveType: "errorShade"
});
setActionStrip(false);
}
},
{
title: "Delete" + note.type,
icon: "delete",
visible: note.type !== "trash",
onPress: async () => {
if (note.type === "reminder") {
presentDialog({
title: `Delete ${note.type}`,
paragraph: "This reminder will be removed",
positivePress: async () => {
const routes = [];
await db.reminders.remove(note.id);
routes.push("Reminders");
Navigation.queueRoutesForUpdate(...routes);
useRelationStore.getState().update();
},
positiveText: "Delete",
positiveType: "errorShade"
});
return;
}
try {
await deleteItems(note);
} catch (e) {
console.error(e);
}
setActionStrip(false);
}
},
{
title: "Close",
icon: "close",
onPress: () => setActionStrip(false),
color: colors.light,
bg: colors.red,
visible: true
}
];
return (
<Animated.View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
entering={SlideInUp.springify().mass(0.4)}
exiting={SlideOutDown}
style={{
position: "absolute",
zIndex: 999,
width: "102%",
height: "100%",
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center"
}}
>
<Button
type="accent"
title="Select"
icon="check"
tooltipText="Select Item"
onPress={() => {
if (!selectionMode) {
setSelectionMode(true);
}
setSelectedItem(note);
setActionStrip(false);
}}
style={{
borderRadius: 100,
paddingHorizontal: 12,
...getElevation(5)
}}
height={30}
/>
{actions.map((item) =>
item.visible ? (
<View
key={item.icon}
style={{
width: width / 1.4 / actions.length,
height: width / 1.4 / actions.length,
backgroundColor: item.bg || colors.nav,
borderRadius: 100,
justifyContent: "center",
alignItems: "center",
...getElevation(5),
marginLeft: 15
}}
>
<IconButton
color={item.color || colors.heading}
onPress={item.onPress}
tooltipText={item.title}
top={60}
bottom={60}
name={item.icon}
size={width / 2.8 / actions.length}
/>
</View>
) : null
)}
</Animated.View>
);
};

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, { useEffect, useState } from "react";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import React, { useRef } from "react";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { history } from "../../../utils";
import { PressableButton } from "../../ui/pressable";
import { ActionStrip } from "./action-strip";
import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
import { useRef } from "react";
const SelectionWrapper = ({
children,
@@ -42,7 +36,6 @@ const SelectionWrapper = ({
}) => {
const itemId = useRef(item.id);
const colors = useThemeStore((state) => state.colors);
const [actionStrip, setActionStrip] = useState(false);
const notebooksListMode = useSettingStore(
(state) => state.settings.notebooksListMode
);
@@ -56,35 +49,18 @@ const SelectionWrapper = ({
if (item.id !== itemId.current) {
itemId.current = item.id;
if (actionStrip) {
setActionStrip(false);
}
}
const _onLongPress = () => {
if (history.selectedItemsList.length > 0) return;
setActionStrip(!actionStrip);
if (!useSelectionStore.getState().selectionMode) {
useSelectionStore.getState().setSelectionMode(true);
}
useSelectionStore.getState().setSelectedItem(item);
};
const _onPress = async () => {
if (actionStrip) {
setActionStrip(false);
return;
}
await onPress();
};
const closeStrip = () => {
setActionStrip(false);
};
useEffect(() => {
eSubscribeEvent("navigate", closeStrip);
return () => {
eUnSubscribeEvent("navigate", closeStrip);
};
}, []);
return (
<PressableButton
customColor={isSheet ? colors.transGray : "transparent"}
@@ -111,15 +87,10 @@ const SelectionWrapper = ({
) : null}
<SelectionIcon
compactMode={compactMode}
setActionStrip={setActionStrip}
item={item}
onLongPress={onLongPress}
/>
{children}
{actionStrip ? (
<ActionStrip note={item} setActionStrip={setActionStrip} />
) : null}
</PressableButton>
);
};

View File

@@ -24,7 +24,7 @@ import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
export const SelectionIcon = ({ setActionStrip, item }) => {
export const SelectionIcon = ({ item }) => {
const colors = useThemeStore((state) => state.colors);
const selectionMode = useSelectionStore((state) => state.selectionMode);
@@ -35,7 +35,6 @@ export const SelectionIcon = ({ setActionStrip, item }) => {
useEffect(() => {
if (selectionMode) {
setActionStrip(false);
let exists = selectedItemsList.filter(
(o) => o.dateCreated === item.dateCreated
);
@@ -50,16 +49,9 @@ export const SelectionIcon = ({ setActionStrip, item }) => {
}
}
}
}, [
selectedItemsList,
item.id,
selectionMode,
setActionStrip,
item.dateCreated,
selected
]);
}, [selectedItemsList, item.id, selectionMode, item.dateCreated, selected]);
return selectionMode && selected ? (
return selectionMode ? (
<View
style={{
width: 40,
@@ -73,11 +65,13 @@ export const SelectionIcon = ({ setActionStrip, item }) => {
}}
pointerEvents="none"
>
<Icon
size={SIZE.xl}
color={selected ? colors.accent : colors.icon}
name="check"
/>
{selected ? (
<Icon
size={SIZE.xl}
color={selected ? colors.accent : colors.icon}
name={"check"}
/>
) : null}
</View>
) : null;
};

View File

@@ -53,45 +53,52 @@ export const Card = ({ color, warning }) => {
>
<View
style={{
width: 40,
backgroundColor:
messageBoardState.type === "error"
? hexToRGBA(colors.red, 0.15)
: hexToRGBA(color, 0.15),
height: 40,
borderRadius: 100,
alignItems: "center",
justifyContent: "center"
flexDirection: "row",
alignItems: "center"
}}
>
<Icon
size={SIZE.lg}
color={
messageBoardState.type === "error" ? colors.errorText : color
}
name={messageBoardState.icon}
/>
</View>
<View
style={{
marginLeft: 10,
flexShrink: 1,
marginRight: 10
}}
>
<Paragraph color={colors.icon} size={SIZE.xs}>
{messageBoardState.message}
</Paragraph>
<Paragraph
<View
style={{
flexWrap: "wrap",
flexShrink: 1
width: 40,
backgroundColor:
messageBoardState.type === "error"
? hexToRGBA(colors.red, 0.15)
: hexToRGBA(color, 0.15),
height: 40,
borderRadius: 100,
alignItems: "center",
justifyContent: "center"
}}
color={colors.heading}
>
{messageBoardState.actionText}
</Paragraph>
<Icon
size={SIZE.lg}
color={
messageBoardState.type === "error" ? colors.errorText : color
}
name={messageBoardState.icon}
/>
</View>
<View
style={{
marginLeft: 10,
flexShrink: 1,
marginRight: 10
}}
>
<Paragraph color={colors.icon} size={SIZE.xs}>
{messageBoardState.message}
</Paragraph>
<Paragraph
style={{
flexWrap: "wrap",
flexShrink: 1
}}
color={colors.heading}
>
{messageBoardState.actionText}
</Paragraph>
</View>
</View>
<View

View File

@@ -18,9 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { ActivityIndicator, Platform, ScrollView, View } from "react-native";
import { LAUNCH_ROCKET } from "../../assets/images/assets";
import umami from "../../common/analytics";
import { db } from "../../common/database";
import { usePricing } from "../../hooks/use-pricing";
import { DDS } from "../../services/device-detection";
@@ -48,7 +47,6 @@ import { Walkthrough } from "../walkthroughs";
import { features } from "./features";
import { Group } from "./group";
import { PricingPlans } from "./pricing-plans";
import { Platform } from "react-native";
export const Component = ({ close, promo }) => {
const colors = useThemeStore((state) => state.colors);
@@ -60,7 +58,6 @@ export const Component = ({ close, promo }) => {
const onPress = async () => {
if (user) {
umami.pageView("/pro-plans", "/pro-screen");
presentSheet({
context: "pricing_plans",
component: (
@@ -69,7 +66,6 @@ export const Component = ({ close, promo }) => {
});
} else {
close();
umami.pageView("/signup", "/pro-screen");
setTimeout(() => {
eSendEvent(eOpenLoginDialog, AuthMode.trialSignup);
}, 400);

View File

@@ -17,10 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Platform, Text, View } from "react-native";
import * as RNIap from "react-native-iap";
import umami from "../../common/analytics";
import { db } from "../../common/database";
import { usePricing } from "../../hooks/use-pricing";
import {
@@ -48,7 +47,6 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import { PricingItem } from "./pricing-item";
import { useCallback } from "react";
const promoCyclesMonthly = {
1: "first month",
@@ -141,10 +139,6 @@ export const PricingPlans = ({
setBuying(false);
return;
}
umami.pageView(
"/iap-native",
`${compact ? "pro-sheet" : "pro-screen"}/pro-plans`
);
await RNIap.requestSubscription(
product?.productId,
false,

View File

@@ -22,12 +22,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 { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { dWidth } from "../../utils";
import { COLORS_NOTE } from "../../utils/color-scheme";
import { refreshNotesPage } from "../../utils/events";
import { SIZE } from "../../utils/size";
@@ -35,9 +33,7 @@ import { PressableButton } from "../ui/pressable";
export const ColorTags = ({ item }) => {
const [note, setNote] = useState(item);
const setColorNotes = useMenuStore((state) => state.setColorNotes);
const dimensions = useSettingStore((state) => state.dimensions);
let width = dimensions.width > 600 ? 600 : 500;
const isTablet = useSettingStore((state) => state.deviceMode) !== "mobile";
const changeColor = async (color) => {
if (note.color === color.name) {
await db.notes.note(note.id).uncolor();
@@ -71,11 +67,12 @@ export const ColorTags = ({ item }) => {
key={color.value}
onPress={() => changeColor(color)}
customStyle={{
width: DDS.isTab ? width / 10 : dWidth / 9,
height: DDS.isTab ? width / 10 : dWidth / 9,
width: 30,
height: 30,
borderRadius: 100,
justifyContent: "center",
alignItems: "center"
alignItems: "center",
marginRight: isTablet ? 10 : undefined
}}
>
{note.color?.toLowerCase() === color.name ? (
@@ -90,12 +87,10 @@ export const ColorTags = ({ item }) => {
style={{
flexDirection: "row",
flexWrap: "wrap",
flexGrow: isTablet ? undefined : 1,
paddingHorizontal: 12,
width: "100%",
marginVertical: 10,
marginTop: 20,
alignItems: "center",
justifyContent: "space-between"
justifyContent: isTablet ? "center" : "space-between"
}}
>
{Object.keys(COLORS_NOTE).map(_renderColor)}

View File

@@ -43,36 +43,41 @@ export const DateMeta = ({ item }) => {
}
};
const renderItem = (key) =>
key.startsWith("date") && key !== "date" ? (
<View
key={key}
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 3
}}
>
<Paragraph size={SIZE.xs} color={colors.icon}>
{getNameFromKey(key)}
</Paragraph>
<Paragraph size={SIZE.xs} color={colors.icon}>
{timeConverter(item[key])}
</Paragraph>
</View>
) : null;
function getDateMeta() {
let keys = Object.keys(item);
if (keys.includes("dateEdited"))
keys.splice(
keys.findIndex((k) => k === "dateModified"),
1
);
return keys.filter((key) => key.startsWith("date") && key !== "date");
}
const renderItem = (key) => (
<View
key={key}
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 3
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
{getNameFromKey(key)}
</Paragraph>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
{timeConverter(item[key])}
</Paragraph>
</View>
);
return (
<View
style={{
paddingVertical: 5,
marginTop: 5,
borderTopWidth: 1,
borderTopColor: colors.nav,
paddingHorizontal: 12
}}
>
{Object.keys(item).map(renderItem)}
{getDateMeta().map(renderItem)}
</View>
);
};

View File

@@ -27,20 +27,32 @@ import SearchService from "../../services/search";
import { useThemeStore } from "../../stores/use-theme-store";
import { COLORS_NOTE } from "../../utils/color-scheme";
import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider";
import { ReminderTime } from "../ui/reminder-time";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { ColorTags } from "./color-tags";
import { DateMeta } from "./date-meta";
import { DevMode } from "./dev-mode";
import { Items } from "./items";
import Notebooks from "./notebooks";
import { Synced } from "./synced";
import { Tags } from "./tags";
import { Topics } from "./topics";
import {
getUpcomingReminder
} from "@notesnook/core/collections/reminders";
import { ReminderTime } from "../ui/reminder-time";
import { Tags, TagStrip } from "./tags";
const Line = ({ top = 6, bottom = 6 }) => {
const colors = useThemeStore((state) => state.colors);
return (
<View
style={{
height: 1,
backgroundColor: colors.nav,
width: "100%",
marginTop: top,
marginBottom: bottom
}}
/>
);
};
export const Properties = ({
close = () => {},
item,
@@ -50,13 +62,18 @@ export const Properties = ({
const colors = useThemeStore((state) => state.colors);
const alias = item.alias || item.title;
const isColor = !!COLORS_NOTE[item.title];
const reminders = db.relations.from(item, "reminder");
const current = getUpcomingReminder(reminders);
const onScrollEnd = () => {
getRef().current?.handleChildScrollEnd();
};
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
Start writing to save your note.
</Paragraph>
);
}
return (
<ScrollView
nestedScrollEnabled
@@ -71,86 +88,53 @@ export const Properties = ({
maxHeight: "100%"
}}
>
{!item || !item.id ? (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
Start writing to save your note.
</Paragraph>
) : (
<View
style={{
marginTop: 5,
zIndex: 10
}}
>
<View
style={{
paddingHorizontal: 12
}}
>
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
) : null}
{alias}
<View
style={{
paddingHorizontal: 12,
marginTop: 5,
zIndex: 10
}}
>
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
{item.headline || item.description ? (
<Paragraph
style={{
marginBottom: 5
}}
numberOfLines={2}
color={colors.icon}
>
{(item.type === "notebook" ||
item.itemType === "notebook" ||
item.type === "reminder") &&
item?.description
? item.description
: null}
{(item.type === "note" || item.itemType === "note") &&
item?.headline
? item.headline
: null}
</Paragraph>
) : null}
{item.type === "reminder" ? (
<ReminderTime
reminder={item}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start"
}}
fontSize={SIZE.xs + 1}
/>
) : null}
{item.type === "note" ? <Tags close={close} item={item} /> : null}
<Topics item={item} close={close} />
</View>
{item.type === "note" ? (
<Notebooks note={item} close={close} />
) : null}
{alias}
</Heading>
<DateMeta item={item} />
</View>
)}
{item.type === "note" ? <TagStrip close={close} item={item} /> : null}
{item.type === "reminder" ? (
<ReminderTime
reminder={item}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
backgroundColor: "transparent",
paddingHorizontal: 0
}}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>
<Line top={12} />
<DateMeta item={item} />
<Line bottom={0} />
{item.type === "note" ? <Tags close={close} item={item} /> : null}
<View
style={{
borderTopWidth: 1,
borderColor: colors.nav
paddingHorizontal: 12
}}
/>
{item.type === "note" ? <ColorTags close={close} item={item} /> : null}
>
{item.notebooks ? <Notebooks note={item} close={close} /> : null}
</View>
<Items
item={item}
@@ -172,6 +156,7 @@ export const Properties = ({
}}
/>
) : null}
<SheetProvider context="properties" />
</ScrollView>
);
};
@@ -184,50 +169,50 @@ Properties.present = (item, buttons = [], isSheet) => {
switch (type) {
case "trash":
props[0] = item;
props.push(["PermDelete", "Restore"]);
props.push(["delete", "restore"]);
break;
case "note":
android = Platform.OS === "android" ? ["PinToNotif"] : [];
android = Platform.OS === "android" ? ["pin-to-notifications"] : [];
props[0] = db.notes.note(item.id).data;
props.push([
"Add to notebook",
"Add-Reminder",
"Share",
"Export",
"Copy",
"Publish",
"Pin",
"Favorite",
"Attachments",
"Vault",
"Delete",
"RemoveTopic",
"History",
"ReadOnly",
"Reminders",
"Local only",
"Duplicate",
"notebooks",
"add-reminder",
"share",
"export",
"copy",
"publish",
"pin",
"favorite",
"attachments",
"lock-unlock",
"trash",
"remove-from-topic",
"history",
"read-only",
"reminders",
"local-only",
"duplicate",
...android,
...buttons
]);
break;
case "notebook":
props[0] = db.notebooks.notebook(item.id).data;
props.push(["Edit Notebook", "Pin", "Add Shortcut", "Delete"]);
props.push(["edit-notebook", "pin", "add-shortcut", "trash"]);
break;
case "topic":
props[0] = db.notebooks
.notebook(item.notebookId)
.topics.topic(item.id)._topic;
props.push(["Move notes", "Edit Topic", "Add Shortcut", "Delete"]);
props.push(["move-notes", "edit-topic", "add-shortcut", "trash"]);
break;
case "tag":
props[0] = db.tags.tag(item.id);
props.push(["Add Shortcut", "Delete", "Rename Tag"]);
props.push(["add-shortcut", "trash", "rename-tag"]);
break;
case "reminder": {
props[0] = db.reminders.reminder(item.id);
props.push(["Edit reminder", "Delete", "ReminderOnOff"]);
props.push(["edit-reminder", "trash", "disable-reminder"]);
break;
}
}

View File

@@ -33,19 +33,19 @@ export const Items = ({ item, buttons, close }) => {
const colors = useThemeStore((state) => state.colors);
const dimensions = useSettingStore((state) => state.dimensions);
const actions = useActions({ item, close });
const data = actions.filter((i) => buttons.indexOf(i.name) > -1 && !i.hidden);
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
let width = dimensions.width > 600 ? 600 : dimensions.width;
let columnItemsCount = DDS.isLargeTablet() ? 7 : 5;
let columnItemWidth = DDS.isTab
? (width - 24) / columnItemsCount
: (width - 24) / columnItemsCount;
? (width - 12) / columnItemsCount
: (width - 12) / columnItemsCount;
const _renderRowItem = ({ item }) => (
<View
onPress={item.func}
key={item.name}
testID={"icon-" + item.name}
key={item.id}
testID={"icon-" + item.id}
style={{
alignItems: "center",
width: columnItemWidth,
@@ -58,7 +58,7 @@ export const Items = ({ item, buttons, close }) => {
customStyle={{
height: columnItemWidth - 12,
width: columnItemWidth - 12,
borderRadius: 5,
borderRadius: 10,
justifyContent: "center",
alignItems: "center",
textAlign: "center",
@@ -72,14 +72,14 @@ export const Items = ({ item, buttons, close }) => {
color={
item.on
? colors.accent
: item.name === "Delete" || item.name === "PermDelete"
: item.id.match(/(delete|trash)/g)
? colors.errorText
: colors.icon
}
/>
</PressableButton>
<Paragraph size={SIZE.xs + 1} style={{ textAlign: "center" }}>
<Paragraph size={SIZE.xs} style={{ textAlign: "center" }}>
{item.title}
</Paragraph>
</View>
@@ -108,31 +108,119 @@ export const Items = ({ item, buttons, close }) => {
/>
);
const renderTopBarItem = ({ item }) => {
return (
<PressableButton
onPress={item.func}
key={item.id}
testID={"icon-" + item.id}
customStyle={{
alignItems: "center",
width: topBarItemWidth,
marginBottom: 10,
marginRight: 10,
backgroundColor: "transparent"
}}
>
<PressableButton
onPress={item.func}
type={item.on ? "shade" : "gray"}
customStyle={{
height: topBarItemWidth,
width: topBarItemWidth,
justifyContent: "center",
alignItems: "center",
textAlign: "center",
textAlignVertical: "center",
marginBottom: DDS.isTab ? 7 : 3.5,
borderRadius: 100
}}
>
<Icon
name={item.icon}
size={DDS.isTab ? SIZE.xxl : SIZE.md + 4}
color={
item.on
? colors.accent
: item.name === "Delete" || item.name === "PermDelete"
? colors.errorText
: colors.icon
}
/>
</PressableButton>
<Paragraph
size={SIZE.xxs + 1}
style={{ textAlign: "center" }}
textBreakStrategy="simple"
>
{item.title}
</Paragraph>
</PressableButton>
);
};
const topBarItemsList = [
"pin",
"favorite",
"copy",
"share",
"export",
"lock-unlock",
"publish"
];
const bottomBarItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) > -1
);
const bottomGridItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) === -1
);
const topBarItemWidth =
(width - (bottomBarItems.length * 10 + 14)) / bottomBarItems.length;
return item.type === "note" ? (
<FlatList
data={data}
keyExtractor={(item) => item.title}
key={columnItemsCount + "key"}
numColumns={columnItemsCount}
style={{
marginTop: item.type !== "note" ? 10 : 0,
paddingTop: 10
}}
columnWrapperStyle={{
justifyContent: "flex-start"
}}
contentContainerStyle={{
alignSelf: "center",
width: buttons.length < 5 ? "100%" : null,
paddingLeft: buttons.length < 5 ? 10 : 0
}}
renderItem={_renderRowItem}
/>
<>
<FlatList
data={bottomBarItems}
keyExtractor={(item) => item.title}
horizontal
disableVirtualization={true}
style={{
paddingHorizontal: 12,
paddingTop: 12
}}
renderItem={renderTopBarItem}
/>
<FlatList
data={bottomGridItems}
keyExtractor={(item) => item.title}
key={columnItemsCount + "key"}
numColumns={columnItemsCount}
disableVirtualization={true}
style={{
marginTop: item.type !== "note" ? 10 : 0,
paddingTop: 10
}}
columnWrapperStyle={{
justifyContent: "flex-start"
}}
contentContainerStyle={{
alignSelf: "center",
width: buttons.length < 5 ? "100%" : null,
paddingLeft: buttons.length < 5 ? 10 : 0
}}
renderItem={_renderRowItem}
/>
</>
) : (
<FlatList
data={data}
keyExtractor={(item) => item.title}
renderItem={renderColumnItem}
disableVirtualization={true}
/>
);
};

View File

@@ -23,39 +23,34 @@ 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 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 { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
export default function Notebooks({ note, close }) {
const colors = useThemeStore((state) => state.colors);
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 notebooks = [];
for (let notebook of item.notebooks) {
let item_notebook = db.notebooks.notebook(notebook.id);
if (item_notebook) {
let data = {
id: notebook.id,
title: item_notebook.title,
topics: notebook.topics
.map((item) => {
let topic = item_notebook.topics.topic(item)?._topic;
if (!topic) return null;
return {
id: topic.id,
title: topic.title
};
})
.filter((i) => i !== null)
};
notebooks.push(data);
let filteredNotebooks = [];
for (let notebookReference of item.notebooks) {
let notebook = {
...(notebooks.find((item) => item.id === notebookReference.id) || {})
};
if (notebook.id) {
notebook.topics = notebook.topics.filter((topic) => {
return notebookReference.topics.findIndex((t) => t === topic.id) > -1;
});
filteredNotebooks.push(notebook);
}
}
return notebooks;
return filteredNotebooks;
}
const noteNotebooks = getNotebooks(note);
const navigateNotebook = (id) => {
let item = db.notebooks.notebook(id)?.data;
@@ -69,84 +64,139 @@ export default function Notebooks({ note, close }) {
TopicNotes.navigate(item, true);
};
const renderItem = (item) => (
<View
key={item.id}
style={{
justifyContent: "flex-start",
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center",
flexShrink: 1,
flexGrow: 1,
padding: 6,
borderWidth: full ? 0 : 1,
borderColor: colors.nav,
borderRadius: 10,
backgroundColor: full ? "transparent" : colors.nav
}}
>
<Icon
name="book-outline"
color={colors.accent}
size={SIZE.sm}
style={{
marginRight: 5
}}
/>
<Heading
numberOfLines={1}
style={{
maxWidth: "50%"
}}
size={SIZE.sm}
onPress={() => {
navigateNotebook(item.id);
close();
}}
>
{item.title}
</Heading>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={{
flexDirection: "row",
marginLeft: 8,
borderLeftColor: colors.nav,
borderLeftWidth: 1,
paddingLeft: 8
}}
>
{item.topics.map((topic) => (
<Button
key={topic.id}
onPress={() => {
navigateTopic(topic.id, item.id);
close();
}}
onLongPress={async () => {
await db.notes.removeFromNotebook(
{
id: item.id,
topic: topic.id
},
note
);
useNotebookStore.getState().setNotebooks();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
ToastEvent.show({
heading: "Note removed from topic",
context: "local",
type: "success"
});
}}
title={topic.title}
type="gray"
height={30}
fontSize={SIZE.xs + 1}
icon="bookmark-outline"
style={{
marginRight: 5,
borderRadius: 100,
paddingHorizontal: 8
}}
/>
))}
<View style={{ width: 10 }} />
</ScrollView>
</View>
);
return !note.notebooks || note.notebooks.length === 0 ? null : (
<View
style={{
width: "100%",
borderTopWidth: 1,
borderTopColor: colors.nav
borderRadius: 10,
marginTop: 6
}}
>
{getNotebooks(note).map((item) => (
<PressableButton
key={item.id}
onPress={() => {
navigateNotebook(item.id);
close();
}}
customStyle={{
justifyContent: "flex-start",
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center",
flexShrink: 1,
flexGrow: 1,
marginTop: 5,
paddingVertical: 6
}}
>
<Icon
name="book-outline"
color={colors.accent}
size={SIZE.sm + 1}
style={{
marginRight: 5
}}
/>
<Heading
numberOfLines={1}
style={{
maxWidth: "50%"
}}
size={SIZE.sm}
>
{item.title}
</Heading>
{full
? noteNotebooks.map(renderItem)
: noteNotebooks.slice(0, 1).map(renderItem)}
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
style={{
flexDirection: "row",
marginLeft: 8,
borderLeftColor: colors.nav,
borderLeftWidth: 1,
paddingLeft: 8
}}
>
{item.topics.map((topic) => (
<Button
key={topic.id}
onPress={() => {
navigateTopic(topic.id, item.id);
close();
}}
title={topic.title}
type="grayBg"
height={22}
fontSize={SIZE.xs + 1}
icon="bookmark-outline"
style={{
marginRight: 5,
borderRadius: 100,
paddingHorizontal: 8
}}
/>
))}
<View style={{ width: 10 }} />
</ScrollView>
</PressableButton>
))}
{noteNotebooks.length > 1 && !full ? (
<Button
title={`See all linked notebooks`}
fontSize={SIZE.xs + 1}
style={{
alignSelf: "flex-end",
marginRight: 12,
paddingHorizontal: 0,
backgroundColor: "transparent"
}}
type="gray"
textStyle={{
textDecorationLine: "underline"
}}
height={20}
onPress={() => {
presentSheet({
context: "properties",
component: (ref, close) => (
<Notebooks note={note} close={close} full={true} />
)
});
}}
/>
) : undefined}
</View>
);
}

View File

@@ -27,6 +27,7 @@ import { eOpenTagsDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import { ColorTags } from "./color-tags";
export const Tags = ({ item, close }) => {
const colors = useThemeStore((state) => state.colors);
@@ -34,41 +35,53 @@ export const Tags = ({ item, close }) => {
<View
style={{
marginTop: 5,
marginBottom: 5
paddingTop: 6,
paddingBottom: 6,
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center",
paddingHorizontal: 12,
alignSelf: "center"
}}
>
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center"
<Button
onPress={async () => {
close();
await sleep(300);
eSendEvent(eOpenTagsDialog, item);
}}
>
<Button
onPress={async () => {
close();
await sleep(300);
eSendEvent(eOpenTagsDialog, item);
}}
buttonType={{
text: colors.accent
}}
title="Add tags"
type="grayBg"
icon="plus"
iconPosition="right"
height={25}
fontSize={SIZE.xs + 1}
style={{
marginRight: 5,
borderRadius: 100,
paddingHorizontal: 8
}}
/>
{item.tags.map((item) =>
item ? <TagItem key={item} tag={item} close={close} /> : null
)}
</View>
buttonType={{
text: colors.accent
}}
title="Add tags"
type="grayBg"
icon="plus"
iconPosition="right"
height={30}
fontSize={SIZE.xs + 1}
style={{
marginRight: 5,
borderRadius: 100,
paddingHorizontal: 8
}}
/>
<ColorTags item={item} />
</View>
) : null;
};
export const TagStrip = ({ item, close }) => {
return item.tags?.length > 0 ? (
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center"
}}
>
{item.tags.map((item) =>
item ? <TagItem key={item} tag={item} close={close} /> : null
)}
</View>
) : null;
};
@@ -83,23 +96,24 @@ const TagItem = ({ tag, close }) => {
};
const style = {
paddingHorizontal: 8,
marginVertical: 5,
paddingHorizontal: 0,
borderRadius: 100,
marginRight: 5
marginRight: 10,
marginTop: 0,
backgroundColor: "transparent"
};
return (
<Button
onPress={onPress}
title={"#" + tag}
// buttonType={{
// text: colors.accent
// }}
type="grayBg"
height={25}
height={20}
fontSize={SIZE.xs + 1}
style={style}
textStyle={{
textDecorationLine: "underline"
}}
/>
);
};

View File

@@ -23,6 +23,7 @@ import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { eSendEvent, 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";
@@ -47,7 +48,9 @@ export const SelectionHeader = React.memo(() => {
const currentScreen = useNavigationStore((state) => state.currentScreen);
const screen = currentScreen.name;
const insets = useGlobalSafeAreaInsets();
SearchService.prepareSearch?.();
const allItems = SearchService.getSearchInformation()?.get() || [];
const allSelected = allItems.length === selectedItemsList.length;
useEffect(() => {
if (selectionMode) {
tabBarRef.current?.lock();
@@ -149,14 +152,15 @@ export const SelectionHeader = React.memo(() => {
<View
style={{
width: "100%",
height: 50 + insets.top,
height: Platform.OS === "android" ? 50 + insets.top : 50,
paddingTop: Platform.OS === "android" ? insets.top : null,
backgroundColor: colors.bg,
justifyContent: "space-between",
alignItems: "center",
flexDirection: "row",
zIndex: 999,
paddingHorizontal: 12
paddingHorizontal: 12,
marginVertical: 10
}}
>
<View
@@ -208,20 +212,21 @@ export const SelectionHeader = React.memo(() => {
alignItems: "center"
}}
>
{/* <ActionIcon
<IconButton
onPress={async () => {
// await sleep(100);
// eSendEvent(eOpenMoveNoteDialog);
useSelectionStore.getState().setAll([...SearchService.getSearchInformation().data]);
useSelectionStore
.getState()
.setAll(allSelected ? [] : [...allItems]);
}}
tooltipText="Select all"
tooltipPosition={4}
customStyle={{
marginLeft: 10
}}
color={colors.pri}
color={allSelected ? colors.accent : colors.pri}
name="select-all"
size={SIZE.xl}
/> */}
/>
{screen === "Trash" ||
screen === "Notebooks" ||
@@ -238,6 +243,8 @@ export const SelectionHeader = React.memo(() => {
marginLeft: 10
}}
color={colors.pri}
tooltipText="Add to notebooks"
tooltipPosition={4}
name="plus"
size={SIZE.xl}
/>
@@ -245,6 +252,8 @@ export const SelectionHeader = React.memo(() => {
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
customStyle={{
marginLeft: 10
}}
@@ -284,6 +293,8 @@ export const SelectionHeader = React.memo(() => {
customStyle={{
marginLeft: 10
}}
tooltipText="Remove from topic"
tooltipPosition={4}
testID="select-minus"
color={colors.pri}
name="minus"
@@ -297,6 +308,8 @@ export const SelectionHeader = React.memo(() => {
customStyle={{
marginLeft: 10
}}
tooltipText="Remove from favorites"
tooltipPosition={4}
color={colors.pri}
name="star-off"
size={SIZE.xl}
@@ -327,6 +340,8 @@ export const SelectionHeader = React.memo(() => {
return;
}}
color={colors.pri}
tooltipText="Move to trash"
tooltipPosition={1}
name="delete"
size={SIZE.xl}
/>
@@ -341,6 +356,8 @@ export const SelectionHeader = React.memo(() => {
color={colors.pri}
onPress={restoreItem}
name="delete-restore"
tooltipText="Restore"
tooltipPosition={4}
size={SIZE.xl - 3}
/>
@@ -350,6 +367,8 @@ export const SelectionHeader = React.memo(() => {
}}
color={colors.pri}
onPress={deleteItem}
tooltipText="Delete"
tooltipPosition={4}
name="delete"
size={SIZE.xl - 3}
/>

View File

@@ -58,7 +58,7 @@ const SheetProvider = ({ context = "global" }) => {
if (visible || dialogData) {
setDialogData(null);
setVisible(false);
await sleep(500);
await sleep(0);
}
setDialogData(data);
setVisible(true);

View File

@@ -0,0 +1,33 @@
/*
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 { 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;
export const useSelectionContext = () => useContext(SelectionContext);

View File

@@ -0,0 +1,75 @@
/*
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, useRef, useState } from "react";
import { FlatList } from "react-native-gesture-handler";
import { db } from "../../../common/database";
import { ListHeaderInputItem } from "./list-header-item.js";
export const FilteredList = ({
data,
itemType,
onAddItem,
hasHeaderSearch,
...restProps
}) => {
const [filtered, setFiltered] = useState(data);
const query = useRef();
const onChangeText = useCallback(
(value) => {
query.current = value;
try {
if (!value) return setFiltered(data);
const results = db.lookup[itemType + "s"]([...data], value);
setFiltered(results);
} catch (e) {
console.warn(e.message);
}
},
[data, itemType]
);
const onSubmit = async (value) => {
return await onAddItem(value);
};
useEffect(() => {
onChangeText(query.current);
}, [data, onChangeText]);
return (
<FlatList
{...restProps}
data={filtered}
ListHeaderComponent={
hasHeaderSearch ? (
<ListHeaderInputItem
onSubmit={onSubmit}
onChangeText={onChangeText}
itemType={itemType}
testID={"list-input" + itemType}
placeholder={`Search or add a new ${itemType}`}
/>
) : null
}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
nestedScrollEnabled
/>
);
};

View File

@@ -17,10 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { createRef, useEffect, useState } from "react";
import React, {
createRef,
useCallback,
useEffect,
useMemo,
useState
} from "react";
import { Keyboard, TouchableOpacity, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { notesnook } from "../../../../e2e/test.ids";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
eSubscribeEvent,
@@ -32,23 +37,17 @@ import SearchService from "../../../services/search";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { getTotalNotes } from "../../../utils";
import { eOpenMoveNoteDialog } from "../../../utils/events";
import layoutmanager from "../../../utils/layout-manager";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
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";
let newNotebookTitle = null;
const notebookInput = createRef();
import { SelectionProvider } from "./context";
import { FilteredList } from "./filtered-list";
import { ListItem } from "./list-item";
const actionSheetRef = createRef();
const AddToNotebookSheet = () => {
const [visible, setVisible] = useState(false);
@@ -69,7 +68,6 @@ const AddToNotebookSheet = () => {
const _onClose = () => {
setVisible(false);
newNotebookTitle = null;
setNote(null);
Navigation.queueRoutesForUpdate(
"Notes",
@@ -93,130 +91,37 @@ export default AddToNotebookSheet;
const MoveNoteComponent = ({ note }) => {
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 selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const [expanded, setExpanded] = useState("");
const [notebookInputFocused, setNotebookInputFocused] = useState(false);
const [noteExists, setNoteExists] = useState([]);
const addNewNotebook = async () => {
if (!newNotebookTitle || newNotebookTitle.trim().length === 0)
return ToastEvent.show({
heading: "Notebook title is required",
type: "error",
context: "local"
});
const [itemState, setItemState] = useState({});
let id = await db.notebooks.add({
title: newNotebookTitle,
description: null,
topics: [],
id: null
});
setExpanded(id);
openAddTopicDialog(db.notebooks.notebook(id).data);
notebookInput.current?.clear();
notebookInput.current?.blur();
setNotebooks();
updateNoteExists();
};
const addNewTopic = async (value, item) => {
if (!value || value.trim().length === 0) {
const onAddNotebook = async (title) => {
if (!title || title.trim().length === 0) {
ToastEvent.show({
heading: "Topic title is required",
heading: "Notebook title is required",
type: "error",
context: "local"
});
return false;
}
await db.notebooks.notebook(item.id).topics.add(value);
await db.notebooks.add({
title: title,
description: null,
topics: [],
id: null
});
setNotebooks();
updateNoteExists();
return true;
};
const handlePress = async (item) => {
let noteIds =
selectedItemsList.length > 0
? selectedItemsList.map((n) => n.id)
: [note?.id];
if (getCount(item)) {
await db.notes.removeFromNotebook(
{
id: item.notebookId,
topic: item.id
},
...noteIds
);
} else {
await db.notes.addToNotebook(
{
topic: item.id,
id: item.notebookId,
rebuildCache: true
},
...noteIds
);
}
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
setNotebooks();
updateNoteExists();
SearchService.updateAndSearch();
};
useEffect(() => {
updateNoteExists();
}, [updateNoteExists]);
const updateNoteExists = useCallback(() => {
if (!note?.id && selectedItemsList?.length === 0) return;
let notes =
selectedItemsList.length > 0
? selectedItemsList.map((n) => n.id)
: [note?.id];
let ids = [];
let notebooks = db.notebooks.all;
for (let i = 0; i < notebooks.length; i++) {
if (notebooks[i].topics) {
for (let t = 0; t < notebooks[i].topics.length; t++) {
let topic = notebooks[i].topics[t];
if (topic.type !== "topic") continue;
for (let id of notes) {
const noteIds = db.notes?.topicReferences.get(topic.id);
if (noteIds.indexOf(id) > -1) {
if (ids.indexOf(notebooks[i].id) === -1) {
ids.push(notebooks[i].id);
}
if (ids.indexOf(topic.id) === -1) ids.push(topic.id);
}
}
}
}
}
setNoteExists(ids);
}, [note?.id, selectedItemsList]);
const openAddTopicDialog = (item) => {
presentDialog({
context: "move_note",
@@ -226,27 +131,223 @@ const MoveNoteComponent = ({ note }) => {
paragraph: "Add a new topic in " + item.title,
positiveText: "Add",
positivePress: (value) => {
return addNewTopic(value, item);
return onAddTopic(value, item);
}
});
};
const getCount = (topic) => {
if (!topic) return;
let notes =
selectedItemsList.length > 0
? selectedItemsList.map((n) => n.id)
: [note?.id];
let count = 0;
for (let id of notes) {
const noteIds = db.notes?.topicReferences.get(topic.id);
if (noteIds.indexOf(id) > -1) {
count++;
const onAddTopic = useCallback(
async (value, item) => {
if (!value || value.trim().length === 0) {
ToastEvent.show({
heading: "Topic title is required",
type: "error",
context: "local"
});
return false;
}
await db.notebooks.notebook(item.id).topics.add(value);
setNotebooks();
return true;
},
[setNotebooks]
);
// eslint-disable-next-line react-hooks/exhaustive-deps
const getSelectedNotesCountInItem = React.useCallback(
(item) => {
switch (item.type) {
case "notebook": {
const noteIds = [];
for (let topic of item.topics) {
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
}
let count = 0;
selectedItemsList.forEach((item) =>
noteIds.indexOf(item.id) > -1 ? count++ : undefined
);
return count;
}
case "topic": {
const noteIds = db.notes?.topicReferences.get(item.id);
let count = 0;
selectedItemsList.forEach((item) =>
noteIds.indexOf(item.id) > -1 ? count++ : undefined
);
return count;
}
}
},
[selectedItemsList]
);
useEffect(() => {
resetItemState();
// 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
? state
: areAllSelectedItemsInAllTopics(notebook, selectedItemsList) &&
getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "selected"
: getSelectedNotesCountInItem(notebook, 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);
}
}
}
return itemState;
});
},
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
);
const getItemsForItem = (item) => {
switch (item.type) {
case "notebook":
return item.topics?.filter((t) => t.type === "topic");
}
};
function areAllSelectedItemsInAllTopics(notebook, items) {
return items.every((item) => {
return notebook.topics.every((topic) => {
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
});
});
}
function areAllSelectedItemsInTopic(topic, items) {
return items.every((item) => {
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
});
}
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 contextValue = useMemo(
() => ({
enabled: multiSelect,
toggleSelection: (item) => {
setItemState((itemState) => {
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
return itemState;
});
},
setMultiSelect: setMultiSelect,
deselect: (item) => {
updateItemState(item, "deselected");
},
select: (item) => {
updateItemState(item, "selected");
},
deselectAll: (state) => {
resetItemState(state);
}
}),
[multiSelect, resetItemState, updateItemState]
);
const getItemFromId = (id) => {
for (const nb of notebooks) {
if (nb.id === id) return nb;
for (const tp of nb.topics) {
if (tp.id === id) return tp;
}
}
return count > 0 && notes.length > 0
? `${count} of ${notes.length} selected notes exist in this topic. (Tap to remove)`
: null;
};
const onSave = async () => {
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
);
} else if (itemState[id] === "deselected") {
await db.notes.removeFromNotebook(
{
id: item.notebookId,
topic: item.id,
rebuildCache: true
},
...noteIds
);
}
}
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
setNotebooks();
SearchService.updateAndSearch();
actionSheetRef.current?.hide();
};
return (
@@ -266,215 +367,162 @@ const MoveNoteComponent = ({ note }) => {
<View
style={{
paddingHorizontal: 12,
justifyContent: "space-between",
flexDirection: "row",
justifyContent: "space-between"
alignItems: "flex-start"
}}
>
<DialogHeader
title="Add to notebook"
paragraph={"Add your notes to notebooks to find them easily."}
style={{
minHeight: 10,
flexShrink: 1
}}
title="Select notebooks"
paragraph={
!multiSelect
? "Long press to enable multi-select."
: "Select topics you want to add note(s) to."
}
/>
<Button
height={35}
style={{
borderRadius: 100,
paddingHorizontal: 24,
alignSelf: "flex-start"
}}
title="Save"
type={"accent"}
onPress={onSave}
/>
</View>
<FlatList
nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
data={notebooks}
ListFooterComponent={
<View
{multiSelect ? (
<View
style={{
paddingHorizontal: 12
}}
>
<Button
title="Reset selection"
height={30}
style={{
height: 200
alignSelf: "flex-start",
paddingHorizontal: 0
}}
onPress={() => {
resetItemState();
setMultiSelect(false);
}}
/>
}
ListHeaderComponent={
<View
style={{
width: "100%",
marginTop: 10
}}
>
<Input
fwdRef={notebookInput}
onChangeText={(value) => {
newNotebookTitle = value;
}}
testID={notesnook.ids.dialogs.addTo.addNotebook}
blurOnSubmit={false}
onFocusInput={() => {
setNotebookInputFocused(true);
}}
onBlurInput={() => {
setNotebookInputFocused(false);
}}
button={{
icon: "check",
color: notebookInputFocused ? colors.accent : colors.icon,
onPress: addNewNotebook
}}
onSubmit={addNewNotebook}
placeholder="Create a new notebook"
/>
</View>
}
style={{
paddingHorizontal: 12
}}
renderItem={({ item }) => (
<View
style={{
borderWidth: 1,
borderColor: expanded ? colors.nav : "transparent",
borderRadius: 6,
overflow: "hidden",
marginBottom: 10
}}
>
<PressableButton
onPress={() => {
if (!item.topics || item.topics.length === 0) {
setExpanded(item.id);
openAddTopicDialog(item);
return;
}
layoutmanager.withAnimation(200);
setExpanded(item.id === expanded ? null : item.id);
setNotebookInputFocused(false);
}}
type="grayBg"
customStyle={{
height: 50,
width: "100%",
borderRadius: 5,
alignItems: "flex-start"
}}
>
</View>
) : null}
<SelectionProvider value={contextValue}>
<FilteredList
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
style={{
paddingHorizontal: 12
}}
ListEmptyComponent={
notebooks.length > 0 ? null : (
<View
style={{
width: "100%",
height: 50,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12
height: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<View>
<Heading
color={
noteExists.indexOf(item.id) > -1 ? colors.accent : null
}
size={SIZE.md}
>
{item.title}
</Heading>
{item.topics?.length > 0 ? (
<Paragraph size={SIZE.xs} color={colors.icon}>
{getTotalNotes(item) + " notes" + " & "}
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
</Paragraph>
) : null}
</View>
<IconButton
name={expanded === item.id ? "plus" : "chevron-down"}
color={expanded === item.id ? colors.accent : colors.pri}
size={SIZE.xl}
onPress={() => {
if (expanded !== item.id) {
setExpanded(item.id);
return;
}
layoutmanager.withAnimation(200);
setExpanded(item.id);
openAddTopicDialog(item);
}}
/>
<Icon name="book-outline" color={colors.icon} size={100} />
<Paragraph style={{ marginBottom: 10 }}>
You do not have any notebooks.
</Paragraph>
</View>
</PressableButton>
{expanded === item.id ? (
<FlatList
nestedScrollEnabled
data={item.topics?.filter((t) => t.type === "topic")}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
style={{
width: "100%",
alignSelf: "flex-end",
maxHeight: 500
}}
renderItem={({ item, index }) => (
<PressableButton
onPress={() => handlePress(item, index)}
type="gray"
customStyle={{
minHeight: 50,
borderTopWidth: index === 0 ? 0 : 1,
borderTopColor: index === 0 ? null : colors.nav,
width: "100%",
borderRadius: 0,
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 12,
justifyContent: "space-between",
paddingVertical: 12
}}
>
<View>
<Paragraph color={colors.heading}>
{item.title}
</Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}>
{getTotalNotes(item) + " notes"}
</Paragraph>
{getCount(item) ? (
<View
style={{
backgroundColor: colors.nav,
borderRadius: 5,
paddingHorizontal: 5,
paddingVertical: 2,
marginTop: 5
}}
>
<Paragraph color={colors.icon} size={SIZE.xs}>
{getCount(item)}
</Paragraph>
</View>
) : null}
</View>
{noteExists.indexOf(item.id) > -1 ? (
<Button
onPress={() => handlePress(item, index)}
icon="check"
testID="icon-check"
iconColor={colors.accent}
iconSize={SIZE.lg}
height={35}
width={35}
style={{
borderRadius: 100,
paddingHorizontal: 0
}}
/>
) : null}
</PressableButton>
)}
/>
) : null}
</View>
)}
/>
)
}
data={notebooks}
hasHeaderSearch={true}
renderItem={({ item, index }) => (
<ListItem
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"}
infoText={
<>
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
</>
}
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,
style: {
marginBottom: 0,
height: 40
},
onPress: (item) => {
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);
}
},
key: item.id,
type: "transparent"
})}
icon={(expanded) => ({
name: expanded ? "chevron-up" : "chevron-down",
color: expanded ? colors.accent : colors.pri
})}
onScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
hasSubList={true}
hasHeaderSearch={false}
type="grayBg"
sublistItemType="topic"
onAddItem={(title) => {
return onAddTopic(title, item);
}}
onAddSublistItem={(item) => {
openAddTopicDialog(item);
}}
/>
)}
itemType="notebook"
onAddItem={async (title) => {
return await onAddNotebook(title);
}}
ListFooterComponent={
<View
style={{
height: 200
}}
/>
}
/>
</SelectionProvider>
</View>
</>
);

View File

@@ -0,0 +1,87 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import Input from "../../ui/input";
import Paragraph from "../../ui/typography/paragraph";
export const ListHeaderInputItem = ({
onSubmit,
onChangeText,
placeholder,
testID
}) => {
const [focused, setFocused] = useState(false);
const colors = useThemeStore((state) => state.colors);
const [inputValue, setInputValue] = useState();
const inputRef = useRef();
return (
<View
style={{
width: "100%",
marginTop: 10,
marginBottom: 5
}}
>
<Input
fwdRef={inputRef}
onChangeText={(value) => {
setInputValue(value);
onChangeText?.(value);
}}
testID={testID}
blurOnSubmit={false}
onFocusInput={() => {
setFocused(true);
}}
onBlurInput={() => {
setFocused(false);
}}
button={{
icon: inputValue ? "plus" : "magnify",
color: focused ? colors.accent : colors.icon,
onPress: async () => {
const result = await onSubmit(inputValue);
if (result) {
inputRef.current?.blur();
}
}
}}
placeholder={placeholder}
marginBottom={5}
/>
{inputValue ? (
<View
style={{
backgroundColor: colors.shade,
padding: 5,
borderRadius: 5,
marginBottom: 10
}}
>
<Paragraph color={colors.accent}>
Tap on + to add {`"${inputValue}"`}
</Paragraph>
</View>
) : null}
</View>
);
};

View File

@@ -0,0 +1,233 @@
/*
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, { useState } from "react";
import { View } from "react-native";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
import { PressableButton } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useSelectionContext } from "./context";
import { FilteredList } from "./filtered-list";
const _ListItem = ({
item,
index,
icon,
infoText,
intermediate,
hasSubList,
onPress,
onScrollEnd,
getListItems,
style,
type,
sublistItemType,
onAddItem,
getSublistItemProps,
removed,
isSelected,
hasHeaderSearch,
onAddSublistItem
}) => {
const { enabled, toggleSelection, setMultiSelect, select, deselect } =
useSelectionContext();
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);
}
});
}
}
return (
<View
style={{
overflow: "hidden",
marginBottom: 10,
...style
}}
>
<PressableButton
onPress={() => {
if (hasSubList) return setExpanded(!expanded);
if (enabled) return selectItem();
onPress?.(item);
}}
type={type}
onLongPress={() => {
setMultiSelect(true);
selectItem();
}}
customStyle={{
height: style?.height || 50,
width: "100%",
alignItems: "flex-start"
}}
>
<View
style={{
width: "100%",
height: 50,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12
}}
>
<View
style={{
flexDirection: "row",
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"
}
/>
<View>
{hasSubList && expanded ? (
<Heading size={SIZE.md}>{item.title}</Heading>
) : (
<Paragraph size={SIZE.sm}>{item.title}</Paragraph>
)}
{infoText ? (
<Paragraph size={SIZE.xs} color={colors.icon}>
{infoText}
</Paragraph>
) : null}
</View>
</View>
<View
style={{
flexDirection: "row"
}}
>
{onAddSublistItem ? (
<IconButton
name={"plus"}
testID="add-item-icon"
color={colors}
size={SIZE.xl}
onPress={() => {
onAddSublistItem(item);
}}
/>
) : null}
{icon ? (
<IconButton
name={icon(expanded).name}
color={icon(expanded).color}
size={icon(expanded).size || SIZE.xl}
onPress={
hasSubList
? () => setExpanded(!expanded)
: icon(expanded).onPress
}
/>
) : null}
</View>
</View>
</PressableButton>
{expanded && hasSubList ? (
<FilteredList
nestedScrollEnabled
data={getListItems(item)}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
onMomentumScrollEnd={onScrollEnd}
style={{
width: "95%",
alignSelf: "flex-end",
maxHeight: 500
}}
itemType={sublistItemType}
hasHeaderSearch={hasHeaderSearch}
renderItem={({ item, index }) => (
<ListItem
item={item}
{...getSublistItemProps(item)}
index={index}
onScrollEnd={onScrollEnd}
/>
)}
onAddItem={onAddItem}
/>
) : null}
</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

@@ -46,6 +46,8 @@ 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";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheet>;
@@ -119,6 +121,10 @@ export default function ReminderSheet({
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 showDatePicker = () => {
setDatePickerVisibility(true);
};
@@ -156,7 +162,7 @@ export default function ReminderSheet({
async function saveReminder() {
try {
if (!(await Notifications.checkAndRequestPermissions()))
if (!(await Notifications.checkAndRequestPermissions(true)))
throw new Error(
"App does not have permission to schedule notifications"
);
@@ -227,6 +233,7 @@ export default function ReminderSheet({
paddingHorizontal: 12
}}
>
<Dialog context="local"/>
<ScrollView
onScrollEndDrag={() => actionSheetRef.current?.handleChildScrollEnd()}
style={{
@@ -235,17 +242,31 @@ export default function ReminderSheet({
>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title}
defaultValue={reminder?.title || referencedItem?.title}
placeholder="Remind me of..."
onChangeText={(text) => (title.current = text)}
containerStyle={{ borderWidth: 0, borderBottomWidth: 1 }}
/>
<Input
defaultValue={reminder?.description}
defaultValue={reminder ? reminder?.description : referencedItem?.headline}
placeholder="Add a quick note"
onChangeText={(text) => (details.current = text)}
containerStyle={{ borderWidth: 0, borderBottomWidth: 1 }}
containerStyle={{
borderWidth: 0,
borderBottomWidth: 1,
maxHeight: 80,
marginTop: 10
}}
multiline
textAlignVertical="top"
inputStyle={{
height: 80
}}
height={80}
wrapperStyle={{
marginBottom: 20
}}
/>
<View

View File

@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useCallback } from "react";
import { FlatList, View } from "react-native";
import { notesnook } from "../../../e2e/test.ids";
import umami from "../../common/analytics";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
@@ -76,7 +75,6 @@ export const SideMenu = React.memo(
name: "Notesnook Pro",
icon: "crown",
func: () => {
umami.pageView("/pro-screen", "/sidemenu");
eSendEvent(eOpenPremiumDialog);
}
};

View File

@@ -73,6 +73,7 @@ interface InputProps extends TextInputProps {
buttonLeft?: React.ReactNode;
inputStyle?: TextInputProps["style"];
containerStyle?: ViewStyle;
wrapperStyle?: ViewStyle;
flexGrow?: number;
}
@@ -100,6 +101,7 @@ const Input = ({
flexGrow = 1,
inputStyle = {},
containerStyle = {},
wrapperStyle = {},
...restProps
}: InputProps) => {
const colors = useThemeStore((state) => state.colors);
@@ -236,7 +238,8 @@ const Input = ({
marginBottom: marginBottom,
flexGrow: flexGrow,
maxHeight: height,
marginRight: marginRight
marginRight: marginRight,
...wrapperStyle
}}
>
<TouchableOpacity

View File

@@ -59,8 +59,8 @@ const SheetWrapper = ({
zIndex: 10,
paddingTop: 5,
paddingBottom: 0,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
alignSelf: "center",
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0
@@ -104,12 +104,12 @@ const SheetWrapper = ({
ExtraOverlayComponent={
<>
{overlay}
<Toast context="local" />
<PremiumToast
context="sheet"
close={() => fwdRef?.current?.hide()}
offset={50}
/>
<Toast context="local" />
</>
}
onClose={_onClose}

View File

@@ -21,23 +21,15 @@ import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Cross platform Reminders 🔔",
body: "Finally reminders are here. You can set reminders on notes or independently. Go to Side Menu > Reminders to set your first reminder!"
title: "Improved editor performance",
body: "The editor perfomance is now much better especially with images"
},
{
title: "Multi-factor auth by default",
body: "Multi-factor auth is enabled by default for all users to improve login security."
title: "Sortable task lists",
body: "Now you can sort tasks by checked/unchecked in editor"
},
{
title: "Change account email",
body: "Now you can go to Settings > Account Settings to change your email."
},
{
title: "Sync settings",
body: "Added options to disable sync, auto-sync & real-time sync from settings."
},
{
title: "Improved PDF exports styling",
body: "Tables, checklists, codeblocks, quotes are now properly formatted in PDF exports."
title: "Improved outline lists",
body: "Rewritten outline lists to be more performant and less buggy"
}
];

View File

@@ -21,7 +21,6 @@ import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native";
import Share from "react-native-share";
import { notesnook } from "../../e2e/test.ids";
import { db } from "../common/database";
import { presentDialog } from "../components/dialog/functions";
import NoteHistory from "../components/note-history";
@@ -602,22 +601,49 @@ export const useActions = ({ close = () => null, item }) => {
};
const actions = [
{
name: "Add to notebook",
title: "Add to notebook",
id: "notebooks",
title: "Link Notebooks",
icon: "book-outline",
func: addTo
},
{
name: "Add-Reminder",
title: "Add reminder",
icon: "bell-plus",
id: "add-tag",
title: "Add tags",
icon: "pound",
func: addTo
},
{
id: "add-reminder",
title: "Remind me",
icon: "clock-plus-outline",
func: () => {
ReminderSheet.present(null, { id: item.id, type: "note" });
},
close: true
},
{
name: "Move notes",
id: "lock-unlock",
title: item.locked ? "Unlock" : "Lock",
icon: item.locked ? "lock-open-outline" : "key-outline",
func: addToVault,
on: item.locked
},
{
id: "publish",
title: isPublished ? "Published" : "Publish",
icon: "cloud-upload-outline",
on: isPublished,
func: publishNote
},
{
id: "export",
title: "Export",
icon: "export",
func: exportNote
},
{
id: "move-notes",
title: "Add notes",
icon: "plus",
func: async () => {
@@ -626,43 +652,40 @@ export const useActions = ({ close = () => null, item }) => {
MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item);
}
},
{
name: "Pin",
title: item.pinned ? "Unpin" : "Pin to top",
id: "pin",
title: item.pinned ? "Unpin" : "Pin",
icon: item.pinned ? "pin-off-outline" : "pin-outline",
func: pinItem,
close: false,
check: true,
on: item.pinned,
nopremium: true,
id: notesnook.ids.dialogs.actionsheet.pin
pro: true
},
{
name: "Favorite",
id: "favorite",
title: !item.favorite ? "Favorite" : "Unfavorite",
icon: item.favorite ? "star-off" : "star-outline",
func: addToFavorites,
close: false,
check: true,
on: item.favorite,
nopremium: true,
id: notesnook.ids.dialogs.actionsheet.favorite,
pro: true,
color: "orange"
},
{
name: "PinToNotif",
id: "pin-to-notifications",
title:
notifPinned !== null
? "Unpin from Notifications"
: "Pin to Notifications",
icon: "bell",
: "Pin to notifications",
icon: "message-badge-outline",
on: notifPinned !== null,
func: pinToNotifications
},
{
name: "Edit Notebook",
id: "edit-notebook",
title: "Edit notebook",
icon: "square-edit-outline",
func: async () => {
@@ -672,7 +695,7 @@ export const useActions = ({ close = () => null, item }) => {
}
},
{
name: "Edit Topic",
id: "edit-topic",
title: "Edit topic",
icon: "square-edit-outline",
func: async () => {
@@ -685,131 +708,72 @@ export const useActions = ({ close = () => null, item }) => {
}
},
{
name: "Copy",
id: "copy",
title: "Copy",
icon: "content-copy",
func: copyContent
},
{
name: "Restore",
id: "restore",
title: "Restore " + item.itemType,
icon: "delete-restore",
func: restoreTrashItem
},
{
name: "Publish",
title: isPublished ? "Published" : "Publish",
icon: "cloud-upload-outline",
on: isPublished,
func: publishNote
},
{
name: "Vault",
title: item.locked ? "Remove from vault" : "Add to vault",
icon: item.locked ? "shield-off-outline" : "shield-outline",
func: addToVault,
on: item.locked
},
{
name: "Add Shortcut",
id: "add-shortcut",
title: isPinnedToMenu ? "Remove Shortcut" : "Add Shortcut",
icon: isPinnedToMenu ? "link-variant-remove" : "link-variant",
func: createMenuShortcut,
close: false,
check: true,
on: isPinnedToMenu,
nopremium: true,
id: notesnook.ids.dialogs.actionsheet.pinMenu
pro: true
},
{
name: "Rename Tag",
id: "rename-tag",
title: "Rename tag",
icon: "square-edit-outline",
func: renameTag
},
{
name: "Share",
id: "share",
title: "Share",
icon: "share-variant",
func: shareNote
},
{
name: "Attachments",
title: "Attachments",
icon: "attachment",
func: showAttachments
},
{
name: "Export",
title: "Export",
icon: "export",
func: exportNote
},
{
name: "RemoveTopic",
title: "Remove from topic",
hidden: !isNoteInTopic(),
icon: "minus-circle-outline",
func: removeNoteFromTopic
},
{
name: "Delete",
title:
item.type !== "notebook" && item.type !== "note"
? "Delete " + item.type
: "Move to trash",
icon: "delete-outline",
type: "error",
func: deleteItem
},
{
name: "PermDelete",
title: "Delete " + item.itemType,
icon: "delete",
func: deleteTrashItem
},
{
name: "ReadOnly",
id: "read-only",
title: "Read only",
icon: "pencil-lock",
func: toggleReadyOnlyMode,
on: item.readonly
},
{
name: "Local only",
id: "local-only",
title: "Local only",
icon: "sync-off",
func: toggleLocalOnly,
on: item.localOnly
},
{
name: "History",
title: "History",
icon: "history",
func: openHistory
},
{
name: "Duplicate",
id: "duplicate",
title: "Duplicate",
icon: "content-duplicate",
func: duplicateNote
},
{
name: "Dark Mode",
id: "dark-mode",
title: "Dark mode",
icon: "theme-light-dark",
func: switchTheme,
switch: true,
on: colors.night ? true : false,
close: false,
nopremium: true,
id: notesnook.ids.dialogs.actionsheet.night
pro: true
},
{
name: "Edit reminder",
id: "edit-reminder",
title: "Edit reminder",
icon: "pencil",
func: async () => {
@@ -819,10 +783,11 @@ export const useActions = ({ close = () => null, item }) => {
},
close: false
},
{
name: "Reminders",
id: "reminders",
title: "Reminders",
icon: "bell",
icon: "clock-outline",
func: async () => {
close();
RelationsList.present({
@@ -842,7 +807,20 @@ export const useActions = ({ close = () => null, item }) => {
close: false
},
{
name: "ReminderOnOff",
id: "attachments",
title: "Attachments",
icon: "attachment",
func: showAttachments
},
{
id: "history",
title: "History",
icon: "history",
func: openHistory
},
{
id: "disable-reminder",
title: !item.disabled ? "Turn off reminder" : "Turn on reminder",
icon: !item.disabled ? "bell-off-outline" : "bell",
func: async () => {
@@ -863,9 +841,32 @@ export const useActions = ({ close = () => null, item }) => {
"Reminders"
);
}
},
{
id: "remove-from-topic",
title: "Remove from topic",
hidden: !isNoteInTopic(),
icon: "minus-circle-outline",
func: removeNoteFromTopic
},
{
id: "trash",
title:
item.type !== "notebook" && item.type !== "note"
? "Delete " + item.type
: "Move to trash",
icon: "delete-outline",
type: "error",
func: deleteItem
},
{
id: "delete",
title: "Delete " + item.itemType,
icon: "delete",
func: deleteTrashItem
}
// {
// name: "ReferencedIn",
// id: "ReferencedIn",
// title: "References",
// icon: "link",
// func: async () => {

View File

@@ -26,12 +26,13 @@ import {
Linking,
NativeEventEmitter,
NativeModules,
Platform
Platform,
Keyboard
} from "react-native";
import * as RNIap from "react-native-iap";
import { enabled } from "react-native-privacy-snapshot";
import { Walkthrough } from "../components/walkthroughs";
import { editorState } from "../screens/editor/tiptap/utils";
import { editorController, editorState } from "../screens/editor/tiptap/utils";
import {
clearMessage,
setEmailVerifyMessage,
@@ -69,6 +70,7 @@ import { clearAppState } from "../screens/editor/tiptap/utils";
import { tabBarRef } from "../utils/global-refs";
import BackupService from "../services/backup";
import { sleep } from "../utils/time";
import notifee from "@notifee/react-native";
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
export const useAppEvents = () => {
@@ -316,6 +318,10 @@ export const useAppEvents = () => {
const onLogout = async (reason) => {
console.log("Logged out", reason);
DatabaseLogger.log("User Logged Out" + reason);
SettingsService.set({
introCompleted: true
});
};
const unsubIAP = () => {
@@ -444,6 +450,7 @@ export const useAppEvents = () => {
const onAppStateChanged = useCallback(
async (state) => {
if (state === "active") {
notifee.setBadgeCount(0);
updateStatusBarColor();
if (
SettingsService.get().appLockMode !== "background" &&
@@ -488,6 +495,10 @@ export const useAppEvents = () => {
!useUserStore.getState().verifyUser
) {
useUserStore.getState().setVerifyUser(true);
if (Platform.OS === "ios") {
editorController.current?.commands.blur();
Keyboard.dismiss();
}
}
if (
SettingsService.get().privacyScreen ||

View File

@@ -107,6 +107,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,

View File

@@ -28,13 +28,14 @@ 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 { useThemeStore } from "../../stores/use-theme-store";
import { eClearEditor, eOnLoadNote } from "../../utils/events";
import { eClearEditor } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { editorState } from "./tiptap/utils";
const EditorOverlay = ({ editorId = "", editor }) => {
@@ -64,13 +65,8 @@ const EditorOverlay = ({ editorId = "", editor }) => {
opacity.value = 1;
translateValue.value = 0;
timers.current.error = setTimeout(() => {
if (_loading) {
let note = _loading;
note.forced = true;
eSendEvent(eOnLoadNote + editorId, note);
}
setError(true);
}, 4000);
}, 15 * 1000);
} else {
clearTimers();
setTimeout(() => {
@@ -85,7 +81,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
}, 100);
}
},
[editorId, opacity, translateValue]
[opacity, translateValue]
);
useEffect(() => {
@@ -144,15 +140,19 @@ const EditorOverlay = ({ editorId = "", editor }) => {
paddingRight: 12
}}
>
<IconButton
onPress={() => {
eSendEvent(eClearEditor);
opacity.value = 0;
translateValue.value = 6000;
}}
name="arrow-left"
color={colors.pri}
/>
{DDS.isTablet() ? (
<View />
) : (
<IconButton
onPress={() => {
eSendEvent(eClearEditor);
opacity.value = 0;
translateValue.value = 6000;
}}
name="arrow-left"
color={colors.pri}
/>
)}
<View
style={{

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
};
};

View File

@@ -34,6 +34,7 @@ import PremiumService from "../../../services/premium";
import { eCloseSheet } from "../../../utils/events";
import { sleep } from "../../../utils/time";
import { editorController, editorState } from "./utils";
import { compressToBase64 } from "../../../common/filesystem/compress";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -124,6 +125,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,
@@ -234,7 +236,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,6 +245,15 @@ 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(image.uri, isPng ? "PNG" : "JPEG"));
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
editorController.current?.commands.insertImage({
hash: hash,
@@ -289,7 +300,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

@@ -28,8 +28,9 @@ import {
NativeEventSubscription
} from "react-native";
import { WebViewMessageEvent } from "react-native-webview";
import umami from "../../../common/analytics/index";
import { db } from "../../../common/database";
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 {
@@ -53,15 +54,13 @@ import {
eOpenPublishNoteDialog,
eOpenTagsDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
import { NoteType } from "../../../utils/types";
import { useDragState } from "../../settings/editor/state";
import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { EditorEvents, editorState } from "./utils";
import { openLinkInBrowser } from "../../../utils/functions";
import { EventTypes } from "./editor-events";
import { RelationsList } from "../../../components/sheets/relations-list";
import ReminderSheet from "../../../components/sheets/reminder";
const publishNote = async (editor: useEditorType) => {
const user = useUserStore.getState().user;
@@ -363,7 +362,6 @@ export const useEditorEvents = (
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;
}
umami.pageView("/pro-screen", "/editor");
eSendEvent(eOpenPremiumDialog);
break;
case EventTypes.monograph:

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";
@@ -50,8 +52,6 @@ import {
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 attachedImages = useRef<{ [name: string]: any }>({});
const loadedImages = useRef<{ [name: string]: any }>({});
const postMessage = useCallback(
async <T>(type: string, data: T) =>
@@ -112,7 +114,7 @@ export const useEditor = (
(show: boolean, data = { type: "new" }) => {
eSendEvent(
"loadingNote" + editorId,
show ? currentNote.current || data : false
show ? data || currentNote.current : false
);
},
[editorId]
@@ -154,7 +156,10 @@ export const useEditor = (
async (resetState = true) => {
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
currentNote.current = null;
attachedImages.current = [];
loadedImages.current = [];
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
useEditorStore.getState().setReadonly(false);
@@ -290,6 +295,52 @@ export const useEditor = (
}
}, []);
const getImagesToLoad = () => {
if (!currentNote.current?.id) return [];
const currentImages = [
...(db.attachments?.ofNote(currentNote.current?.id, "images") || []),
...(db.attachments?.ofNote(currentNote.current?.id, "webclips") || [])
];
if (!currentImages || currentImages?.length === 0) return [];
const imagesToLoad: any[] = [];
for (const image of currentImages) {
if (!loadedImages.current[image.metadata.hash]) {
loadedImages.current[image.metadata.hash] = false;
imagesToLoad.push(image);
}
attachedImages.current[image.metadata.hash] = image;
}
return imagesToLoad;
};
const markImageLoaded = (hash: string) => {
const attachment = attachedImages.current[hash];
if (attachment) {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback(() => {
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 images = getImagesToLoad();
if (images.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, images);
}
}
}, 100);
}, []);
const loadNote = useCallback(
async (
item: Omit<NoteType, "type"> & {
@@ -333,25 +384,17 @@ export const useEditor = (
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 +421,7 @@ export const useEditor = (
return;
lock.current = true;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -408,16 +452,19 @@ 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();
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages();
},
true
);
}
},
[commands, postMessage, lockNoteWithVault]
[loadImages, lockNoteWithVault, postMessage, commands]
);
useEffect(() => {
@@ -560,6 +607,7 @@ export const useEditor = (
onReady,
saveContent,
onContentChanged,
editorId: editorId
editorId: editorId,
markImageLoaded
};
};

View File

@@ -30,6 +30,8 @@ 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();
};
@@ -68,7 +70,7 @@ export const Notebooks = ({
SearchService.prepareSearch = prepareSearch;
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
//@ts-ignore need to update typings in core to fix this
if (db.notebooks.all.length === 0) {
if (db.notebooks.all.length === 0 && !Config.isTesting) {
Walkthrough.present("notebooks");
} else {
Walkthrough.update("notebooks");

View File

@@ -31,13 +31,14 @@ import { TopicType } from "../../utils/types";
import { editorController, editorState } from "../editor/tiptap/utils";
export function toCamelCase(title: string) {
if (!title) return "";
return title.slice(0, 1).toUpperCase() + title.slice(1);
}
export function getAlias(params: Partial<NotesScreenParams>) {
if (!params) return "";
const { item } = params;
return (item as TopicType)?.alias || item?.title;
return (item as TopicType)?.alias || item?.title || "";
}
export function openMonographsWebpage() {

View File

@@ -21,7 +21,6 @@ import React, { useState } from "react";
import { Dimensions, LayoutAnimation, Platform, View } from "react-native";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import umami from "../../common/analytics";
import { SVG_Z } from "../../components/intro";
import { WelcomeNotice } from "../../components/intro/welcome";
import { Button } from "../../components/ui/button";
@@ -130,7 +129,7 @@ const AppLock = ({ route }) => {
<View
style={{
paddingHorizontal: 12,
width: DDS.isTab && welcome ? "50%" : undefined,
width: DDS.isTab && welcome ? "50%" : "100%",
alignSelf: "center"
}}
>
@@ -205,7 +204,6 @@ const AppLock = ({ route }) => {
type: "linear"
}
});
umami.pageView("/privacymode", "/welcome");
setStep(1);
}}
style={{

View File

@@ -22,6 +22,7 @@ import { AccentColorPicker, HomagePageSelector } from "./appearance";
import { AutomaticBackupsSelector } from "./backup-restore";
import DebugLogs from "./debug";
import { ConfigureToolbar } from "./editor/configure-toolbar";
import { Licenses } from "./licenses";
import SoundPicker from "./sound-picker";
import { Subscription } from "./subscription";
export const components: { [name: string]: ReactElement } = {
@@ -31,5 +32,6 @@ export const components: { [name: string]: ReactElement } = {
subscription: <Subscription />,
configuretoolbar: <ConfigureToolbar />,
"debug-logs": <DebugLogs />,
"sound-picker": <SoundPicker />
"sound-picker": <SoundPicker />,
licenses: <Licenses />
};

View File

@@ -0,0 +1,466 @@
/* eslint-disable header/header */
export const LICENSES = [
{
name: "@callstack/repack",
licenseType: "MIT",
author: "zamotany <zamotany.oss@gmail.com>",
link: "https://github.com/callstack/repack"
},
{
name: "@react-native-clipboard/clipboard",
licenseType: "MIT",
author: "M.Haris Baig <harisbaig100@gmail.com>",
link: "https://github.com/react-native-clipboard/clipboard"
},
{
name: "@react-native-community/checkbox",
licenseType: "MIT",
author: "Nicholas Lee <nicholaslee119@gmail.com>",
link: "https://github.com/react-native-community/react-native-checkbox"
},
{
name: "@react-native-community/netinfo",
licenseType: "MIT",
author: "Matt Oakes <hello@mattoakes.net>",
link: "https://github.com/react-native-netinfo/react-native-netinfo"
},
{
name: "@react-native-community/toolbar-android",
licenseType: "MIT",
author: "Connectdotz <vsun@connectdotz.com>",
link: "https://github.com/react-native-community/toolbar-android"
},
{
name: "@react-native-masked-view/masked-view",
licenseType: "MIT",
author: "Mike Nedosekin <crespo8800@gmail.com>",
link: "https://github.com/react-native-masked-view/masked-view"
},
{
name: "@react-navigation/elements",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/react-navigation/react-navigation"
},
{
name: "@react-navigation/native",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/react-navigation/react-navigation"
},
{
name: "@react-navigation/native-stack",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/react-navigation/react-navigation"
},
{
name: "@sayem314/react-native-keep-awake",
licenseType: "MIT",
author: "Sayem Chowdhury",
link: "https://github.com/sayem314/react-native-keep-awake"
},
{
name: "deprecated-react-native-prop-types",
licenseType: "MIT",
author: "n/a",
link: "git@github.com:facebookarchive/react-native-deprecated-modules"
},
{
name: "react",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/facebook/react"
},
{
name: "react-native",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/facebook/react-native"
},
{
name: "react-native-background-actions",
licenseType: "MIT",
author: "Rapsssito contact@rodrigomartin.dev",
link: "https://github.com/Rapsssito/react-native-background-actions"
},
{
name: "react-native-begin-background-task",
licenseType: "MIT",
author: "Ivan Zotov <github@ivanzotov.com>",
link: "n/a"
},
{
name: "react-native-bootsplash",
licenseType: "MIT",
author: "Mathieu Acthernoene <zoontek@gmail.com>",
link: "https://github.com/zoontek/react-native-bootsplash"
},
{
name: "react-native-config",
licenseType: "MIT",
author: "Pedro Belo",
link: "https://github.com/luggit/react-native-config"
},
{
name: "react-native-device-info",
licenseType: "MIT",
author:
"Rebecca Hughes <rebecca@learnium.net> (https://github.com/rebeccahughes)",
link: "https://github.com/react-native-device-info/react-native-device-info"
},
{
name: "react-native-document-picker",
licenseType: "MIT",
author:
"Elyx0 <elyx00@gmail.com> (https://github.com/rnmods), Vojtech Novak (hello@react-native-training.eu)",
link: "https://github.com/rnmods/react-native-document-picker"
},
{
name: "react-native-eventsource",
licenseType: "MIT",
author: "João Ribeiro jonnybgod@gmail.com",
link: "n/a"
},
{
name: "react-native-exit-app",
licenseType: "MIT",
author: "Wumke",
link: "n/a"
},
{
name: "react-native-file-viewer",
licenseType: "MIT",
author: "Vincenzo Scamporlino",
link: "git://github.com/vinzscam/react-native-file-viewer"
},
{
name: "react-native-fingerprint-scanner",
licenseType: "MIT",
author: "Hieu Van (https://github.com/hieuvp)",
link: "n/a"
},
{
name: "react-native-gesture-handler",
licenseType: "MIT",
author: "Krzysztof Magiera krzys.magiera@gmail.com",
link: "https://github.com/software-mansion/react-native-gesture-handler"
},
{
name: "react-native-get-random-values",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/LinusU/react-native-get-random-values"
},
{
name: "react-native-html-to-pdf-lite",
licenseType: "MIT",
author: "Oni Benjamin <onibenjo@gmail.com>",
link: "https://github.com/Onibenjo/react-native-html-to-pdf"
},
{
name: "react-native-iap",
licenseType: "MIT",
author: "dooboolab",
link: "https://github.com/dooboolab/react-native-iap"
},
{
name: "react-native-image-picker",
licenseType: "MIT",
author: "Johan du Toit (Johan-dutoit)",
link: "https://github.com/react-native-image-picker/react-native-image-picker"
},
{
name: "react-native-keychain",
licenseType: "MIT",
author: "Joel Arvidsson joel@oblador.se",
link: "git://github.com/oblador/react-native-keychain"
},
{
name: "react-native-mmkv-storage",
licenseType: "MIT",
author: "Ammar Ahmed for @ammarahm-ed",
link: "https://github.com/ammarahm-ed/react-native-mmkv-storage"
},
{
name: "react-native-orientation",
licenseType: "ISC",
author: "Yamill Vallecillo <yamill@gmail.com> (github.com/yamill)",
link: "n/a"
},
{
name: "react-native-privacy-snapshot",
licenseType: "MIT",
author: "Roger Chapman",
link: "n/a"
},
{
name: "react-native-reanimated",
licenseType: "MIT",
author: "Krzysztof Magiera krzys.magiera@gmail.com",
link: "https://github.com/software-mansion/react-native-reanimated"
},
{
name: "react-native-safe-area-context",
licenseType: "MIT",
author: "Janic Duplessis <janicduplessis@gmail.com>",
link: "https://github.com/th3rdwave/react-native-safe-area-context"
},
{
name: "react-native-scoped-storage",
licenseType: "MIT",
author: "Ammar Ahmed @ammarahm-ed",
link: "https://github.com/ammarahm-ed/react-native-scoped-storage"
},
{
name: "react-native-screens",
licenseType: "MIT",
author: "Krzysztof Magiera krzys.magiera@gmail.com",
link: "https://github.com/software-mansion/react-native-screens"
},
{
name: "react-native-securerandom",
licenseType: "MIT",
author: "Rob Hogan npm@rhogan.net https://github.com/robhogan",
link: "https://github.com/robhogan/react-native-securerandom"
},
{
name: "react-native-share",
licenseType: "MIT",
author: "Esteban Fuentealba efuentealba@json.cl",
link: "https://github.com/react-native-community/react-native-share"
},
{
name: "@ammarahmed/react-native-sodium",
licenseType: "n/a",
author: "n/a",
link: "https://github.com/lyubo/react-native-sodium"
},
{
name: "react-native-svg",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/react-native-community/react-native-svg"
},
{
name: "react-native-tooltips",
licenseType: "Apache License",
author: "Pranav Raj Singh Chauhan",
link: "https://github.com/prscX/react-native-tooltips"
},
{
name: "react-native-vector-icons",
licenseType: "MIT",
author: "Joel Arvidsson joel@oblador.se",
link: "git://github.com/oblador/react-native-vector-icons"
},
{
name: "react-native-webview",
licenseType: "MIT",
author: "Jamon Holmgren <jamon@infinite.red>",
link: "https://github.com/react-native-webview/react-native-webview"
},
{
name: "rn-extensions-share",
licenseType: "MIT",
author:
"Djorkaeff Alexandre djorkaeffalexandre@gmail.com http://github.com/djorkaeffalexandre",
link: "https://github.com/RocketChat/rn-extensions-share"
},
{
name: "rn-fetch-blob",
licenseType: "MIT",
author: "Joltup",
link: "https://github.com/joltup/rn-fetch-blob"
},
{
name: "react-native-gzip",
licenseType: "MIT",
author:
"ammarahm-ed <ammarahmed6506@gmail.com> (https://github.com/ammarahm-ed)",
link: "https://github.com/ammarahm-ed/react-native-gzip"
},
{
name: "@shopify/flash-list",
licenseType: "MIT",
author: "shopify",
link: "https://github.com/Shopify/flash-list"
},
{
name: "@notifee/react-native",
licenseType: "Apache-2.0",
author: "Invertase <oss@invertase.io> (http://invertase.io)",
link: "https://github.com/invertase/notifee"
},
{
name: "react-native-modal-datetime-picker",
licenseType: "MIT",
author: "Mazzarolo Matteo",
link: "https://github.com/mmazzarolo/react-native-modal-datetime-picker"
},
{
name: "@react-native-community/datetimepicker",
licenseType: "MIT",
author:
"Martijn Swaagman <mswaagman@godaddy.com> (https://github.com/swaagie)",
link: "https://github.com/react-native-community/datetimepicker"
},
{
name: "react-native-date-picker",
licenseType: "MIT",
author: "henninghall",
link: "ssh://git@github.com/henninghall/react-native-date-picker"
},
{
name: "react-native-notification-sounds",
licenseType: "MIT",
author: "Asad Iqbal asad.dsoft@gmail.com",
link: "https://github.com/saadqbal/react-native-notification-sounds"
},
{
name: "@flyerhq/react-native-link-preview",
licenseType: "MIT",
author: "Oleksandr Demchenko <alexdemchenko@yahoo.com>",
link: "https://github.com/flyerhq/react-native-link-preview#readme"
},
{
name: "@mdi/js",
licenseType: "Apache-2.0",
author: "Austin Andrews",
link: "https://github.com/Templarian/MaterialDesign-JS"
},
{
name: "absolutify",
licenseType: "MIT",
author:
"Beau Sorensen <mail@beausorensen.com> (http://github.com/sorensen)",
link: "git://github.com/sorensen/absolutify"
},
{
name: "buffer",
licenseType: "MIT",
author: "Feross Aboukhadijeh feross@feross.org https://feross.org",
link: "git://github.com/feross/buffer"
},
{
name: "dayjs",
licenseType: "MIT",
author: "iamkun",
link: "https://github.com/iamkun/dayjs"
},
{
name: "entities",
licenseType: "BSD-2-Clause",
author: "Felix Boehm <me@feedic.com>",
link: "git://github.com/fb55/entities"
},
{
name: "html-to-text",
licenseType: "MIT",
author: "Malte Legenhausen legenhausen@werk85.de",
link: "git://github.com/html-to-text/node-html-to-text"
},
{
name: "phone",
licenseType: "MIT",
author: "AfterShip",
link: "https://github.com/aftership/phone"
},
{
name: "qclone",
licenseType: "MIT",
author: "Yuriy Khomenko",
link: "https://github.com/Yuriy-Khomenko/qclone"
},
{
name: "react-native-actions-sheet",
licenseType: "MIT",
author: "ammarahm-ed",
link: "https://github.com/ammarahm-ed/react-native-actions-sheet"
},
{
name: "react-native-check-version",
licenseType: "MIT",
author: "Flexible Agency <info@flexible.agency>",
link: "n/a"
},
{
name: "react-native-drax",
licenseType: "MIT",
author: "Joe Lafiosca <joe@proxi.gg>",
link: "ssh://git@github.com/nuclearpasta/react-native-drax"
},
{
name: "react-native-image-zoom-viewer",
licenseType: "MIT",
author: "ascoders",
link: "https://github.com/ascoders/react-native-image-viewer"
},
{
name: "react-native-progress",
licenseType: "MIT",
author: "Joel Arvidsson joel@oblador.se",
link: "git://github.com/oblador/react-native-progress"
},
{
name: "react-native-qrcode-svg",
licenseType: "MIT",
author: "awesomejerry",
link: "ssh://git@github.com/awesomejerry/react-native-qrcode-svg"
},
{
name: "react-native-reanimated-material-menu",
licenseType: "MIT",
author:
"Maksim Milyutin <mmxckk@gmail.com> (https://github.com/mxck) & Ammar Ahmed <ammarahmed6506@gmail.com> (https://github.com/ammarahm-ed/)",
link: "n/a"
},
{
name: "react-native-reanimated-progress-bar",
licenseType: "MIT",
author: "Serhii Tanskiy",
link: "https://github.com/serzmerz/react-native-progress-bar"
},
{
name: "@streetwriters/showdown",
licenseType: "MIT",
author: "Estevão Santos",
link: "https://github.com/showdownjs/showdown"
},
{
name: "toggle-switch-react-native",
licenseType: "MIT",
author: "aminebenkeroum",
link: "https://github.com/aminebenkeroum/toggle-switch-react-native"
},
{
name: "url",
licenseType: "MIT",
author: "n/a",
link: "https://github.com/defunctzombie/node-url"
},
{
name: "validator",
licenseType: "MIT",
author: "Chris O'Hara <cohara87@gmail.com>",
link: "https://github.com/validatorjs/validator.js"
},
{
name: "zustand",
licenseType: "MIT",
author: "Paul Henschel",
link: "https://github.com/pmndrs/zustand"
},
{
name: "fflate",
licenseType: "MIT",
author: "Arjun Barrett <arjunbarrett@gmail.com>",
link: "https://github.com/101arrowz/fflate"
},
{
name: "timeago.js",
licenseType: "MIT",
author: "hustcc https://timeago.org",
link: "https://github.com/hustcc/timeago.js"
}
];

View File

@@ -0,0 +1,77 @@
/*
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 from "react";
import { LICENSES } from "./license-data";
import { FlatList, Linking, Platform } from "react-native";
import { PressableButton } from "../../components/ui/pressable";
import Heading from "../../components/ui/typography/heading";
import { SIZE } from "../../utils/size";
import { useThemeStore } from "../../stores/use-theme-store";
import Paragraph from "../../components/ui/typography/paragraph";
type LicenseEntry = {
name: string;
licenseType: string;
author: string;
link: string;
};
export const Licenses = () => {
const colors = useThemeStore((state) => state.colors);
const items =
Platform.OS === "ios"
? LICENSES.filter((l) => l.name.indexOf("android") === -1)
: LICENSES;
const renderItem = React.useCallback(
({ item }: { item: LicenseEntry }) => (
<PressableButton
key={item.name}
customStyle={{
alignItems: "flex-start",
justifyContent: "flex-start",
alignSelf: "flex-start",
padding: 12,
borderBottomWidth: 1,
borderBottomColor: colors.nav,
borderRadius: 0
}}
onPress={() => {
if (!item.link) return;
Linking.openURL(item.link).catch(console.log);
}}
>
<Heading size={SIZE.sm}>{item.name}</Heading>
<Paragraph>
{item.licenseType} | {item.author?.split("<")[0]}
</Paragraph>
</PressableButton>
),
[colors.nav]
);
return (
<FlatList
data={items}
style={{
width: "100%"
}}
renderItem={renderItem}
/>
);
};

View File

@@ -474,7 +474,14 @@ export const settingsGroups: SettingSection[] = [
description:
"Automatically switch to dark mode when system theme changes",
property: "useSystemTheme",
icon: "circle-half"
icon: "circle-half",
modifer: () => {
const current = SettingsService.get().useSystemTheme;
SettingsService.set({
useSystemTheme: !current
});
getColorScheme();
}
},
{
id: "enable-dark-mode",
@@ -1088,6 +1095,14 @@ export const settingsGroups: SettingSection[] = [
}
},
description: "Read our privacy policy"
},
{
id: "licenses",
name: "Open source Licenses",
type: "screen",
component: "licenses",
description: "Open source libraries used in Notesnook",
icon: "open-source-initiative"
}
]
},

View File

@@ -0,0 +1,123 @@
/*
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 BackgroundFetch from "react-native-background-fetch";
// import { DatabaseLogger, db } from "../common/database";
// import { AppState, AppRegistry } from "react-native";
// import Notifications from "./notifications";
// import SettingsService from "./settings";
// let backgroundFetchStarted = false;
// async function start() {
// if (backgroundFetchStarted) return;
// backgroundFetchStarted = true;
// // BackgroundFetch event handler.
// const onEvent = async (taskId: string) => {
// console.log("[BackgroundFetch] task: ", taskId, AppState.currentState);
// // Do your background work...
// await onBackgroundSyncStarted();
// // IMPORTANT: You must signal to the OS that your task is complete.
// BackgroundFetch.finish(taskId);
// };
// // Timeout callback is executed when your Task has exceeded its allowed running-time.
// // You must stop what you're doing immediately BackgroundFetch.finish(taskId)
// const onTimeout = async (taskId: string) => {
// console.warn("[BackgroundFetch] TIMEOUT: ", taskId);
// BackgroundFetch.finish(taskId);
// };
// // Initialize BackgroundFetch only once when component mounts.
// const status = await BackgroundFetch.configure(
// {
// minimumFetchInterval: 15,
// enableHeadless: true,
// startOnBoot: true,
// stopOnTerminate: false,
// requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY
// },
// onEvent,
// onTimeout
// );
// DatabaseLogger.info(`[BackgroundFetch] configure status: ${status}`);
// console.log(`[BackgroundFetch] configure status: ${status}`);
// }
// const task = async (event: { taskId: string; timeout: boolean }) => {
// // Get task id from event {}:
// const taskId = event.taskId;
// const isTimeout = event.timeout; // <-- true when your background-time has expired.
// if (isTimeout) {
// console.log("[BackgroundFetch] Headless TIMEOUT:", taskId);
// BackgroundFetch.finish(taskId);
// return;
// }
// DatabaseLogger.info(
// "[BackgroundFetch HeadlessTask] start: " + taskId + AppState.currentState
// );
// await onBackgroundSyncStarted();
// BackgroundFetch.finish(taskId);
// };
// BackgroundFetch.registerHeadlessTask(task);
// async function onBackgroundSyncStarted() {
// try {
// DatabaseLogger.info("Background Sync" + "start");
// await db.init();
// const user = await db.user?.getUser();
// if (user) {
// await db.sync(true, false);
// }
// await Notifications.setupReminders();
// DatabaseLogger.info("Background Sync" + "end");
// } catch (e) {
// DatabaseLogger.error(e as Error);
// console.log("Background Sync Error", (e as Error).message);
// }
// }
// const onBoot = async () => {
// try {
// DatabaseLogger.info("BOOT TASK STARTED");
// await db.init();
// await Notifications.setupReminders();
// SettingsService.init();
// if (SettingsService.get().notifNotes) {
// Notifications.pinQuickNote(false);
// }
// DatabaseLogger.info("BOOT TASK COMPLETE");
// } catch (e) {
// console.log(e);
// }
// };
// const registerHeadlessTask = () =>
// AppRegistry.registerHeadlessTask(
// "com.streetwriters.notesnook.BOOT_TASK",
// () => {
// return onBoot;
// }
// );
// export const BackgroundSync = {
// start,
// registerHeadlessTask
// };
export {};

View File

@@ -85,7 +85,7 @@ async function save(path, data, fileName, extension) {
uri = await ScopedStorage.writeFile(
path,
data,
fileName + `.${extension}`,
`${fileName}.${extension}`,
MIMETypes[extension],
extension === "pdf" ? "base64" : "utf8",
false
@@ -124,7 +124,6 @@ async function exportAs(type, note, bulk) {
case "pdf":
{
let html = await makeHtml(note);
console.log(html);
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
});
@@ -197,6 +196,19 @@ function zipsync(results) {
return Buffer.from(data.buffer).toString("base64");
}
function getUniqueFileName(fileName, results) {
const chunks = fileName.split(".");
const ext = chunks.pop();
const name = chunks.join(".");
let resolvedName = fileName;
let count = 0;
while (results[resolvedName]) {
resolvedName = `${name}${++count}.${ext}`;
}
return resolvedName;
}
/**
*
* @param {"txt" | "pdf" | "md" | "html"} type
@@ -212,14 +224,12 @@ async function bulkExport(notes, type, callback) {
let note = notes[i];
if (note.locked) continue;
let result = await exportAs(type, note);
let fileName = sanitizeFilename(note.title + Date.now(), {
let fileName = sanitizeFilename(note.title, {
replacement: "_"
});
if (result) {
results[fileName + `.${type}`] = Buffer.from(
result,
type === "pdf" ? "base64" : "utf-8"
);
results[getUniqueFileName(fileName + `.${type}`, results)] =
Buffer.from(result, type === "pdf" ? "base64" : "utf-8");
}
callback(`${i + 1}/${notes.length}`);
} catch (e) {

View File

@@ -17,6 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import { verifyUser } from "../screens/settings/functions";
import { useMessageStore } from "../stores/use-message-store";
import {
eOpenLoginDialog,
@@ -25,9 +27,6 @@ import {
} from "../utils/events";
import { eSendEvent } from "./event-manager";
import PremiumService from "./premium";
import { verifyUser } from "../screens/settings/functions";
import { Platform } from "react-native";
import umami from "../common/analytics";
import SettingsService from "./settings";
const rateAppMessage = {
@@ -82,7 +81,6 @@ const loginMessage = {
message: "You are not logged in",
actionText: "Login to encrypt and sync notes",
onPress: () => {
umami.pageView("/signup", "/welcome/home");
eSendEvent(eOpenLoginDialog);
},
data: {},

View File

@@ -41,10 +41,10 @@ import { DDS } from "./device-detection";
import { eSendEvent } from "./event-manager";
import SettingsService from "./settings";
import { useSettingStore } from "../stores/use-setting-store";
import ReminderNotify from "../components/sheets/reminder-notify";
import { sleep } from "../utils/time";
import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { presentDialog } from "../components/dialog/functions";
export type Reminder = {
id: string;
@@ -97,6 +97,7 @@ const onEvent = async ({ type, detail }: Event) => {
return;
}
if (type === EventType.PRESS) {
notifee.decrementBadgeCount();
if (notification?.data?.type === "quickNote") return;
editorState().movedAway = false;
MMKV.removeItem("appState");
@@ -105,6 +106,8 @@ const onEvent = async ({ type, detail }: Event) => {
if (notification?.data?.type === "reminder") {
const reminder = db.reminders?.reminder(notification.id?.split("_")[0]);
await sleep(1000);
const ReminderNotify =
require("../components/sheets/reminder-notify").default;
ReminderNotify.present(reminder);
}
@@ -125,8 +128,11 @@ const onEvent = async ({ type, detail }: Event) => {
}
if (type === EventType.ACTION_PRESS) {
notifee.decrementBadgeCount();
switch (pressAction?.id) {
case "REMINDER_SNOOZE": {
await db.init();
await db.notes?.init();
const reminder = db.reminders?.reminder(
notification?.id?.split("_")[0]
);
@@ -145,6 +151,8 @@ const onEvent = async ({ type, detail }: Event) => {
break;
}
case "REMINDER_DISABLE": {
await db.init();
await db.notes?.init();
const reminder = db.reminders?.reminder(
notification?.id?.split("_")[0]
);
@@ -160,6 +168,8 @@ const onEvent = async ({ type, detail }: Event) => {
break;
}
case "UNPIN": {
await db.init();
await db.notes?.init();
remove(notification?.id as string);
const reminder = db.reminders?.reminder(
notification?.id?.split("_")[0]
@@ -254,21 +264,25 @@ async function scheduleNotification(
try {
const { title, description, priority } = reminder;
await clearAllPendingTriggersForId(reminder.id);
await remove(reminder.id);
if (reminder.disabled) return;
if (reminder.disabled) {
remove(reminder.id);
return;
}
const triggers = await getTriggers(reminder);
console.log(triggers);
if (!triggers && reminder.mode === "permanent") {
displayNotification({
id: reminder.id,
title: title,
message: description || "",
ongoing: true,
subtitle: description || "",
actions: ["UNPIN"]
});
if (reminder.mode === "permanent") {
const notifications = await get();
const pinned = notifications.findIndex((i) => i.id === reminder.id) > -1;
if (!pinned) {
displayNotification({
id: reminder.id,
title: title,
message: description || "",
ongoing: true,
subtitle: description || "",
actions: ["UNPIN"]
});
}
return;
}
await setupIOSCategories();
@@ -320,6 +334,7 @@ async function scheduleNotification(
id: "default",
mainComponent: "notesnook"
},
badgeCount: 1,
actions: androidActions,
sound: notificationSound?.url,
style: !description
@@ -332,6 +347,7 @@ async function scheduleNotification(
ios: {
interruptionLevel: "active",
criticalVolume: 1.0,
badgeCount: 1,
critical:
reminder.priority === "silent" || reminder.priority === "urgent"
? false
@@ -370,7 +386,8 @@ async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") {
case "default":
return await notifee.createChannel({
id: "com.streetwriters.notesnook",
name: "Default"
name: "Default",
vibration: false
});
case "silent":
return await notifee.createChannel({
@@ -419,6 +436,7 @@ async function displayNotification({
id?: string;
}) {
if (!(await checkAndRequestPermissions())) return;
try {
await notifee.displayNotification({
id: id,
@@ -430,9 +448,14 @@ async function displayNotification({
},
android: {
ongoing: ongoing,
smallIcon: "ic_stat_name",
localOnly: true,
channelId: await getChannelId("default"),
autoCancel: false,
pressAction: {
id: "default",
mainComponent: "notesnook"
},
actions: actions?.map((action) => ({
pressAction: {
id: action
@@ -460,7 +483,30 @@ async function displayNotification({
}
}
async function checkAndRequestPermissions() {
function openSettingsDialog(context: any) {
return new Promise((resolve) => {
presentDialog({
title: "Notifications disabled",
paragraph: `Reminders cannot be set because notifications have been disabled from app settings. If you want to keep receiving reminder notifications, enable notifications for Notesnook from app settings.`,
positiveText: Platform.OS === "ios" ? undefined : "Open settings",
negativeText: Platform.OS === "ios" ? "Close" : "Cancel",
positivePress:
Platform.OS === "ios"
? undefined
: () => {
resolve(true);
},
onClose: () => {
resolve(false);
},
context: context
});
});
}
async function checkAndRequestPermissions(
promptUser?: boolean
): Promise<boolean> {
let permissionStatus = await notifee.getNotificationSettings();
if (Platform.OS === "android") {
if (
@@ -475,16 +521,28 @@ async function checkAndRequestPermissions() {
await notifee.openAlarmPermissionSettings();
}
permissionStatus = await notifee.getNotificationSettings();
if (
permissionStatus.authorizationStatus === AuthorizationStatus.AUTHORIZED &&
permissionStatus.android.alarm === 1
)
return true;
if (promptUser) {
if (await openSettingsDialog("local")) {
await notifee.openNotificationSettings();
return false;
}
}
return false;
} else {
permissionStatus = await notifee.requestPermission();
if (permissionStatus.authorizationStatus === AuthorizationStatus.AUTHORIZED)
return true;
if (promptUser) {
await openSettingsDialog("local");
}
return false;
}
}
@@ -733,6 +791,9 @@ async function setupReminders(checkNeedsScheduling = false) {
const triggers = await notifee.getTriggerNotifications();
for (const reminder of reminders) {
if (reminder.mode === "permanent") {
await scheduleNotification(reminder);
}
const pending = triggers.filter((t) =>
t.notification.id?.startsWith(reminder.id)
);

View File

@@ -19,12 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Dimensions, PlatformOSType } from "react-native";
import Config from "react-native-config";
import { Sound } from "react-native-notification-sounds";
import { initialWindowMetrics } from "react-native-safe-area-context";
import { FileType } from "react-native-scoped-storage";
import create, { State } from "zustand";
import { ACCENT } from "../utils/color-scheme";
import { initialWindowMetrics } from "react-native-safe-area-context";
import { Reminder } from "../services/notifications";
import { Sound } from "react-native-notification-sounds";
import { ACCENT } from "../utils/color-scheme";
export type Settings = {
showToolbarOnTop?: boolean;
@@ -123,7 +123,7 @@ export const useSettingStore = create<SettingStore>((set) => ({
screenshotMode: true,
privacyScreen: false,
appLockMode: "none",
telemetry: true,
telemetry: false,
notebooksListMode: "normal",
notesListMode: "normal",
devMode: false,

View File

@@ -47,8 +47,9 @@ export const useUserStore = create<UserStore>((set) => ({
verifyUser: false,
setUser: (user) => set({ user: user }),
setPremium: (premium) => set({ premium: premium }),
setSyncing: (syncing, status = SyncStatus.Passed) =>
set({ syncing: syncing, lastSyncStatus: status }),
setSyncing: (syncing, status = SyncStatus.Passed) => {
set({ syncing: syncing, lastSyncStatus: status });
},
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
setVerifyUser: (verified) => set({ verifyUser: verified }),
lastSyncStatus: SyncStatus.Never

View File

@@ -51,7 +51,7 @@ var illegalRe = /[/?<>\\:*|"]/g;
var reservedRe = /^\.+$/;
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
var windowsTrailingRe = /[. ]+$/;
var whitespace = /\W+/g;
var whitespace = /\s+/g;
function sanitize(input, replacement) {
if (typeof input !== "string") {

View File

@@ -25,17 +25,18 @@ import {
tapByText,
createNote,
prepare,
visibleById,
notVisibleById,
navigate,
elementByText,
sleep
sleep,
notVisibleByText
} from "./utils";
async function createNotebook(
title = "Notebook 1",
description = true,
topic = true
topic = true,
topicCount = 1
) {
await tapByText("Add your first notebook");
await elementById(notesnook.ids.dialogs.notebook.inputs.title).typeText(
@@ -47,10 +48,12 @@ async function createNotebook(
).typeText(`Description of ${title}`);
}
if (topic) {
await elementById(notesnook.ids.dialogs.notebook.inputs.topic).typeText(
"Topic"
);
await tapById("topic-add-button");
for (let i = 1; i <= topicCount; i++) {
await elementById(notesnook.ids.dialogs.notebook.inputs.topic).typeText(
i === 1 ? "Topic" : "Topic " + i
);
await tapById("topic-add-button");
}
}
await tapByText("Create notebook");
await sleep(500);
@@ -67,7 +70,7 @@ describe("NOTEBOOKS", () => {
it("Create a notebook with title only", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", false, false);
await sleep(500);
@@ -79,7 +82,7 @@ describe("NOTEBOOKS", () => {
it("Create a notebook title & description", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, false);
await sleep(500);
@@ -91,7 +94,6 @@ describe("NOTEBOOKS", () => {
it("Create a notebook with description and topics", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", false, false);
await sleep(500);
@@ -104,7 +106,6 @@ describe("NOTEBOOKS", () => {
await prepare();
let note = await createNote();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, true);
await sleep(500);
@@ -121,7 +122,6 @@ describe("NOTEBOOKS", () => {
it("Add new topic to notebook", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, false);
await sleep(500);
@@ -138,7 +138,6 @@ describe("NOTEBOOKS", () => {
it("Edit topic", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, true);
await sleep(500);
@@ -157,7 +156,6 @@ describe("NOTEBOOKS", () => {
it("Add new note to topic", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, true);
await sleep(500);
@@ -170,8 +168,6 @@ describe("NOTEBOOKS", () => {
it("Remove note from topic", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, true);
await sleep(500);
await device.pressBack();
@@ -184,31 +180,59 @@ describe("NOTEBOOKS", () => {
await notVisibleById(note.title);
});
it("Add/Remove note to notebook from home", async () => {
it.only("Add/Remove note to notebook from home", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", true, true);
await createNotebook("Notebook 1", true, true, 3);
await sleep(500);
await device.pressBack();
await sleep(500);
await navigate("Notes");
await createNote();
console.log("ADD TO A SINGLE TOPIC");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await sleep(500);
await tapByText("Notebook 1");
await tapByText("Topic");
await visibleById("icon-check");
await tapByText("Topic");
await notVisibleById("icon-check");
await tapByText("Save");
await sleep(300);
await visibleByText("Notebook 1 Topic");
console.log("MOVE FROM ONE TOPIC TO ANOTHER");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapByText("Notebook 1");
await tapByText("Topic 2");
await tapByText("Save");
await visibleByText("Notebook 1 Topic 2");
console.log("REMOVE FROM TOPIC");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapByText("Notebook 1");
await tapByText("Topic 2");
await tapByText("Save");
await sleep(300);
await notVisibleByText("Notebook 1 Topic 2");
console.log("MOVE TO MULTIPLE TOPICS");
await tapById(notesnook.listitem.menu);
await tapById("icon-Add to notebook");
await tapByText("Notebook 1");
await elementByText("Topic").longPress();
await visibleByText("Reset selection");
await tapByText("Topic 2");
await tapByText("Topic 3");
await tapByText("Save");
await sleep(300);
await tapById(notesnook.listitem.menu);
await visibleByText("Topic");
await visibleByText("Topic 2");
await visibleByText("Topic 3");
});
it("Edit notebook title, description and add a topic", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook();
await sleep(500);
@@ -241,7 +265,7 @@ describe("NOTEBOOKS", () => {
it("Move notebook to trash", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", false, false);
await sleep(500);
@@ -261,7 +285,7 @@ describe("NOTEBOOKS", () => {
await prepare();
let note = await createNote();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", false, true);
await sleep(500);
@@ -284,7 +308,7 @@ describe("NOTEBOOKS", () => {
await prepare();
let note = await createNote();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", false, true);
await sleep(500);
@@ -306,7 +330,7 @@ describe("NOTEBOOKS", () => {
it("Pin notebook to side menu", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1", false, false);
await sleep(500);
@@ -323,7 +347,7 @@ describe("NOTEBOOKS", () => {
it("Pin topic to side menu", async () => {
await prepare();
await navigate("Notebooks");
await tapByText("Skip introduction");
await sleep(500);
await createNotebook("Notebook 1");
await sleep(500);

View File

@@ -139,6 +139,10 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def fdroidBuild() {
return project.hasProperty("fdroidBuild") && project.fdroidBuild == "true"
}
def getNpmVersion() {
def inputFile = file("$rootDir/../../package.json")
def jsonPackage = new JsonSlurper().parseText(inputFile.text)
@@ -160,7 +164,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2023
versionCode 2036
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -280,18 +284,21 @@ android {
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
if (fdroidBuild()) {
output.versionCodeOverride =
defaultConfig.versionCode * 5 + versionCodes.get(abi)
println("Fdroid Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
} else {
println("Version code: ${output.versionCodeOverride} for abi ${versionCodes.get(abi)}");
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
}
dependencies {

View File

@@ -54,4 +54,7 @@
# Wix
-keep class org.apache.commons.lang3.** { *; }
-keep class org.apache.commons.io.** { *; }
-keep class org.apache.commons.io.** { *; }
# Background fetch
-keep class com.transistorsoft.rnbackgroundfetch.HeadlessTask { *; }

View File

@@ -130,6 +130,8 @@
android:stopWithTask="false" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
<service android:name="com.streetwriters.notesnook.BootTaskService" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
@@ -150,22 +152,13 @@
android:resource="@xml/file_viewer_provider_paths" />
</provider>
<!-- Change the value to true to enable pop-up for in foreground on receiving remote notifications (for prevent duplicating while showing local notifications set this to false) -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_foreground"
android:value="false"/>
<!-- Change the resource name to your App's accent color - or any other color you want -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_color"
android:resource="@android:color/white"/> <!-- or @android:color/{name} to use a standard color -->
<receiver android:exported="true" android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
<receiver android:exported="true" android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<receiver android:exported="true" android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>
<!-- <receiver android:exported="true" android:name=".BootRecieverService" >-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.BOOT_COMPLETED" />-->
<!-- <action android:name="android.intent.action.QUICKBOOT_POWERON" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
</application>

View File

@@ -0,0 +1,48 @@
package com.streetwriters.notesnook;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.app.ActivityManager;
import com.facebook.react.HeadlessJsTaskService;
import java.util.List;
public class BootRecieverService extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!isAppOnForeground((context))) {
/**
We will start our service and send extra info about
network connections
**/
Intent serviceIntent = new Intent(context, BootTaskService.class);
context.startService(serviceIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}
private boolean isAppOnForeground(Context context) {
/**
We need to check if app is in foreground otherwise the app will crash.
http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
**/
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses =
activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
final String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance ==
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,20 @@
package com.streetwriters.notesnook;
import android.content.Intent;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;
public class BootTaskService extends HeadlessJsTaskService {
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
return new HeadlessJsTaskConfig(
"com.streetwriters.notesnook.BOOT_TASK",
Arguments.createMap(),
30000, // timeout for the task
false // optional: defines whether or not the task is allowed in foreground. Default is false
);
}
}

View File

@@ -69,5 +69,8 @@ allprojects {
maven { url 'https://www.jitpack.io' }
// maven {
// url("${project(':react-native-background-fetch').projectDir}/libs")
// }
}
}

View File

@@ -42,4 +42,7 @@ newArchEnabled=false
# V8 Cache Mode Config
# v8.cacheMode=none
# v8.android.tools.dir=/home/ammarahm-ed/Repos/notesnook-mobile/node_modules/v8-android-jit-nointl/dist/tools/android
# v8.android.tools.dir=/home/ammarahm-ed/Repos/notesnook-mobile/node_modules/v8-android-jit-nointl/dist/tools/android
# fdroid
fdroidBuild=false

View File

@@ -1,8 +1,9 @@
- Cross platform Reminders 🔔
- Multi-factor auth is enabled by default for all users to improve login security
- Now you can change account email from Settings -> Account Settings -> Change email
- Added options to disable sync, auto-sync & real-time sync from settings.
- Improved PDF exports styling
- Many bug fixes and improvements
- You can not sort task list items
- Editor performance with images has been improved a lot
- Fixed issue with images not loading in editor during real-time sync
- Fix app not opening from notifications
- Added oss licenses
- Fix cursor goes behind keyboard on some devices
- Many other bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -1,60 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
global.Buffer = require('buffer').Buffer;
//import { ScriptManager, Script } from '@callstack/repack/client';
import '../app/common/logger/index';
import { DOMParser } from './worker.js';
// class DOM {
// static domparser;
// parseFromString(markupLanguage, mimeType, globals) {
// return DOM.domparser?.parseFromString(markupLanguage, mimeType, globals);
// }
// static async prepare() {
// if (!DOM.domparser) {
// let module = await import('./worker.js');
// DOM.domparser = new module.DOMParser();
// }
// }
// }
global.DOMParser = DOMParser;
// try {
// const shared = ScriptManager.shared;
// } catch (e) {
// new ScriptManager({
// resolve: async (scriptId, caller) => {
// if (__DEV__) {
// return {
// url: Script.getDevServerURL(scriptId),
// cache: false
// };
// }
// return {
// url: Script.getFileSystemURL(scriptId)
// };
// }
// });
// }
if (__DEV__) {
// ScriptManager.shared.on('resolving', (...args) => {
// console.log('DEBUG/resolving', ...args);
// });
// ScriptManager.shared.on('resolved', (...args) => {
// console.log('DEBUG/resolved', ...args);
// });
// ScriptManager.shared.on('prefetching', (...args) => {
// console.log('DEBUG/prefetching', ...args);
// });
// ScriptManager.shared.on('loading', (...args) => {
// console.log('DEBUG/loading', ...args);
// });
// ScriptManager.shared.on('loaded', (...args) => {
// console.log('DEBUG/loaded', ...args);
// });
// ScriptManager.shared.on('error', (...args) => {
// console.log('DEBUG/error', ...args);
// });
}

View File

@@ -1,13 +1,14 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import './globals.js';
import 'react-native-get-random-values';
import 'react-native-gesture-handler';
import React from 'react';
import { AppRegistry, LogBox, Platform, UIManager } from 'react-native';
import Config from 'react-native-config';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import appJson from './app.json';
import Notifications from '../app/services/notifications';
import Notifications from "../app/services/notifications";
Notifications.init();
const appName = appJson.name;
if (Config.isTesting) {
Date.prototype.toLocaleString = () => 'XX-XX-XX';
@@ -20,7 +21,7 @@ if (__DEV__) {
LogBox.ignoreAllLogs();
}
let NotesnookShare;
Notifications.init();
let QuickNoteIOS;
const AppProvider = () => {

View File

@@ -1083,7 +1083,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2023;
CURRENT_PROJECT_VERSION = 2028;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1157,7 +1157,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.4.5;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1187,7 +1187,7 @@
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2023;
CURRENT_PROJECT_VERSION = 2028;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1260,7 +1260,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.4.5;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1418,7 +1418,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2023;
CURRENT_PROJECT_VERSION = 2028;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1430,7 +1430,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.4.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1460,7 +1460,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2023;
CURRENT_PROJECT_VERSION = 2028;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1472,7 +1472,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.4.5;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1501,7 +1501,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2023;
CURRENT_PROJECT_VERSION = 2028;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1575,7 +1575,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.4.5;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1605,7 +1605,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2023;
CURRENT_PROJECT_VERSION = 2028;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1679,7 +1679,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.0;
MARKETING_VERSION = 2.4.5;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -7,6 +7,7 @@
#import <React/RCTAppSetupUtils.h>
#import <React/RCTLinkingManager.h>
#import "RNShortcuts.h"
//#import <TSBackgroundFetch/TSBackgroundFetch.h>
#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
@@ -62,6 +63,7 @@ RCTBridge *bridge;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
//[[TSBackgroundFetch sharedInstance] didFinishLaunching];
return YES;
}

View File

@@ -4,6 +4,7 @@
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.transistorsoft.fetch</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
<key>CFBundleDevelopmentRegion</key>

View File

@@ -254,6 +254,8 @@ PODS:
- React
- react-native-image-picker (4.1.2):
- React-Core
- react-native-image-resizer (3.0.5):
- React-Core
- react-native-keep-awake (1.1.0):
- React-Core
- react-native-mmkv-storage (0.8.0):
@@ -271,7 +273,7 @@ PODS:
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-sodium (1.2.0):
- react-native-sodium (1.3.0):
- React
- react-native-webview (11.23.1):
- React-Core
@@ -369,10 +371,10 @@ PODS:
- React-Core
- RNKeychain (4.0.5):
- React
- RNNotifee (7.3.1):
- RNNotifee (7.4.3):
- React-Core
- RNNotifee/NotifeeCore (= 7.3.1)
- RNNotifee/NotifeeCore (7.3.1):
- RNNotifee/NotifeeCore (= 7.4.3)
- RNNotifee/NotifeeCore (7.4.3):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React-Core
@@ -459,6 +461,7 @@ DEPENDENCIES:
- react-native-gzip (from `../../node_modules/react-native-gzip`)
- react-native-html-to-pdf-lite (from `../../node_modules/react-native-html-to-pdf-lite`)
- react-native-image-picker (from `../../node_modules/react-native-image-picker`)
- "react-native-image-resizer (from `../../node_modules/@bam.tech/react-native-image-resizer`)"
- "react-native-keep-awake (from `../../node_modules/@sayem314/react-native-keep-awake`)"
- react-native-mmkv-storage (from `../../node_modules/react-native-mmkv-storage`)
- "react-native-netinfo (from `../../node_modules/@react-native-community/netinfo`)"
@@ -578,6 +581,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-html-to-pdf-lite"
react-native-image-picker:
:path: "../../node_modules/react-native-image-picker"
react-native-image-resizer:
:path: "../../node_modules/@bam.tech/react-native-image-resizer"
react-native-keep-awake:
:path: "../../node_modules/@sayem314/react-native-keep-awake"
react-native-mmkv-storage:
@@ -714,13 +719,14 @@ SPEC CHECKSUMS:
react-native-gzip: 02f9968afa759e189f0414d41f8f4a951a86b4f1
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
react-native-keep-awake: acbee258db16483744910f0da3ace39eb9ab47fd
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
react-native-sodium: 460beee758415bd72cd8d820820fae0bc4076f07
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
@@ -748,7 +754,7 @@ SPEC CHECKSUMS:
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNNotifee: 2fab820e17613db15a1f836053ee3b87af7e96b9
RNNotifee: 5dfb0c5783ddb3da47b39e75d06cc7e748d6ca39
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d

View File

@@ -43,7 +43,7 @@
"react-native-screens": "^3.13.1",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^7.2.0",
"@ammarahmed/react-native-sodium": "1.2.0",
"@ammarahmed/react-native-sodium": "1.3.0",
"react-native-svg": "^12.3.0",
"react-native-tooltips": "^1.0.3",
"react-native-vector-icons": "^9.0.0",
@@ -52,11 +52,12 @@
"rn-fetch-blob": "^0.12.0",
"react-native-gzip":"1.0.0",
"@shopify/flash-list":"1.4.0",
"@ammarahmed/notifee-react-native": "7.3.1",
"@ammarahmed/notifee-react-native": "7.4.3",
"react-native-modal-datetime-picker":"14.0.0",
"@react-native-community/datetimepicker":"6.6.0",
"react-native-date-picker": "4.2.6",
"react-native-notification-sounds": "0.5.5"
"react-native-notification-sounds": "0.5.5",
"@bam.tech/react-native-image-resizer": "3.0.5"
},
"devDependencies": {
"@babel/core": "^7.12.9",

View File

@@ -1,5 +1,5 @@
const isGithubRelease = process.env.GITHUB_RELEASE;
const isGithubRelease = false;
const config = {
commands: require('@callstack/repack/commands'),
project: {

View File

@@ -1,8 +1,10 @@
- Cross platform Reminders 🔔
- Multi-factor auth is enabled by default for all users to improve login security
- Now you can change account email from Settings -> Account Settings -> Change email
- Added options to disable sync, auto-sync & real-time sync from settings.
- Improved PDF exports styling
- Many bug fixes and improvements
- You can not sort task list items
- Editor performance with images has been improved a lot
- Fixed issue with images not loading in editor during real-time sync
- Fix app not opening from notifications
- Added oss licenses
- Fix cursor goes behind keyboard on some devices
- Fix 2FA sheet does not show up sometimes after entering email
- Many other bug fixes and improvements
Thank you for using Notesnook!
Thank you for using Notesnook!

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "2.3.0",
"version": "2.4.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "2.3.0",
"version": "2.4.4",
"license": "GPL-3.0-or-later",
"workspaces": [
"native/",
@@ -60,8 +60,9 @@
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"dependencies": {
"@ammarahmed/notifee-react-native": "7.3.1",
"@ammarahmed/react-native-sodium": "1.2.0",
"@ammarahmed/notifee-react-native": "7.4.3",
"@ammarahmed/react-native-sodium": "1.3.0",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.0.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
@@ -154,17 +155,17 @@
}
},
"node_modules/@ammarahmed/notifee-react-native": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.3.1.tgz",
"integrity": "sha512-kzBDw2NAt9kGD0CJ193JME4bS4uQolY+UubGiTnJwHfaLH7dFNju6RC15/u8tzG59ebDuCalkMLr+rAONtg2Rw==",
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.3.tgz",
"integrity": "sha512-7jz91BZLxsz3v2nOPSnUf0ULa/k+KtkyX8zN2ijeluLHSQ5fjSd5jQ+u+4+Rcja0g0xQze7fpg26q6lA6F2j1g==",
"peerDependencies": {
"react-native": "*"
}
},
"node_modules/@ammarahmed/react-native-sodium": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.2.0.tgz",
"integrity": "sha512-KOVTeQMIeo8BXHBABIlYDj9QhhcrVzqxaLRra2gg2pB08V3Nq/fPPVexRf0Ir8q7vChd1+gzEFWjMYlvTIssQg=="
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.3.0.tgz",
"integrity": "sha512-Z0wjclwl69QttV6kupzY4bHrRX4ijjZMhjhy1P+WodNCrJpSnG4hI1Jne0hjKi0ghajuFZi+yPB8rjnDvsgQjA=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
@@ -2131,6 +2132,15 @@
"node": ">=6.9.0"
}
},
"node_modules/@bam.tech/react-native-image-resizer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz",
"integrity": "sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -21510,14 +21520,14 @@
},
"dependencies": {
"@ammarahmed/notifee-react-native": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.3.1.tgz",
"integrity": "sha512-kzBDw2NAt9kGD0CJ193JME4bS4uQolY+UubGiTnJwHfaLH7dFNju6RC15/u8tzG59ebDuCalkMLr+rAONtg2Rw=="
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.3.tgz",
"integrity": "sha512-7jz91BZLxsz3v2nOPSnUf0ULa/k+KtkyX8zN2ijeluLHSQ5fjSd5jQ+u+4+Rcja0g0xQze7fpg26q6lA6F2j1g=="
},
"@ammarahmed/react-native-sodium": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.2.0.tgz",
"integrity": "sha512-KOVTeQMIeo8BXHBABIlYDj9QhhcrVzqxaLRra2gg2pB08V3Nq/fPPVexRf0Ir8q7vChd1+gzEFWjMYlvTIssQg=="
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.3.0.tgz",
"integrity": "sha512-Z0wjclwl69QttV6kupzY4bHrRX4ijjZMhjhy1P+WodNCrJpSnG4hI1Jne0hjKi0ghajuFZi+yPB8rjnDvsgQjA=="
},
"@ampproject/remapping": {
"version": "2.2.0",
@@ -22856,6 +22866,11 @@
"to-fast-properties": "^2.0.0"
}
},
"@bam.tech/react-native-image-resizer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz",
"integrity": "sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ=="
},
"@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -24310,13 +24325,14 @@
"@notesnook/mobile-native": {
"version": "file:native",
"requires": {
"@ammarahmed/notifee-react-native": "7.3.1",
"@ammarahmed/react-native-sodium": "1.2.0",
"@ammarahmed/notifee-react-native": "7.4.3",
"@ammarahmed/react-native-sodium": "1.3.0",
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.16.5",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.16.5",
"@babel/preset-env": "^7.18.6",
"@babel/runtime": "^7.12.5",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.0.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.4.0",
"version": "2.4.5",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -0,0 +1,21 @@
diff --git a/node_modules/react-native-actions-sheet/dist/src/index.js b/node_modules/react-native-actions-sheet/dist/src/index.js
index 7eb4357..18bf26d 100644
--- a/node_modules/react-native-actions-sheet/dist/src/index.js
+++ b/node_modules/react-native-actions-sheet/dist/src/index.js
@@ -747,7 +747,7 @@ var ActionSheet = /** @class */ (function (_super) {
width: "100%"
},
]}>
- {this.props.ExtraOverlayComponent}
+
<FlatList testID={(_a = this.props.testIDs) === null || _a === void 0 ? void 0 : _a.scrollview} bounces={false} keyboardShouldPersistTaps={keyboardShouldPersistTaps} keyboardDismissMode={keyboardDismissMode} ref={this.scrollViewRef} scrollEventThrottle={16} overScrollMode="never" showsVerticalScrollIndicator={false} onMomentumScrollBegin={this._onScrollBegin} onScrollEndDrag={this._onScrollEnd} onMomentumScrollEnd={this._onScrollEnd} scrollEnabled={scrollable} onScrollBeginDrag={this._onScrollBeginDrag} onTouchEnd={this._onTouchEnd} onScroll={this._onScroll} scrollsToTop={false} style={[
styles.scrollView,
@@ -821,6 +821,7 @@ var ActionSheet = /** @class */ (function (_super) {
</Animated.View>
</View>);
}}/>
+ {this.props.ExtraOverlayComponent}
</Animated.View>
</Root>
</>);

View File

@@ -7,14 +7,21 @@
{
"type": "chrome",
"request": "launch",
"preLaunchTask": "StartReactApp",
"preLaunchTask": "Start Notesnook (web)",
"postDebugTask": "Terminate All Tasks",
"name": "Launch Chrome against localhost",
"name": "Debug Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"env": { "DISPLAY": ":0" },
"runtimeExecutable": "/usr/bin/brave-browser",
"runtimeArgs": ["--remote-debugging-port=9222"]
"webRoot": "${workspaceFolder}"
},
{
"type": "firefox",
"request": "launch",
"reAttach": true,
"preLaunchTask": "Start Notesnook (web)",
"postDebugTask": "Terminate All Tasks",
"name": "Debug Firefox",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}

View File

@@ -5,24 +5,26 @@
"tasks": [
{
"type": "shell",
"command": "yarn debug",
"command": "npm run debug",
"group": {
"kind": "test",
"kind": "build",
"isDefault": true
},
"dependsOn": "Terminate All Tasks",
"isBackground": true, //This prevents the launch.json to wait for the completion of the task
"problemMatcher": {
"fileLocation": "relative",
"owner": "custom", //This is not needed but, required by the problemMatcher Object
"pattern": {
"regexp": "^$" //This is not needed but, required by the problemMatcher Object
},
"background": {
"activeOnStart": true,
"beginsPattern": "Compiling...", //Signals the begin of the Task
"endsPattern": "Compiled .*" //Signals that now the initialization of the task is complete
"beginsPattern": "^$",
"endsPattern": "^You can now view (.*) in the browser.$"
}
},
"label": "StartReactApp"
"label": "Start Notesnook (web)"
},
{
"label": "Terminate All Tasks",

View File

@@ -40,7 +40,7 @@ test.afterAll(async () => {
});
test.afterEach(async () => {
await app.goto();
await app.goto(true);
});
function roundOffPrices(prices: PriceItem[]) {
@@ -142,7 +142,7 @@ test("apply coupon through url", async () => {
planPrices[plan] = roundOffPrices(await pricing.getPrices());
await app.goto();
await app.goto(true);
}
for (const key in planPrices) {

View File

@@ -289,9 +289,7 @@ test("Ctrl+Click to select/unselect notes", async ({ page }, info) => {
}
await selectedNote.contextMenu.open();
expect(
(await selectedNote.contextMenu.title()) === (await selectedNote.getTitle())
).toBeTruthy();
expect(await selectedNote.contextMenu.title()).toBeFalsy();
});
test("opening a note & right clicking on another note should show note properties menu", async ({
@@ -306,7 +304,5 @@ test("opening a note & right clicking on another note should show note propertie
await notesList[0].click();
await selectedNote.contextMenu.open();
expect(
(await selectedNote.contextMenu.title()) === (await selectedNote.getTitle())
).toBeTruthy();
expect(await selectedNote.contextMenu.title()).toBeFalsy();
});

View File

@@ -48,9 +48,10 @@ export class AppModel {
this.routeHeader = this.page.locator(getTestId("routeHeader"));
}
async goto() {
async goto(isLoggedIn = false) {
await this.page.goto("/");
await this.routeHeader.waitFor({ state: "visible" });
if (!isLoggedIn) await this.navigation.waitForItem("Login");
}
goBack() {
@@ -101,7 +102,9 @@ export class AppModel {
async navigateTo(title: string) {
if ((await this.getRouteHeader()) === title) return;
const item = await this.navigation.findItem(title);
await item?.click();
if (!item) throw new Error(`Could not find item to navigate to: ${title}`);
await item.click();
await this.page.waitForTimeout(1000);
}

View File

@@ -21,12 +21,15 @@ import { Page, Locator } from "@playwright/test";
import { getTestId } from "../utils";
export class ContextMenuModel {
readonly menuContainer: Locator;
readonly titleText: Locator;
constructor(private readonly page: Page) {
this.menuContainer = this.page.locator(getTestId(`menu-container`));
this.titleText = this.page.locator(getTestId(`menu-title`));
}
async title() {
if (!(await this.titleText.isVisible())) return null;
return await this.titleText.textContent();
}
@@ -35,7 +38,7 @@ export class ContextMenuModel {
button: "left" | "right" | "middle" | undefined = "right"
) {
await locator.click({ button });
await this.titleText.waitFor();
await this.menuContainer.waitFor();
}
async clickOnItem(id: string) {

View File

@@ -38,6 +38,12 @@ export class NavigationMenuModel {
}
}
waitForItem(title: string) {
return this.menu
.locator(getTestId(`navigation-item`), { hasText: title })
.waitFor();
}
async getShortcuts() {
const shortcuts: string[] = [];
for await (const item of this.iterateList()) {

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