Compare commits

...

427 Commits

Author SHA1 Message Date
Ammar Ahmed
82e8bc80ae mobile: fix dialog calling onClose after pressing positive button 2025-02-11 10:24:25 +05:00
luis-411
0517c6ce41 web: fix status-bar not visible on medium sized screens (#7493)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-08 19:31:37 +05:00
01zulfi
54d06328c1 web: add tab keyboard shortcuts (#7322)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-08 19:30:20 +05:00
Ammar Ahmed
c74677acd4 mobile: restore settings on logout (#7423) 2025-02-05 09:30:00 +05:00
01zulfi
7689e1a7c5 web: keep some configs in local storage on logout (#7436)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-05 09:29:36 +05:00
luzpaz
5ffae2a08b global: fix various typos (#7463)
Found via `codespell -q 3 -D ../dictionary.txt -S "*.patch,*.po" -L actualy,childs,modifer,ontext,ontop,ony,recieved,reciever,se,seperator,te`

Signed-off-by: Luz Paz <luzpaz@pm.me>
2025-02-05 09:28:06 +05:00
Ammar Ahmed
5fa80abdc8 mobile: update changelog 2025-02-04 19:25:16 +05:00
Ammar Ahmed
a30598fedf mobile: release v3.0.30 2025-02-04 19:23:49 +05:00
Ammar Ahmed
48ffecfb5f mobile: fix e2e tests 2025-02-04 19:23:11 +05:00
Ammar Ahmed
89fe712104 mobile: fix file size is 0 errors when downloading attachments 2025-02-04 17:36:46 +05:00
Ammar Ahmed
5dd298ee86 mobile: fix unlocking note with biometrics 2025-02-04 17:36:32 +05:00
Ammar Ahmed
275b285a8b mobile: release v3.0.29 2025-02-04 12:39:04 +05:00
Ammar Ahmed
96310a46e9 mobile: fix configure note activity not showing biometric unlock prompt 2025-02-04 12:33:05 +05:00
Ammar Ahmed
42f9bf1593 mobile: fix opening widget note on app launch 2025-02-04 12:33:05 +05:00
Ammar Ahmed
25aab6913a mobile: show progress when taking backup and allow to hide backup dialog 2025-02-04 12:32:51 +05:00
Ammar Ahmed
3bd7da68be mobile: fix crash on app launch with new tabs 2025-02-04 12:32:30 +05:00
Abdullah Atta
a218738eff web: improve unfocused window ui 2025-02-04 11:11:51 +05:00
Abdullah Atta
8bf283b3bc config: add *.patch files to .gitignore 2025-02-04 10:14:59 +05:00
luis-411
cb09430ff4 web: fix subnotebook title not updating on navigate (#7286)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-04 10:06:59 +05:00
luis-411
66d75492bb editor: fix task list stats 0/0 on app reload (#7327)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-02-04 10:04:16 +05:00
Abdullah Atta
d772d38dc7 web: fix collapsed pane expanding on app reload 2025-02-04 09:35:04 +05:00
Ammar Ahmed
46a74f9512 mobile: release v3.0.28 2025-02-03 15:45:54 +05:00
Ammar Ahmed
7df1037e3f mobile: fix realtime sync editor updates in tabs 2025-02-03 15:38:32 +05:00
Abdullah Atta
5c3136d07d ci: run actions on ubuntu-22.04 2025-02-03 14:57:30 +05:00
Abdullah Atta
6a9ddb770e web: bump version to 3.0.25 2025-02-03 14:39:44 +05:00
Abdullah Atta
f4c54fd27f web: fix crash on setting a reminder 2025-02-03 14:22:58 +05:00
Ammar Ahmed
05b12df7d7 mobile: cache password when restoring backup 2025-02-03 14:16:46 +05:00
Ammar Ahmed
dcf053eede mobile: call onClose when closing dialog 2025-02-03 14:16:46 +05:00
Abdullah Atta
5430966b02 web: fix tabs are not clickable everywhere 2025-02-03 13:25:17 +05:00
Ammar Ahmed
7271140627 mobile: fix release notes rendering 2025-02-03 13:02:06 +05:00
01zulfi
eb5ae0773b editor: fix hover styling in toolbar color buttons
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-02-03 12:36:06 +05:00
Ammar Ahmed
9661dd0c5e mobile: fix android widget bugs 2025-02-03 12:19:53 +05:00
Ammar Ahmed
8e70860918 mobile: fix attaching a file in new note editor 2025-02-03 12:19:34 +05:00
Ammar Ahmed
3540ecf4ab mobile: close sheet on backup completed 2025-02-03 12:19:17 +05:00
Ammar Ahmed
f1dba9ffc2 mobile: cleanup 2025-02-03 12:17:57 +05:00
Abdullah Atta
3f1761a540 core: fix empty note cannot be exported 2025-02-03 12:17:57 +05:00
Abdullah Atta
a25d21038a core: fix Object is not iterable error on some platforms
This happened due to [Symbol.asyncIterable] not getting
transformed when it was a method of a class. Moving it inside
another method fixes the issue.
2025-02-03 12:17:57 +05:00
Abdullah Atta
d55a6a6fc0 web: fix pinned tab not moving to the beginning 2025-02-03 10:46:24 +05:00
Abdullah Atta
e5cf902780 web: fix unnecessary rerenders of the tab strip 2025-02-03 10:01:52 +05:00
Abdullah Atta
a99996fcf6 web: fix tab drag/drop 2025-02-03 09:52:00 +05:00
Abdullah Atta
0cc9c31bc3 editor: fix Failed to execute 'collapse' on 'Selection' 2025-02-01 15:14:34 +05:00
Abdullah Atta
62da8484ad web: ignore notes in other tabs when navigating back 2025-02-01 14:26:12 +05:00
Ammar Ahmed
821b8eebaa mobile: fix tab issues 2025-02-01 13:47:27 +05:00
Ammar Ahmed
269941c297 mobile: keep all datatype classes 2025-02-01 13:47:27 +05:00
Abdullah Atta
e37ff88f1f web: fix flicker when opening a note in new tab 2025-02-01 13:38:48 +05:00
Abdullah Atta
1a1b4bc43c web: fix note already opened in another tab is not focused 2025-02-01 13:18:31 +05:00
Abdullah Atta
2c59df9fb7 web: remove unused code from action-bar 2025-02-01 12:58:53 +05:00
Abdullah Atta
4fc30cc224 web: move focus mode and editor margins to status bar 2025-02-01 12:57:55 +05:00
Abdullah Atta
9bd7d392af web: fix zoom buttons ui 2025-02-01 12:27:51 +05:00
Abdullah Atta
3bcd288bc7 web: more compact and unified action bar 2025-02-01 12:18:47 +05:00
Ammar Ahmed
3b86e51c49 mobile: fix commands 2025-01-31 15:17:45 +05:00
Abdullah Atta
c286b1d595 web: use new storage key for storing editor sessions 2025-01-31 15:15:48 +05:00
Abdullah Atta
2f6b1e6bcc web: fix rapidly clicking on back/forward buttons opened new tab 2025-01-31 15:15:48 +05:00
Abdullah Atta
abb918e179 web: fix tests 2025-01-31 15:15:48 +05:00
Abdullah Atta
dd0dc09977 web: fix note version can't be restored 2025-01-31 15:15:48 +05:00
Abdullah Atta
211ab47165 web: save selection state before switching note 2025-01-31 15:15:48 +05:00
Abdullah Atta
bc44e28e1e web: remove more usages of session.id where noteId was expected 2025-01-31 15:15:48 +05:00
Abdullah Atta
ade60e4272 web: move tab navigation keyboard shortcuts to key-map.ts 2025-01-31 15:15:48 +05:00
Abdullah Atta
3f5bfcb499 web: focus tab if note is already opened in one & reuse sessions for note 2025-01-31 15:15:48 +05:00
Abdullah Atta
782a979397 web: handle case where a new note session is in the tab history 2025-01-31 15:15:48 +05:00
Abdullah Atta
e7f1e2d533 web: add support for opening a session without focusing the tab 2025-01-31 15:15:48 +05:00
Abdullah Atta
12e5f2acc1 web: fix focus next/prev tab not working 2025-01-31 15:15:48 +05:00
Abdullah Atta
dccc79ace3 web: remove usage of session.id in places where note id is expected 2025-01-31 15:15:48 +05:00
Abdullah Atta
a9a5ba09ff web: activate a new note session on tab rehydration 2025-01-31 15:15:48 +05:00
Abdullah Atta
a6c8bbbf0a web: reactivate same session if user tries to create consecutive new notes 2025-01-31 15:15:48 +05:00
Abdullah Atta
e19e9eb96f web: get fresh note context when saving a new note 2025-01-31 15:15:48 +05:00
Ammar Ahmed
9208140b97 mobile: improve tab button 2025-01-31 15:15:48 +05:00
Ammar Ahmed
8241bb8ae5 mobile: fix opening tab for the first time 2025-01-31 15:15:48 +05:00
Ammar Ahmed
329c2e220f mobile: fix tabs 2025-01-31 15:15:48 +05:00
Abdullah Atta
140e343289 common: allow using custom session id in tab session history 2025-01-31 15:15:48 +05:00
Abdullah Atta
0d99367a93 web: fix title not updating in real time sync 2025-01-31 15:15:48 +05:00
Abdullah Atta
b802b3e165 web: fix diff session not opening in editor 2025-01-31 15:15:48 +05:00
Ammar Ahmed
46583e12d9 mobile: update tabs 2025-01-31 15:15:48 +05:00
Abdullah Atta
c9a3f31e8f web: add more tests 2025-01-31 15:15:48 +05:00
Abdullah Atta
53f68aaf81 web: add tests for new tab button 2025-01-31 15:15:48 +05:00
Abdullah Atta
f31df9a6cc web: add tests for new tabs 2025-01-31 15:15:48 +05:00
Abdullah Atta
c812581bb9 web: add support for new tab navigation 2025-01-31 15:15:48 +05:00
Abdullah Atta
1e6e940f17 common: string tab ids, get rid of currentTab taking tab id as a parameter 2025-01-31 15:15:48 +05:00
Ammar Ahmed
9ab670d933 mobile: fix deps 2025-01-31 15:15:48 +05:00
Ammar Ahmed
c46e57418b mobile: remove unused properties 2025-01-31 15:15:48 +05:00
Ammar Ahmed
83557401f5 mobile: update tabs 2025-01-31 15:15:48 +05:00
Ammar Ahmed
2277644e89 mobile: cleanup 2025-01-31 15:15:48 +05:00
Ammar Ahmed
5eb09a1d3e mobile: cleanup 2025-01-31 15:15:48 +05:00
Ammar Ahmed
d957be7a7b mobile: fix editor header ui 2025-01-31 15:15:48 +05:00
Ammar Ahmed
44ddb49d0e editor: fix imports 2025-01-31 15:15:48 +05:00
Ammar Ahmed
24fcd5cd0c common: fix missing exports 2025-01-31 15:15:48 +05:00
Ammar Ahmed
2a34225d49 global: update deps 2025-01-31 15:15:48 +05:00
Ammar Ahmed
e81c825fc7 mobile: multi-tab support 2025-01-31 15:15:48 +05:00
Ammar Ahmed
c80286b587 mobile: tab history 2025-01-31 15:15:48 +05:00
luis-411
e85f8b60b0 editor: add field labels to add a link popup (#7097)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-01-31 10:11:59 +05:00
luis-411
5daa99d969 web: fix nav menu collapse (#7377)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-01-30 09:09:28 +05:00
01zulfi
073bb576b5 editor: fix search not resetting when closed (#7415)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-29 11:23:26 +05:00
Abdullah Atta
eaeac130df Revert "intl: update lockfile"
This reverts commit 95a20b3740.
2025-01-29 10:22:18 +05:00
Abdullah Atta
b21519c24e ci: include bundled electron app in desktop build 2025-01-29 10:22:18 +05:00
Abdullah Atta
4f9fe9b1e0 Revert "intl: update lockfile"
This reverts commit f657380ec7.
2025-01-28 14:49:12 +05:00
Abdullah Atta
f657380ec7 intl: update lockfile 2025-01-28 14:20:41 +05:00
Abdullah Atta
05b2e559d8 setup: downgrade tsup version 2025-01-28 13:33:29 +05:00
Abdullah Atta
ab21f02c2d ci: include intl, theme-builder & monograph in npm cache 2025-01-28 13:07:50 +05:00
Abdullah Atta
12b51ed72a intl: update lockfiles 2025-01-28 13:04:19 +05:00
Abdullah Atta
b672fd540c setup: generate sources for intl package 2025-01-28 12:50:42 +05:00
Abdullah Atta
95a20b3740 intl: update lockfile 2025-01-28 11:25:38 +05:00
Abdullah Atta
6f5a7a4923 global: use same version for @lingui deps everywhere 2025-01-28 10:45:04 +05:00
Ammar Ahmed
1b4d98ac0e mobile: fix strings not rendering correctly in editor 2025-01-22 15:23:56 +05:00
Ammar Ahmed
ef2ed2bba0 mobile: show toast when changing some settings 2025-01-22 14:03:52 +05:00
Ammar Ahmed
abf4612977 core: fix esm imports 2025-01-21 14:32:48 +05:00
Ammar Ahmed
9c6637908d ci: update workflow
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2025-01-21 13:58:26 +05:00
01zulfi
e1006ed501 core: add nowz and timestampz formats (#7270)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-21 13:17:08 +05:00
Ammar Ahmed
7d034f7f16 mobile: fix build errors 2025-01-21 13:01:40 +05:00
Abdullah Atta
76eec5f8bb web: do not show email confirm notice for loggedout users 2025-01-21 12:47:51 +05:00
Ammar Ahmed
698866f53d mobile: release v3.0.27 2025-01-21 12:31:37 +05:00
Abdullah Atta
21e1e73bee desktop: fix ui when native titlebar is enabled 2025-01-21 11:35:47 +05:00
Abdullah Atta
52e89c7842 web: update snapshots 2025-01-21 10:41:05 +05:00
Abdullah Atta
8b357559fa web: fix keyboard-navigation-list tests 2025-01-21 10:30:17 +05:00
Abdullah Atta
2be35fadff editor: fix tests 2025-01-21 10:14:42 +05:00
Abdullah Atta
5fe366f4f3 global: use same versions for dependencies everywhere (#7365)
* global: use same versions for dependencies everywhere

* intl: fix `Cannot find module '@lingui/macro'`

* web: fix `I18n' is not assignable to parameter` type error

* setup: log post install cmd

* setup: more logging

* web: update lockfile
2025-01-21 09:35:38 +05:00
Ammar Ahmed
a13cbadd44 feat: new widgets on android (#7311)
- Added a new widget on android that allows you to put a note on your home screen.
- Added a new widget on android that allows you to see upcoming reminders on home screen
- Fixed new note widget freezing
2025-01-20 17:17:01 +05:00
Abdullah Atta
aa993a2fce web: bump version to 3.0.24 2025-01-20 13:26:50 +05:00
01zulfi
975af5701b web: fix reveal in list (#7297)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-20 12:08:13 +05:00
luis-411
3add56cfe4 global: fix search strings (#7302)
Signed-off-by: Luis Kriner <luis@kriner.info>
2025-01-20 11:47:48 +05:00
Abdullah Atta
522ea1520d web: fix tests failing on macos 2025-01-20 11:28:26 +05:00
Ammar Ahmed
c680121eb0 mobile: always load note content in editor 2025-01-20 10:38:16 +05:00
Ammar Ahmed
a9de0696c9 mobile: fix image previews not loading on ios 2025-01-20 10:38:00 +05:00
Ammar Ahmed
4a4c52e4c8 mobile: fix material menu 2025-01-20 10:37:39 +05:00
Ammar Ahmed
92f5e04788 mobile: fix lock state detection in sheets and dialogs 2025-01-20 10:37:26 +05:00
Ammar Ahmed
0cad098551 mobile: fix selecting image from gallery when applock is on 2025-01-20 10:37:26 +05:00
Abdullah Atta
46105b2175 web: fix web app on Safari 2025-01-20 10:35:23 +05:00
Abdullah Atta
8ffbda9626 Revert "editor: update tiptap to v2.10.4 (#6872)"
This reverts commit 800f3ca5d3.
2025-01-20 10:20:15 +05:00
Ammar Ahmed
1fc55a69c9 editor: fix clamped image size is larger than editor (#7324) 2025-01-20 10:07:20 +05:00
Ammar Ahmed
c417d4df91 core: fix DATE_TIME_STRIP_REGEX (#7325) 2025-01-20 10:06:26 +05:00
01zulfi
43ccf72773 intl: fix follow casing (#7342)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-18 11:38:20 +05:00
01zulfi
a177d36571 web: treat setting item descriptions as md and convert them to html (#7343)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-18 11:37:54 +05:00
01zulfi
4428ccd3a0 web: add lock app button in status bar (#7333)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-17 11:35:13 +05:00
01zulfi
218ffbd758 intl: fix referencedIn casing (#7334)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-17 11:24:11 +05:00
01zulfi
5ace95c9d1 web: fix canceling the edit profile dialog would set name to undefined (#7338)
* escaping or canceling the dialog would set profile's name to undefined

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-17 11:21:18 +05:00
Ammar Ahmed
77a2f86119 mobile: fix trash interval picker text showing NaN 2025-01-14 12:02:16 +05:00
01zulfi
71db85458d web: add go to next/previous tab shortcut (#7109)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-02 15:13:49 +05:00
01zulfi
0be59c71de web: save image compression upload setting (#7089)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-02 15:10:58 +05:00
Abdullah Atta
2357e3de0b web: title bar underlay not showing on mac 2025-01-02 14:15:09 +05:00
Abdullah Atta
53b89a59ed web: fix integrated titlebar on windows 2025-01-02 14:15:09 +05:00
Abdullah Atta
202c9135c2 web: show exclusive titlebar in tablet/mobile mode 2025-01-02 14:15:09 +05:00
Abdullah Atta
69e6263d34 web: better integrated titlebar that looks same across platforms 2025-01-02 14:15:09 +05:00
Abdullah Atta
ebe830bbcc theme: add new borderless input variant 2025-01-02 14:15:09 +05:00
Abdullah Atta
44bb9752d2 web: improve slider ux on mobile & small screens 2025-01-02 14:15:09 +05:00
Abdullah Atta
2f6af50fe2 web: split pane now supports adding/removing panes in place 2025-01-02 14:15:09 +05:00
Ammar Ahmed
1d80ca7d3c mobile: release v3.0.26 2024-12-31 14:03:49 +05:00
Ammar Ahmed
d33baebac8 editor: fix frame around image on selection 2024-12-31 13:21:47 +05:00
Ammar Ahmed
25aaef361a mobile: throw error if db key not found in keychain 2024-12-31 12:50:38 +05:00
Abdullah Atta
f3af39a093 web: fix action bar ui on web 2024-12-30 16:12:19 +05:00
Abdullah Atta
149c2013a2 editor: fix multiple editor views being created 2024-12-30 16:05:28 +05:00
Ammar Ahmed
8fb0b6f94a mobile: fix build errors in editor-mobile 2024-12-30 15:44:57 +05:00
Ammar Ahmed
f09e5cadcf mobile: fix image and file uploads 2024-12-30 15:44:57 +05:00
Ammar Ahmed
724c69655b mobile: fix scroll jumping in realtime sync 2024-12-30 15:44:41 +05:00
Abdullah Atta
800f3ca5d3 editor: update tiptap to v2.10.4 (#6872)
* editor: update tiptap to v2.9.1

* editor: update tiptap & pkgs to 2.10.4

* editor: update test snapshots
2024-12-30 14:51:50 +05:00
Abdullah Atta
193007370a web: add experimental support for responsive pane resizing 2024-12-30 11:56:42 +05:00
Abdullah Atta
c8aff3764b web: lots of improvements 2024-12-30 11:56:42 +05:00
Abdullah Atta
d72b502977 web: fix snapping logic 2024-12-30 11:56:42 +05:00
Abdullah Atta
263c970320 web: add support for macOS like pane resizing logic 2024-12-30 11:56:42 +05:00
Abdullah Atta
aeebc9878d web: only reset size of doubled clicked pane 2024-12-30 11:56:42 +05:00
Abdullah Atta
4f37c92ecd web: add snapping support 2024-12-30 11:56:42 +05:00
Abdullah Atta
3180a88ef0 web: replace react-resizable-panels with custom implementation 2024-12-30 11:56:42 +05:00
01zulfi
6672db6ac2 editor: add markdown shortcut for checklist (#7087)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-30 11:17:18 +05:00
01zulfi
b6da62469e core: fix vault unlock event not sent when a note is unlocked (#7172)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-30 10:43:33 +05:00
Ammar Ahmed
e67e6374c3 mobile: set default widget size 2024-12-30 10:32:15 +05:00
Ammar Ahmed
ff1fc5b391 mobile: fix formatting 2024-12-30 10:32:15 +05:00
Ammar Ahmed
2f8bf191aa mobile: resizable and themeable android widget 2024-12-30 10:32:15 +05:00
Abdullah Atta
c4af2b2159 web: fix build on macos (by updating packages) 2024-12-30 10:13:40 +05:00
Abdullah Atta
690b157387 desktop: b/w when window is unfocused 2024-12-30 10:13:12 +05:00
Abdullah Atta
5a276a43a4 web: improve tab ui 2024-12-30 10:12:02 +05:00
Abdullah Atta
a364ae2e9d core: fix notes search in tags, colors & notebooks not working 2024-12-27 16:20:53 +05:00
Abdullah Atta
1e2055c81b web: fix Cannot start service: Host version "0.20.2" does not match binary version "0.23.1" on macos 2024-12-27 15:30:37 +05:00
01zulfi
31d110252c web: fix status bar size changing (#7143)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-27 11:50:40 +05:00
Ammar Ahmed
702976c6cf mobile: do not show progress when doing full auto backup 2024-12-26 12:44:54 +05:00
Ammar Ahmed
3ec8500515 mobile: add fdroid release notes 2024-12-26 12:32:27 +05:00
Ammar Ahmed
2c03420f33 mobile: release v3.0.25 2024-12-26 12:25:12 +05:00
Ammar Ahmed
868ae597ae mobile: fix ts errors 2024-12-26 12:25:12 +05:00
01zulfi
59d3e06935 intl: fix casing in legal & notebooks strings (#7142)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-26 11:29:57 +05:00
Ammar Ahmed
b296f5dd5c mobile: update export options 2024-12-24 15:59:33 +05:00
Abdullah Atta
2dc5e4fef5 docs(help): add notice on gift card page 2024-12-24 15:54:44 +05:00
Abdullah Atta
f836de3854 docs(help): clarify about expiry date on gift cards 2024-12-24 15:54:44 +05:00
Ammar Ahmed
631a591477 mobile: update ios provisioning 2024-12-24 15:31:06 +05:00
Ammar Ahmed
1bb27145ce mobile: release v3.0.24 2024-12-24 14:14:16 +05:00
Ammar Ahmed
79b89c2e85 mobile: skip migration progress during tests 2024-12-24 14:10:11 +05:00
Ammar Ahmed
ceb2a8c747 intl: fix locales data 2024-12-24 12:06:52 +05:00
Ammar Ahmed
bee06ecf18 mobile: fix multi file sharing 2024-12-24 12:06:52 +05:00
Ammar Ahmed
33c7eb5a74 mobile: fix imports 2024-12-24 12:06:52 +05:00
Abdullah Atta
2cb788712a web: fix editor toolbar scroll ui 2024-12-24 11:13:57 +05:00
Abdullah Atta
90d2338412 Revert "web: fix editor toolbar scroll styling (#7113)"
This reverts commit 5229408156.
2024-12-24 10:31:14 +05:00
01zulfi
4bebf5a7b5 editor: fix task list state & readonly bugs (#7117)
* fix task list complete state not showing on first load
* fix task list allowing paste when readonly

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-24 10:18:23 +05:00
Ammar Ahmed
68eb5023f8 mobile: update deps 2024-12-24 10:10:56 +05:00
Abdullah Atta
8692c87ed7 intl: fix resend code string 2024-12-24 10:10:56 +05:00
Abdullah Atta
ea0623c6bc web: create backup on submit when changing password 2024-12-24 10:10:56 +05:00
Abdullah Atta
b526dc4d39 core: hash old password before deriving new crypto key 2024-12-24 10:10:56 +05:00
Ammar Ahmed
76a1025f59 mobile: do not call clearSessions when changing password 2024-12-24 10:10:56 +05:00
Ammar Ahmed
85e046a379 mobile: fix error in shortcut manager 2024-12-24 10:10:56 +05:00
Ammar Ahmed
58a6bbd262 mobile: fix logout handling 2024-12-24 10:10:56 +05:00
Ammar Ahmed
07aabf9a2a core: return false if no fallback key 2024-12-24 10:10:56 +05:00
Ammar Ahmed
8c7ba6b963 mobile: fix profile not rendered on home 2024-12-24 10:10:56 +05:00
Ammar Ahmed
d125e0f6d9 mobile: fix encryption 2024-12-24 10:10:56 +05:00
Ammar Ahmed
43d017d64a mobile: fix code input when changing email 2024-12-24 10:10:56 +05:00
Abdullah Atta
bc3e7c6a53 web: add noop fallback methods in storage 2024-12-24 10:10:56 +05:00
Abdullah Atta
6e8610c358 core: compute usesFallbackPWHash instead of storing it
this fixes the fallback logic for already logged in users
2024-12-24 10:10:56 +05:00
Abdullah Atta
ab00645c23 web: fix recovery view button text not wrapping 2024-12-23 18:29:10 +05:00
Abdullah Atta
e041976ae0 web: fix gift cards link 2024-12-23 18:06:09 +05:00
Abdullah Atta
9d7e6b40a0 web: bump version to 3.0.23 2024-12-23 18:05:43 +05:00
Abdullah Atta
db40a648ab web: fix Cannot read properties of undefined (reading 'instance') 2024-12-23 17:53:14 +05:00
Abdullah Atta
805e60a808 web: handle NetworkError when attempting to fetch resource. 2024-12-23 17:46:53 +05:00
Abdullah Atta
8c7093189b core: add support for sorting search results
search results are sorted by date created for now until
we add proper support for sorting search results
in the ui
2024-12-23 14:30:45 +05:00
Abdullah Atta
3092696c83 docs: add docs for gift cards 2024-12-23 14:30:25 +05:00
Ammar Ahmed
c2e7f7576e mobile: redeem gift code 2024-12-23 14:30:25 +05:00
Abdullah Atta
6b4f7900ec web: show subscription provider info in subscription settings 2024-12-23 14:30:25 +05:00
Abdullah Atta
523ae5b07b intl: add other subscription providers 2024-12-23 14:30:25 +05:00
Abdullah Atta
a628b3fd92 intl: add strings for gift redeeming 2024-12-23 14:30:25 +05:00
Abdullah Atta
e9f3037129 web: add gift redeem support 2024-12-23 14:30:25 +05:00
Abdullah Atta
46d4b9dece core: add api for redeeming gift codes 2024-12-23 14:30:25 +05:00
01zulfi
c701baaa2c web: better ux when autosave is off (#6610) (#6849)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-12-23 14:30:03 +05:00
Ammar Ahmed
a798f8e280 mobile: fix intro button position 2024-12-23 13:43:57 +05:00
Abdullah Atta
baebf79046 web: fix type errors 2024-12-23 12:29:07 +05:00
Abdullah Atta
5727aa6c4d web: fix checkout discounted snapshots 2024-12-21 16:41:16 +05:00
Abdullah Atta
36cf1feff9 web: fix crash on opening sort menu 2024-12-21 16:41:04 +05:00
Abdullah Atta
91a3b0f5ed web: fix imports with nested notebooks 2024-12-21 16:15:29 +05:00
Ammar Ahmed
293219d781 mobile: fix encryption on ios 2024-12-21 14:15:56 +05:00
Ammar Ahmed
2cd4f44a92 core: fix fallback key encryption 2024-12-21 14:15:56 +05:00
Abdullah Atta
4b9a95ded7 core: add support for fallback hashing
on ios some users using passwords with multi byte characters
generates the wrong hash. This commit adds support for fallback
to allow those users to continue to login once the fix has been applied.
2024-12-21 14:15:56 +05:00
Ammar Ahmed
565b0382f9 mobile: fix encryption on ios 2024-12-21 14:15:56 +05:00
Ammar Ahmed
1e837f2a9c mobile: fix undo/redo buttons in editor 2024-12-21 14:13:07 +05:00
Ammar Ahmed
765ebbe10c mobile: allow saving empty string input 2024-12-21 14:12:24 +05:00
Ammar Ahmed
e95d1601a6 mobile: fix sharing files on android 2024-12-21 14:10:55 +05:00
Ammar Ahmed
c6dd1d1615 mobile: fix snooze time not rendered properly 2024-12-21 14:10:34 +05:00
Ammar Ahmed
bb50850995 Merge pull request #7008 from streetwriters/fix-localization-error 2024-12-21 14:10:05 +05:00
Ammar Ahmed
83083134e9 Merge branch 'master' into fix-localization-error
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-12-21 14:09:22 +05:00
01zulfi
61b8960aa5 web: fix mfa fallback string (#7123)
* also add selected style to mfa type selector button

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-20 15:55:42 +05:00
01zulfi
5229408156 web: fix editor toolbar scroll styling (#7113)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-20 12:30:08 +05:00
01zulfi
0921fef267 web: show account password label in delete vault dialog (#7085)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-11 11:30:42 +05:00
luis-411
60989359e2 theme: make link color align with theme (#7077)
Signed-off-by: Luis Kriner <luis@kriner.info>
2024-12-10 11:01:04 +05:00
luis-411
8fe381afa9 editor: prevent creation of multiple empty notes (#6922)
Signed-off-by: Luis Kriner <luis@kriner.info>
2024-12-10 10:30:19 +05:00
01zulfi
fea9c4e42b web: fix links in report issue dialog (#7071)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-10 10:28:17 +05:00
01zulfi
1d78402d5d web: fix tags not showing in locked note (#7061)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-10 09:58:13 +05:00
01zulfi
aa72c33aea webclipper: handle default case in listener (#7058)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-10 09:57:23 +05:00
01zulfi
69504a150e core: handle error in parseInternalLink (#7065)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-10 09:56:47 +05:00
dyw770
a78a2e4ae2 core: fix object-id is sometimes less than 24 chars in length (#6991) (#6994)
Signed-off-by: dyw770 <dyw770@qq.com>
2024-12-06 10:11:57 +05:00
luis-411
0ffbf83141 editor: hide edit link button when in read only mode (#7018)
Signed-off-by: Luis Kriner <luis@kriner.info>
2024-12-06 09:46:44 +05:00
01zulfi
75c346a460 docs: add README.md for web clipper (#7054)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-05 10:55:28 +05:00
01zulfi
93af79926d editor: disable spellcheck for inline code (#7036)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-05 10:49:40 +05:00
01zulfi
1b354e4bca web: hide spin buttons in mfa code input (#7048)
* they overlap with the resend button

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-05 10:26:54 +05:00
01zulfi
6d53b78f6a web: round off zoom value
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-28 09:59:32 +05:00
Ammar Ahmed
8e90adccab mobile: fix localization issues 2024-11-27 14:02:22 +05:00
Abdullah Atta
c19635b16a web: fix direct index access of strings 2024-11-27 13:14:44 +05:00
Abdullah Atta
94e4b00294 intl: fix direct access of strings in object 2024-11-27 13:14:12 +05:00
Abdullah Atta
76908b1fbd intl: generate action for when data type is unknown 2024-11-27 12:27:09 +05:00
Ammar Ahmed
7409c5382d mobile: fix localization error on searching notebook/tags 2024-11-27 10:41:52 +05:00
01zulfi
2462eb1631 monograph: remove callout headings from toc (#6875) (#6993)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-26 16:27:32 +05:00
Abdullah Atta
986ff32293 monograph: bump version to 1.2.2 2024-11-26 16:23:00 +05:00
Abdullah Atta
8f7fd9d8ee monograph: use fallback fonts when rendering og image 2024-11-26 16:22:48 +05:00
01zulfi
54d6a5c307 monograph: fix PUBLIC_URL on client
* use loader to get PUBLIC_URL

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-26 15:27:43 +05:00
Abdullah Atta
d672a43a83 monograph: bump version to 1.2.1 2024-11-26 11:58:54 +05:00
Abdullah Atta
48343e5d5c web: do not zoom if e.deltaY is 0 2024-11-25 16:26:02 +05:00
Levi Ob
0d3a8c88c8 web: fix inconsistent zoom amount when adjusting with scroll wheel. (#6990)
Zoom steps are always in increments of 10 when using a mouse wheel or trackpad.

Signed-off-by: Levi Ob <leviob.msrni@slmail.me>
2024-11-25 12:36:32 +05:00
Abdullah Atta
31367066f7 web: run prerequisite checks on app start
this gives useful errors to users if their browser doesn't support something
2024-11-25 12:33:05 +05:00
Abdullah Atta
f7c097988a web: fix cannot read property clear of undefined when pressing fix it 2024-11-25 12:31:38 +05:00
Abdullah Atta
b7f1f36090 web: fix async sqlite
async sqlite mode uses indexeddb and has some limitations like:
- running 1 query at a time
This commit uses a mutex to run all queries sequentially
2024-11-25 12:31:11 +05:00
01zulfi
8756299d80 web: always show desktop backup directory path (#6867)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-25 10:26:30 +05:00
dyw770
3525d60f8c monograph: add monograph response charset header
Signed-off-by: dyw770 <dyw770@qq.com>
2024-11-24 23:19:36 +05:00
Abdullah Atta
d08a485111 desktop: extract better-sqlite3 patching to its own module 2024-11-24 22:01:28 +05:00
Abdullah Atta
c203b035c4 monograph: bump version to 1.2.0 2024-11-24 15:38:20 +05:00
Abdullah Atta
0f4b2bdae7 monograph: fix Cannot find module '@remix-run/server-runtime' 2024-11-24 15:24:46 +05:00
Abdullah Atta
cb0bc39cd1 monograph: use bun in prod 2024-11-24 15:18:52 +05:00
Abdullah Atta
84abfa3478 monograph: speed up og image generation
this gets rid of satori and uses custom logic for drawing using @napi-rs/canvas
2024-11-24 15:18:52 +05:00
thecodrr
a40a48e2c1 server(theme): add installs.json to gitignore 2024-11-23 17:48:34 +00:00
Abdullah Atta
124499cabb monograph: use sharp output as buffer instead of stream 2024-11-23 22:23:42 +05:00
Ammar Ahmed
e874d1efd4 mobile: changelog 2024-11-23 22:20:05 +05:00
Ammar Ahmed
fb1a17566f mobile: release v3.0.23 2024-11-23 22:17:42 +05:00
Abdullah Atta
8dd8447212 monograph: cache satori output in lru cache
satori is sloooooow so we need to cache.
find another solution for og generation
2024-11-23 22:16:47 +05:00
Ammar Ahmed
eba7341d45 mobile: restore editor state only if not restored already 2024-11-23 22:15:57 +05:00
Ammar Ahmed
08bd36d518 mobile: fix deps 2024-11-23 22:15:57 +05:00
Ammar Ahmed
7a45522ff3 mobile: make attachment item pressable 2024-11-23 22:15:57 +05:00
Ammar Ahmed
4ee2923181 mobile: fix attachment downloads failing on mobile 2024-11-23 22:15:57 +05:00
Abdullah Atta
48459efdb1 monograph: mark sharp as external in ssr build 2024-11-23 21:45:44 +05:00
Abdullah Atta
c848f3c588 monograph: mark sharp as external dep in vite 2024-11-23 21:44:24 +05:00
Abdullah Atta
52a75cfdaf monograph: install sharp with optional deps during docker 2024-11-23 21:42:28 +05:00
Abdullah Atta
9331b5f919 monograph: bump version to 1.1.1 2024-11-23 21:33:37 +05:00
Abdullah Atta
2ff53d4650 monograph: use sharp for generating og images 2024-11-23 21:33:21 +05:00
Abdullah Atta
7f2c8ca05a web: fix checkout snapshots 2024-11-23 14:56:58 +05:00
Abdullah Atta
9e752e9406 web: bump version to 3.0.22 2024-11-23 14:53:31 +05:00
Abdullah Atta
b8c3e2098f web: fix one-time discount wrongly shown as recurring 2024-11-23 14:39:45 +05:00
Abdullah Atta
4308e3bf49 web: fix editor zoom on trackpads 2024-11-23 14:03:31 +05:00
Abdullah Atta
25956ccadb web: remove auto item reveal when opening items 2024-11-23 13:31:23 +05:00
Abdullah Atta
af3fba5176 core: lazily use db in config & kv stores 2024-11-23 13:28:55 +05:00
Ammar Ahmed
4afaa6a36e mobile: fix reminder visual issues 2024-11-23 13:00:57 +05:00
Ammar Ahmed
e98e1a5f36 mobile: fix pdf-preview crash 2024-11-23 12:24:17 +05:00
01zulfi
abfdad0fec web: fix setting item width (#6909) (#6935)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2024-11-23 12:16:26 +05:00
dyw770
99e50ddad6 monograph: fix monograph version endpoints cors
Signed-off-by: dyw770 <dyw770@qq.com>
2024-11-23 12:05:29 +05:00
Ammar Ahmed
67a61dd3f3 mobile: release v3.0.22 2024-11-20 12:40:37 +05:00
Ammar Ahmed
13df01d55a mobile: fix readonly editor not loading 2024-11-20 12:38:17 +05:00
Ammar Ahmed
af4ea036c8 mobile: fix sync on iOS 2024-11-20 12:37:38 +05:00
Abdullah Atta
97a75d9498 ci: generate & publish flatpak sources during desktop release 2024-11-20 11:51:04 +05:00
Abdullah Atta
950028e3b4 web: bump version to v3.0.21 2024-11-20 11:26:23 +05:00
Abdullah Atta
874726c712 global: update lockfiles 2024-11-20 11:25:28 +05:00
Abdullah Atta
0f380b9962 Revert "editor: fix heading and p gap (#6530)"
This reverts commit c26988134f.
2024-11-20 11:21:48 +05:00
Abdullah Atta
2ce569726c editor: fix image on load animation 2024-11-20 11:17:25 +05:00
Abdullah Atta
a38fe7da43 web: fix editor margins state reset on restart 2024-11-20 11:09:45 +05:00
Abdullah Atta
057b563608 web: fix password app lock stuck on "wrong password" 2024-11-20 11:07:05 +05:00
Ammar Ahmed
3741e26219 mobile: fix error when changing 2fa method 2024-11-20 10:25:11 +05:00
Ammar Ahmed
86bf8f68bf ci: build ios app on latest mac runner 2024-11-19 19:28:45 +05:00
Ammar Ahmed
e7f67d1bb9 mobile: do not show migration on first launch 2024-11-19 18:52:27 +05:00
Ammar Ahmed
9c28e6bb93 mobile: fix build errors 2024-11-19 18:38:29 +05:00
Abdullah Atta
0f84fab810 mobile: only show migration progress for main db 2024-11-19 17:01:44 +05:00
Abdullah Atta
228a8e612f web: only show migration progress for main db 2024-11-19 17:01:33 +05:00
Abdullah Atta
175c6cdba0 core: send db name with migration events 2024-11-19 17:01:24 +05:00
Abdullah Atta
d3e2fcc3ac web: fix app stuck on splash screen after logout 2024-11-19 16:35:25 +05:00
Abdullah Atta
dafc2de2b3 ci: run desktop macos x64 tests on macos-13 runner 2024-11-19 16:00:08 +05:00
Abdullah Atta
f9eb9a1ad9 desktop: add test timeout and force exit 2024-11-19 15:52:27 +05:00
Abdullah Atta
1f5001210b ci: build electron wrapper after npm i 2024-11-19 15:52:17 +05:00
Abdullah Atta
3150eafb89 core: workaround for tokens shorter than 3 chars 2024-11-19 15:45:40 +05:00
Abdullah Atta
ad1e3f2c67 ci: Install sqlite-better-trigram for all arch 2024-11-19 15:16:21 +05:00
Abdullah Atta
d67b78d86e desktop: remove sqlite-better-trigram installation from build script 2024-11-19 12:21:44 +05:00
Abdullah Atta
25f74ff04d desktop: always install both arm64 and x64 packages on all platforms 2024-11-19 12:10:52 +05:00
Abdullah Atta
be7c1453fe desktop: force install both x64 & arm64 packages for sqlite-better-trigram on macos 2024-11-19 12:00:21 +05:00
Abdullah Atta
daa60939c3 desktop: take app screenshot if test fails 2024-11-19 11:55:37 +05:00
Abdullah Atta
ac659b05e4 web: ignore network error 2024-11-19 11:49:31 +05:00
Abdullah Atta
8391d51e02 ci: run desktop tests on macos x64 2024-11-19 11:09:37 +05:00
01zulfi
ad78b3a931 editor: fix backspace deleting formatting in list item (#6712) (#6878)
* fix hitting backspace inside the second (or next) p in list item deleted the formatting of the entire list item

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-19 10:30:13 +05:00
Abdullah Atta
e3a1fdd87d ci: simplify macos desktop test step 2024-11-18 23:43:44 +05:00
Abdullah Atta
12f24db3af desktop: add tests (#6900)
* desktop: add tests

* ci: fix install packages step

* ci: set NOTESNOOK_STAGING env

* ci: fix working dir

* ci: use cross-env on windows

* desktop: fix tests

* ci: use `xvfb-run` on linux

* ci: install playwright deps

* ci: remove DEBUG=pw*

* ci: no need to install playwright deps
2024-11-18 23:38:41 +05:00
Abdullah Atta
2af805017c web: bump version to v3.0.20 2024-11-18 20:01:20 +05:00
Abdullah Atta
97d4ca0afe desktop: install and load sqlite-better-trigram module 2024-11-18 19:03:57 +05:00
Ammar Ahmed
8b5c246531 mobile: release 3.0.21 2024-11-18 18:39:36 +05:00
Ammar Ahmed
8f2113b155 mobile: show migration progress 2024-11-18 13:45:52 +05:00
Abdullah Atta
a378bd397b web: show status when db is migrating on startup 2024-11-18 13:45:52 +05:00
Abdullah Atta
a315473ea4 core: add migration start/finished events 2024-11-18 13:45:52 +05:00
Ammar Ahmed
7dc581b9f3 mobile: copy support email automatically 2024-11-18 12:42:22 +05:00
alihamuh
5ef392ca31 web: add copy button for support email in settings 2024-11-18 12:42:22 +05:00
Abdullah Atta
846c949cd1 desktop: handle Error while decrypting the ciphertext provided to safeStorage.decryptString. error 2024-11-18 12:16:58 +05:00
Abdullah Atta
d88ead4609 web: fix dialogs not closing when app gets locked 2024-11-18 11:51:29 +05:00
Abdullah Atta
055c109563 web: fix crash immediately after unlocking app 2024-11-18 11:51:09 +05:00
dyw770
378a7197c2 monograph: fix code block copy (#6891)
Signed-off-by: dyw770 <dyw770@qq.com>
2024-11-18 11:31:45 +05:00
Abdullah Atta
89ee0f8d27 core: fix sync tests and remove obsolete snapshots 2024-11-16 15:16:18 +05:00
Abdullah Atta
c9ea9b1d4e web: fix app note starting due to outdated sqlite wasm libs 2024-11-16 14:16:01 +05:00
Ammar Ahmed
a91532839f mobile: add better-trigram extension 2024-11-16 14:16:01 +05:00
Abdullah Atta
b32f6b2460 core: remove special handling for queries < 3 in length 2024-11-16 14:16:01 +05:00
Abdullah Atta
e02970b936 web: install better trigram sqlite extension 2024-11-16 14:16:01 +05:00
Abdullah Atta
1f24fd4758 core: use better_trigram for improved content tokenization 2024-11-16 14:16:01 +05:00
Abdullah Atta
5a16403a13 web: fix Could not find a provider port to communicate with. error 2024-11-16 07:42:37 +05:00
01zulfi
48bbaad1b8 editor: fix markdown link pasting (#6639) (#6866)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-16 06:41:22 +05:00
Ammar Ahmed
520711eec9 mobile: match height with mobile action sheet 2024-11-15 16:22:29 +05:00
Abdullah Atta
3f82a4ebd4 core: fix using wrong encryption key shows incorrect password error 2024-11-15 16:02:55 +05:00
Abdullah Atta
4feb22fc12 web: open empty note by default on startup 2024-11-15 16:02:55 +05:00
Abdullah Atta
4e0e8dbb66 web: even faster startup 2024-11-15 16:02:55 +05:00
Ammar Ahmed
431c4eae14 mobile: fix localization issues 2024-11-15 15:54:20 +05:00
Ammar Ahmed
6373cbc9a4 mobile: fix core api usage 2024-11-15 15:54:20 +05:00
Ammar Ahmed
bbf3477512 editor: fix action-sheet not clickable 2024-11-15 15:54:19 +05:00
01zulfi
ca30ac86d7 web: fix close tab opening again
* introduced in #6817

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-15 15:47:39 +05:00
Abdullah Atta
88aecb3af2 setup: use --moduleResolution Bundler when running esm build 2024-11-15 12:31:56 +05:00
Abdullah Atta
c5a78b0293 global: update lockfiles 2024-11-15 12:31:56 +05:00
Abdullah Atta
b0bc1ec4a6 web: fix FOUC on startup 2024-11-15 12:31:56 +05:00
Abdullah Atta
84b3892053 desktop: update electron to v31 2024-11-15 12:31:56 +05:00
Abdullah Atta
d985683c0f desktop: fix Electron failed to install correctly error 2024-11-15 12:31:55 +05:00
Ammar Ahmed
008ce9222e mobile: fix warning 2024-11-15 12:00:41 +05:00
Ammar Ahmed
c360fa8c9b mobile: fix build 2024-11-15 11:56:46 +05:00
Abdullah Atta
c1afd4692d desktop: disable shrinking for titlebar 2024-11-15 10:43:01 +05:00
Abdullah Atta
7748cf453c desktop: fix node version is different error on startup 2024-11-15 10:42:17 +05:00
Abdullah Atta
34d0123351 ci: upgrade all actions to actions/checkout@v4 2024-11-13 18:24:14 +05:00
Abdullah Atta
af1cfc92c2 web: use unique note during sync tests 2024-11-13 18:13:16 +05:00
Abdullah Atta
59e06ef938 core: fix sync not running on app startup 2024-11-13 18:12:54 +05:00
Abdullah Atta
9d12ef82c0 web: do not prefetch wa-sqlite wasm module on desktop 2024-11-13 17:52:40 +05:00
Abdullah Atta
0a9bfd4b69 web: get rid of mitata; run benchmarks directly 2024-11-13 17:29:07 +05:00
Abdullah Atta
329248ebbd web: reduce pbkdf2 db key generator iterations to 100k
this is for performance reasons mainly. 100k should still be super secure.
2024-11-13 17:29:07 +05:00
Ammar Ahmed
cd51f2f2c9 ci: fix tests 2024-11-13 15:53:14 +05:00
Abdullah Atta
a3e7d5bd84 ci: get rid of codspeed 2024-11-13 15:48:33 +05:00
Abdullah Atta
6a261d284f web: update @playwright/test to v1.48.2 2024-11-13 15:48:33 +05:00
Abdullah Atta
eddf9f6d04 web: kill server after running benchmarks 2024-11-13 15:48:33 +05:00
Abdullah Atta
33e0f81988 ci: install playwright chromium before running benchmark 2024-11-13 15:48:33 +05:00
Abdullah Atta
0df8f80b48 ci: add benchmark workflow for @notesnook/web 2024-11-13 15:48:33 +05:00
Abdullah Atta
2f4590fbb6 web: add benchmarks 2024-11-13 15:48:33 +05:00
Abdullah Atta
a011857286 web: more fixes to new notebooks tree 2024-11-13 15:48:33 +05:00
Abdullah Atta
cc9e7e10b6 web: fix tests 2024-11-13 15:48:33 +05:00
Abdullah Atta
78e04bfac5 global: update lockfiles 2024-11-13 15:48:33 +05:00
Abdullah Atta
652231d5fd editor(mobile): fix editor not loading in dev 2024-11-13 15:48:33 +05:00
Abdullah Atta
343920d376 editor(mobile): fix ui for new custom action sheet 2024-11-13 15:48:33 +05:00
Abdullah Atta
1f3e7574f5 web: improve app startup 2024-11-13 15:48:33 +05:00
Abdullah Atta
ea9b10ff23 web: get rid of text-scramble 2024-11-13 15:48:33 +05:00
Abdullah Atta
232a347f4a web: lazy load hash-wasm 2024-11-13 15:48:33 +05:00
Abdullah Atta
00053cb99d web: get rid of web-streams-polyfill 2024-11-13 15:48:33 +05:00
Abdullah Atta
03a2a2f9cc web: lazily load zip/unzip streams 2024-11-13 15:48:33 +05:00
Abdullah Atta
09c8c66841 web: @notesnook/core related changes 2024-11-13 15:48:33 +05:00
Abdullah Atta
53e4bda69d web: stop lazy loading fs
we really should split it up so its easier to partially import
2024-11-13 15:48:33 +05:00
Abdullah Atta
4c77c8e22c web: get rid of react-complex-tree
build a custom implementation using react-virtuouso
2024-11-13 15:48:33 +05:00
Abdullah Atta
9afb4a52c2 web: get rid of tinycolor2 2024-11-13 15:48:33 +05:00
Abdullah Atta
b3d6fd7f5b web: import svg assets as url to exclude them from main bundle 2024-11-13 15:48:33 +05:00
Abdullah Atta
245d022f58 web: prefetch sqlite wasm module 2024-11-13 15:48:33 +05:00
Abdullah Atta
2d226db271 web: replace react-day-picker with custom built day picker
it uses `@rehookify/datepicker`
2024-11-13 15:48:33 +05:00
Abdullah Atta
9281f9a390 web: get rid of framer-motion 2024-11-13 15:48:33 +05:00
Abdullah Atta
d30bb56e75 web: remove unused modules & components 2024-11-13 15:48:33 +05:00
Abdullah Atta
eb92989478 web: remove unused packages 2024-11-13 15:48:33 +05:00
Abdullah Atta
d0dcdcd337 web: replace marked with snarkdown 2024-11-13 15:48:33 +05:00
Abdullah Atta
60aa92ca03 setup: update typescript to 5.6 2024-11-13 15:48:33 +05:00
Abdullah Atta
e2bbc1425b setup: add build script 2024-11-13 15:48:33 +05:00
Abdullah Atta
fc01103353 ui: remove framer-motion 2024-11-13 15:48:33 +05:00
Abdullah Atta
22919561fe theme: remove @types/tinycolor 2024-11-13 15:48:33 +05:00
Abdullah Atta
745cbc082f editor: replace tinycolor2 with colord 2024-11-13 15:48:33 +05:00
Abdullah Atta
6671a97181 editor: get rid of framer-motion 2024-11-13 15:48:33 +05:00
Abdullah Atta
b7aaa382b1 editor: fix postbuild script 2024-11-13 15:48:33 +05:00
Abdullah Atta
ecfa0e2c6b theme: get rid of tinycolor2 2024-11-13 15:48:33 +05:00
Abdullah Atta
58c6cce4e4 common: await getContentFromData 2024-11-13 15:48:33 +05:00
Abdullah Atta
b388a550a5 core: remove unneeded packages 2024-11-13 15:48:33 +05:00
Abdullah Atta
f0c5f6b949 core: lazy init compressor 2024-11-13 15:48:33 +05:00
Abdullah Atta
459cd70c32 core: lazy load signalr 2024-11-13 15:48:33 +05:00
Abdullah Atta
ece142e8d8 core: lazy load content-type 2024-11-13 15:48:33 +05:00
Abdullah Atta
5f1f308540 core: replace mime-db with mime and lazy load it 2024-11-13 15:48:33 +05:00
Abdullah Atta
7437b29518 core: fix type errors in migrations 2024-11-13 15:48:33 +05:00
Abdullah Atta
7acb0a8c0f common: use tsc for builds instead of tsup 2024-11-13 15:48:33 +05:00
Abdullah Atta
a1a24ee865 editor: use tsc for builds instead of tsup 2024-11-13 15:48:33 +05:00
Abdullah Atta
02008ad098 core: use tsc instead of tsup for builds 2024-11-13 15:48:33 +05:00
Abdullah Atta
0a02431f6a web: remove unneeded modules 2024-11-13 15:48:33 +05:00
Ammar Ahmed
9291711036 ci: enable all tests 2024-11-13 11:22:32 +05:00
01zulfi
f3e0fda5fa web: fix button is not defined error
* introduced in #6848

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-13 11:13:17 +05:00
Ammar Ahmed
0e721a4948 mobile: add e2e test workflow 2024-11-13 09:17:11 +05:00
Ammar Ahmed
e0a704d022 mobile: fix e2e tests 2024-11-13 09:17:11 +05:00
Abdullah Atta
e36bc816ea web: fix more failing tests 2024-11-13 09:17:11 +05:00
Abdullah Atta
f8a62ca437 web: fix note & notebook tests 2024-11-13 09:17:11 +05:00
Abdullah Atta
22ecb079b0 intl: update po files 2024-11-13 09:17:11 +05:00
Abdullah Atta
06333cf9aa web: localize more missing strings 2024-11-13 09:17:11 +05:00
Ammar Ahmed
1ad199ce53 mobile: cleanup and fix errors 2024-11-13 09:17:11 +05:00
Abdullah Atta
ceebda5314 web: localize missing strings 2024-11-13 09:17:11 +05:00
Abdullah Atta
e2d33381cc intl: fix type error in vite.config.mts due to duplicate rollup packages 2024-11-13 09:17:11 +05:00
Abdullah Atta
2417f8e233 global: fix many localization related errors and issues 2024-11-13 09:17:11 +05:00
Ammar Ahmed
a240b0b3d7 ci: add e2e workflow 2024-11-11 19:28:08 +05:00
01zulfi
fab085579a web: add text zoom functionality in editor (#6633) (#6846)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-11-09 12:15:27 +05:00
614 changed files with 40608 additions and 68379 deletions

View File

@@ -10,7 +10,7 @@ const authors = readFileSync("AUTHORS", "utf-8");
const isAuthor = authors.includes(`<${authorEmail}>`);
const SCOPES = [
// for full list of scopes + details see: https://github.com/streetwriters/notesnook-private/blob/master/CONTRIBUTING.md#commit-guidelines
// for full list of scopes + details see: https://github.com/streetwriters/notesnook/blob/master/CONTRIBUTING.md#commit-guidelines
"mobile",
"web",
@@ -36,7 +36,8 @@ const SCOPES = [
"global",
"docs",
"themebuilder",
"intl"
"intl",
"webclipper"
];
module.exports = {

View File

@@ -14,6 +14,8 @@ runs:
apps/mobile/package-lock.json
apps/desktop/package-lock.json
apps/web/package-lock.json
apps/monograph/package-lock.json
apps/theme-builder/package-lock.json
extensions/web-clipper/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
@@ -24,3 +26,4 @@ runs:
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
packages/intl/package-lock.json

100
.github/workflows/android.e2e.yml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: e2e-android
on: workflow_dispatch
jobs:
e2e-android:
runs-on: ubuntu-latest
env:
API_LEVEL: 34
ARCH: x86_64
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: false
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: wrapper
cache-read-only: false
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2
with:
java-version: "17"
architecture: "x64"
- name: Install Detox CLI
run: npm install detox-cli --global
- name: Detox build
run: |
yarn build:android
ls apps/mobile/native/android/app/build/outputs/apk
ls apps/mobile/native/android/app/build/outputs/apk/release
- name: Get device name
id: device
run: |
AVD_NAME=$(node -p "require('./apps/mobile/native/.detoxrc.js').devices.emulator.device.avdName")
echo "AVD_NAME=$AVD_NAME" >> $GITHUB_OUTPUT
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: ${{ steps.device.outputs.AVD_NAME }}
- name: create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.API_LEVEL }}
arch: ${{ env.ARCH }}
avd-name: ${{ steps.device.outputs.AVD_NAME }}
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: echo "Generated AVD snapshot for caching."
- name: Detox test
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.API_LEVEL }}
arch: ${{ env.ARCH }}
avd-name: ${{ steps.device.outputs.AVD_NAME }}
disable-animations: true
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
force-avd-creation: false
script: yarn test:android --headless --record-logs failing --record-videos failing --take-screenshots failing
- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: detox-artifacts
path: artifacts
retention-days: 14

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache

View File

@@ -18,10 +18,10 @@ on:
jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -37,10 +37,10 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -49,6 +49,13 @@ jobs:
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Setup environment
run: |
@@ -57,6 +64,10 @@ jobs:
- name: Generate desktop build
run: npx nx build:desktop @notesnook/web
- name: Build desktop bundle
working-directory: ./apps/desktop
run: npm run bundle
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
@@ -71,7 +82,8 @@ jobs:
- name: Package desktop build
run: |
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/web/build/
cp -r ./apps/web/build ./apps/desktop/
zip -r ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip ./apps/desktop/build/
echo "Build folder archived to ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip"
- name: Upload desktop build
@@ -84,6 +96,26 @@ jobs:
files: ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
- name: Generate flatpak sources
if: inputs.publish-github && inputs.build-linux
run: |
sudo apt-get install -y flatpak flatpak-builder pipx
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
cd /tmp/flatpak-builder-tools/node
pipx install .
cd -
node scripts/generate-sources.mjs
- name: Upload flatpak sources
uses: softprops/action-gh-release@v1
if: inputs.publish-github && inputs.build-linux
with:
draft: true
name: Notesnook Desktop v${{ steps.app_metadata.outputs.app_version }}
tag_name: v${{ steps.app_metadata.outputs.app_version }}
files: ./generated-sources.json
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
build-macos:
name: Build for macOS
needs: build
@@ -92,7 +124,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -136,6 +168,12 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Install provisioning profile
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
working-directory: ./apps/desktop
@@ -188,7 +226,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -212,6 +250,12 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
@@ -247,7 +291,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v2
@@ -272,6 +316,12 @@ jobs:
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build
run: npx nx run release --project @notesnook/desktop

233
.github/workflows/desktop.tests.yml vendored Normal file
View File

@@ -0,0 +1,233 @@
name: Test @notesnook/desktop
on:
workflow_dispatch:
push:
branches:
- "master"
paths:
- "app/desktop/**"
# re-run workflow if workflow file changes
- ".github/workflows/desktop.tests.yml"
pull_request:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Generate desktop build
run: npx nx build:desktop @notesnook/web
- name: Archive build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: apps/web/build/**/*
test-macos-x64:
name: Test macOS x64
needs: build
runs-on: macos-13
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
- name: Build app
run: |
yarn electron-builder --mac --dir --x64
working-directory: ./apps/desktop
- name: Run tests x64
run: |
EXECUTABLE_PATH=output/mac/Notesnook.app/Contents/MacOS/Notesnook npm run test
working-directory: ./apps/desktop
- name: Upload test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-macos-x64
path: apps/desktop/test-results
retention-days: 5
test-macos:
name: Test macOS
needs: build
runs-on: macos-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
- name: Build app
run: |
yarn electron-builder --mac --dir --arm64
working-directory: ./apps/desktop
- name: Run tests arm64
run: |
EXECUTABLE_PATH=output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook npm run test
working-directory: ./apps/desktop
- name: Upload test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-macos
path: apps/desktop/test-results
retention-days: 5
test-linux:
name: Test for Linux
needs: build
runs-on: ubuntu-22.04
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
- name: Build app
run: |
yarn electron-builder --linux --dir --arm64 --x64
working-directory: ./apps/desktop
- name: Run tests
run: |
EXECUTABLE_PATH=output/linux-unpacked/notesnook xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
working-directory: ./apps/desktop
- name: Upload test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-linux
path: apps/desktop/test-results
retention-days: 5
test-windows:
name: Test for Windows
needs: build
runs-on: windows-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=desktop
- name: Install sqlite-better-trigram for all arch
run: |
npm i --cpu arm64 sqlite-better-trigram
npm i --cpu x64 sqlite-better-trigram
working-directory: ./apps/desktop
- name: Build Electron wrapper
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
- name: Build app
run: |
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --win --dir --arm64 --x64
working-directory: ./apps/desktop
- name: Run tests
run: |
npx cross-env EXECUTABLE_PATH=output/win-unpacked/Notesnook.exe npm run test
working-directory: ./apps/desktop
- name: Upload test results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results-win
path: apps/desktop/test-results
retention-days: 5

View File

@@ -19,10 +19,10 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -1,6 +1,6 @@
name: Publish Notesnook Help
on:
on:
workflow_dispatch:
push:
branches:
@@ -11,10 +11,10 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: stable

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: macos-13
runs-on: macos-latest
timeout-minutes: 60
steps:

View File

@@ -14,7 +14,7 @@ on: workflow_dispatch
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
permissions:
packages: write
contents: read

View File

@@ -5,10 +5,10 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -5,10 +5,10 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

44
.github/workflows/web.benchmarks.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Benchmark @notesnook/web
on:
workflow_dispatch:
push:
branches:
- "master"
paths:
- "apps/web/**"
# re-run workflow if workflow file changes
- ".github/workflows/web.benchmarks.yml"
pull_request:
jobs:
bench:
name: Bench
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Build
run: npm run build:web
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: apps/web
- name: Run benchmarks
id: benchmark
run: node __bench__/app.bench.mjs
working-directory: apps/web

View File

@@ -5,10 +5,10 @@ on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache

View File

@@ -19,10 +19,10 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
@@ -53,7 +53,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Download build
uses: actions/download-artifact@v4

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ nx-cloud.env
site
node_modules.backup
.nx
*.patch

View File

@@ -0,0 +1,53 @@
/*
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 test from "node:test";
import { launchApp } from "./utils.mjs";
import assert from "assert";
import slugify from "slugify";
import path from "path";
import { mkdir } from "fs/promises";
test("make sure app loads", async (t) => {
const { app, page } = await launchApp();
try {
await page.waitForSelector("#authForm");
assert.ok(
await page.getByRole("button", { name: "Create account" }).isVisible()
);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page.waitForSelector(".ProseMirror");
} catch (e) {
await mkdir("test-results", { recursive: true });
await page.screenshot({
path: path.join(
"test-results",
`${slugify(t.name)}-${process.platform}-${process.arch}-error.png`
)
});
throw e;
} finally {
await app.close();
}
});

View File

@@ -0,0 +1,38 @@
/*
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 { _electron as electron } from "playwright";
const executablePath = process.env.EXECUTABLE_PATH;
// process.platform === "linux"
// ? "output/linux-unpacked/Notesnook"
// : process.platform === "darwin"
// ? "output/mac/Notesnook.app/Contents/MacOS/Notesnook"
// : "output/win-unpacked/Notesnook.exe";
export async function launchApp() {
const app = await electron.launch({
executablePath
});
const page = await app.firstWindow();
return { app, page };
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,21 +2,22 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.18",
"version": "3.0.25",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"sideEffects": false,
"exports": {
".": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
"types": "./dist/types/index.d.ts",
"default": "./dist/cjs/index.js"
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
"types": "./dist/types/index.d.ts",
"default": "./dist/esm/index.js"
}
}
},
@@ -24,29 +25,33 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@lingui/core": "4.11.4",
"@lingui/core": "5.1.2",
"@notesnook/intl": "file:../../packages/intl",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "11.2.1",
"better-sqlite3-multiple-ciphers": "11.5.0",
"electron-trpc": "0.6.1",
"electron-updater": "^6.3.4",
"icojs": "^0.19.4",
"sqlite-better-trigram": "0.0.2",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"zod": "^3.23.8"
"zod": "3.23.8"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
"@types/node": "22.7.0",
"@types/yargs": "^17.0.33",
"chokidar": "^4.0.1",
"electron": "^30.5.1",
"electron-builder": "^25.1.7",
"esbuild": "^0.24.0",
"electron": "^31.7.4",
"electron-builder": "^25.1.8",
"esbuild": "0.21.5",
"vitest": "2.1.8",
"node-abi": "^3.68.0",
"node-gyp-build": "^4.8.2",
"playwright": "^1.48.2",
"prebuildify": "^6.0.1",
"slugify": "1.6.6",
"tree-kill": "^1.2.2",
"undici": "^6.19.8"
},
@@ -57,10 +62,11 @@
"start": "node scripts/dev.mjs",
"staging": "node scripts/build.mjs --run",
"release": "node scripts/build.mjs",
"build": "tsup-node src/index.ts",
"build": "node ../../scripts/build.mjs",
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
"bundle:mas": "esbuild electron=./src/main.ts ./src/preload.ts --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
"postinstall": "patch-package"
"postinstall": "patch-package",
"test": "node --test --test-timeout 30000 --test-force-exit"
},
"author": {
"name": "Streetwriters (Private) Limited",
@@ -89,7 +95,20 @@
"node_modules/file-uri-to-path",
"node_modules/bindings",
"node_modules/node-gyp-build",
"node_modules/sqlite-better-trigram",
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
{
"from": "node_modules/sqlite-better-trigram-linux-${arch}",
"to": "node_modules/sqlite-better-trigram-linux-${arch}"
},
{
"from": "node_modules/sqlite-better-trigram-darwin-${arch}",
"to": "node_modules/sqlite-better-trigram-darwin-${arch}"
},
{
"from": "node_modules/sqlite-better-trigram-windows-${arch}",
"to": "node_modules/sqlite-better-trigram-windows-${arch}"
},
"node_modules/sodium-native/index.js",
"node_modules/sodium-native/package.json"
],
@@ -162,10 +181,12 @@
]
}
],
"signingHashAlgorithms": [
"sha256"
],
"sign": "./scripts/sign.js",
"signtoolOptions": {
"signingHashAlgorithms": [
"sha256"
],
"sign": "./scripts/sign.js"
},
"icon": "assets/icons/app.ico"
},
"portable": {

View File

@@ -18,19 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import path from "path";
import fs, { readFile, writeFile } from "fs/promises";
import { existsSync, readFileSync } from "fs";
import fs from "fs/promises";
import { existsSync } from "fs";
import yargs from "yargs-parser";
import os from "os";
import * as childProcess from "child_process";
import { fileURLToPath } from "url";
import { patchBetterSQLite3 } from "./patch-better-sqlite3.mjs";
const args = yargs(process.argv);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageJson = JSON.parse(
readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")
);
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
@@ -38,7 +36,7 @@ await fs.rm("./build/", { force: true, recursive: true });
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
await exec(
"yarn nx build:desktop @notesnook/web",
"npx nx build:desktop @notesnook/web",
path.join(__dirname, "..", "..", "..")
);
}
@@ -46,12 +44,6 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
// temporary until there's support for prebuilt binaries for linux ARM
if (os.platform() === "linux") await patchBetterSQLite3();
// if (os.platform() === "win32")
// await exec(
// `npx prebuildify --arch=arm64 --strip -t electron@${packageJson.devDependencies.electron}`,
// path.join(__dirname, "..", "node_modules", "sodium-native")
// );
await fs.cp(path.join(webAppPath, "build"), "build", {
recursive: true,
force: true
@@ -66,11 +58,13 @@ if (args.variant === "mas") {
await exec(`yarn run build`);
if (args.run) {
await exec(`yarn electron-builder --dir --x64`);
await exec(`yarn electron-builder --dir --${process.arch}`);
if (process.platform === "win32") {
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
} else if (process.platform === "darwin") {
await exec(`./output/mac/Notesnook.app/Contents/MacOS/Notesnook`);
if (process.arch === "arm64")
await exec(`./output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook`);
else await exec(`./output/mac/Notesnook.app/Contents/MacOS/Notesnook`);
} else {
await exec(`./output/linux-unpacked/Notesnook`);
}
@@ -83,21 +77,3 @@ async function exec(cmd, cwd) {
cwd: cwd || process.cwd()
});
}
async function patchBetterSQLite3() {
const jsonPath = path.join(
__dirname,
"..",
"node_modules",
"better-sqlite3-multiple-ciphers",
"package.json"
);
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
json.version = "11.2.2";
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
json.repository.url =
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
await writeFile(jsonPath, JSON.stringify(json));
}

View File

@@ -36,6 +36,8 @@ const ENV = {
FORCE_COLOR: "false",
COLOR: "0"
};
process.chdir(path.join(__dirname, ".."));
await onChange(true);
console.log("Watching...");
@@ -53,6 +55,8 @@ async function onChange(first) {
if (first) {
await fs.rm("./build/", { force: true, recursive: true });
await exec("npm rebuild electron --verbose --foreground-scripts");
await exec("yarn electron-builder install-app-deps");
}
@@ -66,8 +70,8 @@ async function onChange(first) {
if (first) {
await spawnAndWaitUntil(
["yarn", "nx", "start:desktop", "@notesnook/web"],
path.join(__dirname, "..", "..", ".."),
["npm", "run", "start:desktop"],
path.join(__dirname, "..", "..", "web"),
(data) => data.includes("Network: use --host to expose")
);
}

View File

@@ -0,0 +1,48 @@
/*
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 { readFile, writeFile } from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export async function patchBetterSQLite3() {
const jsonPath = path.join(
__dirname,
"..",
"node_modules",
"better-sqlite3-multiple-ciphers",
"package.json"
);
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
json.version = "11.5.1";
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
json.repository.url =
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
await writeFile(jsonPath, JSON.stringify(json));
}
if (process.argv[1] === __filename) {
console.log("Patching better-sqlite3");
patchBetterSQLite3();
}

View File

@@ -46,6 +46,8 @@ export class SQLite {
this.sqlite = require("better-sqlite3-multiple-ciphers")(
filePath
).unsafeMode(true);
const betterTrigram = require("sqlite-better-trigram");
betterTrigram.load(this.sqlite);
}
/**

View File

@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeImage, nativeImage } from "electron";
import path from "path";
import { isDevelopment } from "./index";
// @ts-expect-error stupid icojs
import { ParsedImage, parseICO } from "icojs";
import { getSystemTheme } from "./theme";
import { readFile } from "fs/promises";

View File

@@ -57,7 +57,8 @@ function getSystemTheme() {
nativeTheme.themeSource = oldThemeSource;
setTimeout(
() => listeners.forEach((a) => nativeTheme.addListener("updated", a)),
() =>
listeners.forEach((a) => nativeTheme.addListener("updated", () => a())),
1000
);
return currentTheme;
@@ -72,7 +73,8 @@ function changeTheme(theme: Theme) {
nativeTheme.themeSource = theme;
setTimeout(
() => listeners.forEach((a) => nativeTheme.addListener("updated", a)),
() =>
listeners.forEach((a) => nativeTheme.addListener("updated", () => a())),
1000
);
}

View File

@@ -3,8 +3,7 @@
"compilerOptions": {
"outDir": "./dist/",
"lib": ["ESNext"],
"moduleResolution": "nodenext",
"module": "nodenext"
"moduleResolution": "Bundler"
},
"include": ["src", "global.d.ts"]
}

View File

@@ -39,6 +39,7 @@ DerivedData
*.xcuserstate
*.hprof
**/.xcode.env.local
native/cache
# Android/IntelliJ
#
rn-build-deps/

View File

@@ -32,6 +32,7 @@ import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { NotePreviewConfigure } from "./screens/note-preview-configure";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
@@ -47,7 +48,7 @@ if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
const App = () => {
const App = (props: { configureMode: "note-preview" }) => {
useAppEvents();
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
@@ -61,6 +62,7 @@ const App = () => {
TipManager.init();
}, 100);
}, []);
return (
<View
style={{
@@ -90,7 +92,11 @@ const App = () => {
width: "100%"
}}
>
<ApplicationHolder />
{props.configureMode === "note-preview" ? (
<NotePreviewConfigure />
) : (
<ApplicationHolder />
)}
</GestureHandlerRootView>
<AppLockedOverlay />
</View>
@@ -103,8 +109,8 @@ let currTheme =
: SettingsService.getProperty("lighTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element: () => JSX.Element) => {
return function AppWithThemeProvider() {
export const withTheme = (Element: (props: any) => JSX.Element) => {
return function AppWithThemeProvider(props: any) {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
state.colorScheme,
state.darkTheme,
@@ -123,13 +129,14 @@ export const withTheme = (Element: () => JSX.Element) => {
})
.then((theme) => {
if (theme) {
console.log(theme.version, "theme updated");
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(theme)
: useThemeStore.getState().setLightTheme(theme);
}
})
.catch(console.log);
.catch(() => {
/* empty */
});
}, 1000);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -144,7 +151,7 @@ export const withTheme = (Element: () => JSX.Element) => {
return (
<I18nProvider i18n={i18n}>
<Element />
<Element {...props} />
</I18nProvider>
);
};

View File

@@ -1,385 +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 Sodium from "@ammarahmed/react-native-sodium";
import { Platform } from "react-native";
import "react-native-get-random-values";
import * as Keychain from "react-native-keychain";
import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
import { generateSecureRandom } from "react-native-securerandom";
import { DatabaseLogger } from ".";
import { MMKV } from "./mmkv";
import { ToastManager } from "../../services/event-manager";
// Database key cipher is persisted across different user sessions hence it has
// it's independent storage which we will never clear. This is only used when application has
// app lock with password enabled.
export const CipherStorage = new MMKVLoader()
.withInstanceID("cipher_storage")
.setProcessingMode(
Platform.OS === "ios"
? ProcessingModes.MULTI_PROCESS
: ProcessingModes.SINGLE_PROCESS
)
.disableIndexing()
.initialize();
const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
const NOTESNOOK_APPLOCK_KEY_SALT = "kBwr1Kre86ebOZ8ThLu2OA";
const NOTESNOOK_DB_KEY_SALT = "SNuzOcEK3amoqL0WvPeKqw";
const DB_KEY_CIPHER = "databaseKeyCipher";
const USER_KEY_CIPHER = "userKeyCipher";
const APPLOCK_CIPHER = "applockCipher";
const KEYSTORE_CONFIG = Platform.select({
ios: {
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
accessGroup: IOS_KEYCHAIN_ACCESS_GROUP,
service: IOS_KEYCHAIN_SERVICE_NAME
},
android: {}
});
function generatePassword() {
const length = 80;
const crypto = window.crypto || window.msCrypto;
if (typeof crypto === "undefined") {
throw new Error(
"Crypto API is not supported. Please upgrade your web browser"
);
}
const charset =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&+_{}[]():<>/?;";
const indexes = crypto.getRandomValues(new Uint32Array(length));
let secret = "";
for (const index of indexes) {
secret += charset[index % charset.length];
}
return secret;
}
export async function encryptDatabaseKeyWithPassword(appLockPassword) {
const key = getDatabaseKey();
const appLockCredentials = await Sodium.deriveKey(
appLockPassword,
NOTESNOOK_APPLOCK_KEY_SALT
);
const databaseKeyCipher = await encrypt(appLockCredentials, key);
MMKV.setMap(DB_KEY_CIPHER, databaseKeyCipher);
// We reset the database key from keychain once app lock password is set.
await Keychain.resetInternetCredentials(KEYCHAIN_SERVER_DBKEY);
return true;
}
export async function restoreDatabaseKeyToKeyChain(appLockPassword) {
const databaseKeyCipher = CipherStorage.getMap(DB_KEY_CIPHER);
const databaseKey = await decrypt(
{
password: appLockPassword
},
databaseKeyCipher
);
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
databaseKey,
KEYSTORE_CONFIG
);
MMKV.removeItem(DB_KEY_CIPHER);
return true;
}
export async function setAppLockVerificationCipher(appLockPassword) {
try {
const appLockCredentials = await Sodium.deriveKey(
appLockPassword,
NOTESNOOK_APPLOCK_KEY_SALT
);
const encrypted = await encrypt(appLockCredentials, generatePassword());
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
DatabaseLogger.info("setAppLockVerificationCipher");
} catch (e) {
DatabaseLogger.error(e);
console.log(e);
}
}
export async function clearAppLockVerificationCipher() {
CipherStorage.removeItem(APPLOCK_CIPHER);
}
export async function validateAppLockPassword(appLockPassword) {
try {
const appLockCipher = CipherStorage.getMap(APPLOCK_CIPHER);
if (!appLockCipher) return true;
const key = await Sodium.deriveKey(appLockPassword, appLockCipher.salt);
const decrypted = await decrypt(key, appLockCipher);
DatabaseLogger.info(
`validateAppLockPassword: ${typeof decrypted === "string"}`
);
return typeof decrypted === "string";
} catch (e) {
DatabaseLogger.error(e);
return false;
}
}
let DB_KEY;
export function clearDatabaseKey() {
DB_KEY = undefined;
DatabaseLogger.info("Cleared database key");
}
export async function getDatabaseKey(appLockPassword) {
if (DB_KEY) return DB_KEY;
try {
if (appLockPassword) {
const databaseKeyCipher = CipherStorage.getMap("databaseKeyCipher");
const databaseKey = await decrypt(
{
password: appLockPassword
},
databaseKeyCipher
);
DatabaseLogger.info("Getting database key from cipher");
DB_KEY = databaseKey;
}
if (!DB_KEY) {
const hasKey = await Keychain.hasInternetCredentials(
KEYCHAIN_SERVER_DBKEY
);
if (hasKey) {
let credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
KEYSTORE_CONFIG
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = credentials.password;
}
}
if (!DB_KEY) {
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
DB_KEY = derivedDatabaseKey.key;
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
DB_KEY,
KEYSTORE_CONFIG
);
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
);
if (userKeyCredentials) {
const userKeyCipher = await encrypt(
{
key: DB_KEY,
salt: NOTESNOOK_DB_KEY_SALT
},
userKeyCredentials.password
);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
}
DatabaseLogger.info("Migrated user credentials to cipher storage");
}
return DB_KEY;
} catch (e) {
ToastManager.error(e, "Error getting database key");
console.log(e, "error");
DatabaseLogger.error(e);
return null;
}
}
export async function deriveCryptoKey(data) {
try {
let credentials = await Sodium.deriveKey(data.password, data.salt);
const userKeyCipher = await encrypt(
{
key: await getDatabaseKey(),
salt: NOTESNOOK_DB_KEY_SALT
},
credentials.key
);
DatabaseLogger.info("User key stored: ", !!userKeyCipher);
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
return credentials.key;
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function getCryptoKey(_name) {
try {
const keyCipher = MMKV.getMap(USER_KEY_CIPHER);
if (!keyCipher) {
DatabaseLogger.info("User key cipher is null");
return null;
}
const key = await decrypt(
{
key: await getDatabaseKey(),
salt: keyCipher.salt
},
keyCipher
);
return key;
} catch (e) {
console.log("getCryptoKey", e);
DatabaseLogger.error(e);
}
}
export async function removeCryptoKey(_name) {
try {
MMKV.removeItem(USER_KEY_CIPHER);
await Keychain.resetInternetCredentials("notesnook");
return true;
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function getRandomBytes(length) {
return await generateSecureRandom(length);
}
export async function hash(password, email) {
let result = await Sodium.hashPassword(password, email);
return result;
}
export async function generateCryptoKey(password, salt) {
try {
let credentials = await Sodium.deriveKey(password, salt || null);
return credentials;
} catch (e) {
DatabaseLogger.error(e);
}
}
export function getAlgorithm(base64Variant) {
return `xcha-argon2i13-${base64Variant}`;
}
export async function decrypt(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let _data = { ...data };
_data.output = "plain";
if (!password.salt) password.salt = data.salt;
return await Sodium.decrypt(password, _data);
}
export async function decryptMulti(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
data = data.map((d) => {
d.output = "plain";
return d;
});
if (data.length && !password.salt) {
password.salt = data[0].salt;
}
return await Sodium.decryptMulti(password, data);
}
export function parseAlgorithm(alg) {
if (!alg) return {};
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
return {
encryptionAlgorithm: enc,
kdfAlgorithm: kdf,
compressionAlgorithm: compressionAlg,
isCompress: compressed === "1",
base64_variant: base64variant
};
}
export async function encrypt(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let message = {
type: "plain",
data: data
};
let result = await Sodium.encrypt(password, message);
return {
...result,
alg: getAlgorithm(7)
};
}
export async function encryptMulti(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let results = await Sodium.encryptMulti(
password,
data.map((item) => ({
type: "plain",
data: item
}))
);
return !results
? []
: results.map((result) => ({
...result,
alg: getAlgorithm(7)
}));
}

View File

@@ -0,0 +1,473 @@
/*
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 Sodium, { Cipher, Password } from "@ammarahmed/react-native-sodium";
import { SerializedKey } from "@notesnook/crypto";
import { Platform } from "react-native";
import "react-native-get-random-values";
import * as Keychain from "react-native-keychain";
import { MMKVLoader, ProcessingModes } from "react-native-mmkv-storage";
import { generateSecureRandom } from "react-native-securerandom";
import { DatabaseLogger } from ".";
import { ToastManager } from "../../services/event-manager";
import { MMKV } from "./mmkv";
// Database key cipher is persisted across different user sessions hence it has
// it's independent storage which we will never clear. This is only used when application has
// app lock with password enabled.
export const CipherStorage = new MMKVLoader()
.withInstanceID("cipher_storage")
.setProcessingMode(
Platform.OS === "ios"
? ProcessingModes.MULTI_PROCESS
: ProcessingModes.SINGLE_PROCESS
)
.disableIndexing()
.initialize();
const IOS_KEYCHAIN_ACCESS_GROUP = "group.org.streetwriters.notesnook";
const IOS_KEYCHAIN_SERVICE_NAME = "org.streetwriters.notesnook";
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
const NOTESNOOK_APPLOCK_KEY_SALT = "kBwr1Kre86ebOZ8ThLu2OA";
const NOTESNOOK_DB_KEY_SALT = "SNuzOcEK3amoqL0WvPeKqw";
const DB_KEY_CIPHER = "databaseKeyCipher";
const USER_KEY_CIPHER = "userKeyCipher";
const APPLOCK_CIPHER = "applockCipher";
const KEYSTORE_CONFIG = Platform.select({
ios: {
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
accessGroup: IOS_KEYCHAIN_ACCESS_GROUP,
service: IOS_KEYCHAIN_SERVICE_NAME
},
android: {}
});
function generatePassword() {
const length = 80;
//@ts-ignore
const crypto = window.crypto || window.msCrypto;
if (typeof crypto === "undefined") {
throw new Error(
"Crypto API is not supported. Please upgrade your web browser"
);
}
const charset =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!@#$%^&+_{}[]():<>/?;";
const indexes = crypto.getRandomValues(new Uint32Array(length));
let secret = "";
for (const index of indexes) {
secret += charset[index % charset.length];
}
return secret;
}
export async function encryptDatabaseKeyWithPassword(appLockPassword: string) {
const key = (await getDatabaseKey()) as string;
const appLockCredentials = await Sodium.deriveKey(
appLockPassword,
NOTESNOOK_APPLOCK_KEY_SALT
);
const databaseKeyCipher = (await encrypt(appLockCredentials, key)) as Cipher;
MMKV.setMap(DB_KEY_CIPHER, databaseKeyCipher);
// We reset the database key from keychain once app lock password is set.
await Keychain.resetInternetCredentials(KEYCHAIN_SERVER_DBKEY);
return true;
}
export async function restoreDatabaseKeyToKeyChain(appLockPassword: string) {
const databaseKeyCipher: Cipher = CipherStorage.getMap(DB_KEY_CIPHER);
const databaseKey = (await decrypt(
{
password: appLockPassword
},
databaseKeyCipher
)) as string;
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
databaseKey,
KEYSTORE_CONFIG
);
MMKV.removeItem(DB_KEY_CIPHER);
return true;
}
export async function setAppLockVerificationCipher(appLockPassword: string) {
try {
const appLockCredentials = await Sodium.deriveKey(
appLockPassword,
NOTESNOOK_APPLOCK_KEY_SALT
);
const encrypted = (await encrypt(
appLockCredentials,
generatePassword()
)) as Cipher;
CipherStorage.setMap(APPLOCK_CIPHER, encrypted);
DatabaseLogger.info("setAppLockVerificationCipher");
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function clearAppLockVerificationCipher() {
CipherStorage.removeItem(APPLOCK_CIPHER);
}
export async function validateAppLockPassword(appLockPassword: string) {
try {
const appLockCipher: Cipher = CipherStorage.getMap(APPLOCK_CIPHER);
if (!appLockCipher) return true;
const key = await Sodium.deriveKey(appLockPassword, appLockCipher.salt);
const decrypted = await decrypt(key, appLockCipher);
DatabaseLogger.info(
`validateAppLockPassword: ${typeof decrypted === "string"}`
);
return typeof decrypted === "string";
} catch (e) {
DatabaseLogger.error(e);
return false;
}
}
let DB_KEY: string | undefined;
export function clearDatabaseKey() {
DB_KEY = undefined;
DatabaseLogger.info("Cleared database key");
}
export async function getDatabaseKey(appLockPassword?: string) {
if (DB_KEY) return DB_KEY;
if (appLockPassword) {
const databaseKeyCipher: Cipher = CipherStorage.getMap("databaseKeyCipher");
const databaseKey = await decrypt(
{
password: appLockPassword
},
databaseKeyCipher
);
DatabaseLogger.info("Getting database key from cipher");
DB_KEY = databaseKey;
}
if (!DB_KEY) {
const hasKey = await Keychain.hasInternetCredentials(KEYCHAIN_SERVER_DBKEY);
if (hasKey) {
const credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = (credentials as Keychain.UserCredentials).password;
}
}
if (!DB_KEY) {
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
DB_KEY = derivedDatabaseKey.key as string;
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
DB_KEY,
KEYSTORE_CONFIG
);
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook"
);
if (userKeyCredentials) {
const userKeyCipher: Cipher = (await encrypt(
{
key: DB_KEY,
salt: NOTESNOOK_DB_KEY_SALT
},
userKeyCredentials.password
)) as Cipher;
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
}
DatabaseLogger.info("Migrated user credentials to cipher storage");
}
if (!DB_KEY) {
throw new Error(
`Failed to get database key, ${await Keychain.hasInternetCredentials(
KEYCHAIN_SERVER_DBKEY
)}`
);
}
return DB_KEY;
}
export async function deriveCryptoKeyFallback(data: SerializedKey) {
if (Platform.OS !== "ios") return;
try {
if (!data.password || !data.salt)
throw new Error(
"Invalid password and salt provided to deriveCryptoKeyFallback"
);
const credentials = await Sodium.deriveKeyFallback?.(
data.password,
data.salt
);
if (!credentials) return;
const userKeyCipher = (await encrypt(
{
key: (await getDatabaseKey()) as string,
salt: NOTESNOOK_DB_KEY_SALT
},
credentials.key as string
)) as Cipher<"base64">;
DatabaseLogger.info("User key fallback stored: ", {
userKeyCipher: !!userKeyCipher
});
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function deriveCryptoKey(data: SerializedKey) {
try {
if (!data.password || !data.salt)
throw new Error("Invalid password and salt provided to deriveCryptoKey");
const credentials = (await Sodium.deriveKey(
data.password,
data.salt
)) as Password;
const userKeyCipher = (await encrypt(
{
key: (await getDatabaseKey()) as string,
salt: NOTESNOOK_DB_KEY_SALT
},
credentials.key as string
)) as Cipher<"base64">;
DatabaseLogger.info("User key stored: ", {
userKeyCipher: !!userKeyCipher
});
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function getCryptoKey() {
try {
const keyCipher: Cipher = MMKV.getMap(USER_KEY_CIPHER);
if (!keyCipher) {
DatabaseLogger.info("User key cipher is null");
return undefined;
}
const key = await decrypt(
{
key: (await getDatabaseKey()) as string,
salt: keyCipher.salt
},
keyCipher
);
return key;
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function removeCryptoKey() {
try {
MMKV.removeItem(USER_KEY_CIPHER);
await Keychain.resetInternetCredentials("notesnook");
return true;
} catch (e) {
DatabaseLogger.error(e);
}
}
export async function getRandomBytes(length: number) {
return await generateSecureRandom(length);
}
export async function hash(
password: string,
email: string,
options?: { usesFallback?: boolean }
) {
DatabaseLogger.log(`Hashing password: fallback: ${options?.usesFallback}`);
if (options?.usesFallback && Platform.OS !== "ios") {
return "";
}
return (
options?.usesFallback
? await Sodium.hashPasswordFallback?.(password, email)
: await Sodium.hashPassword(password, email)
) as string;
}
export async function generateCryptoKey(password: string, salt?: string) {
return Sodium.deriveKey(password, salt) as Promise<SerializedKey>;
}
export async function generateCryptoKeyFallback(
password: string,
salt?: string
): Promise<SerializedKey> {
return Sodium.deriveKeyFallback?.(
password,
salt as string
) as Promise<SerializedKey>;
}
export function getAlgorithm(base64Variant: number) {
return `xcha-argon2i13-${base64Variant}`;
}
export async function decrypt(password: SerializedKey, data: Cipher<"base64">) {
const _data = { ...data };
_data.output = "plain";
if (!password.salt) password.salt = data.salt;
if (Platform.OS === "ios" && !password.key && password.password) {
const key = await Sodium.deriveKey(password.password, password.salt);
try {
return await Sodium.decrypt(key, _data);
} catch (e) {
const fallbackKey = await Sodium.deriveKeyFallback?.(
password.password,
password.salt
);
if (Platform.OS === "ios" && fallbackKey) {
DatabaseLogger.info("Using fallback key for decryption");
}
if (fallbackKey) {
return await Sodium.decrypt(fallbackKey, _data);
} else {
throw e;
}
}
}
return await Sodium.decrypt(password, _data);
}
export async function decryptMulti(
password: Password,
data: Cipher<"base64">[]
) {
data = data.map((d) => {
d.output = "plain";
return d;
});
if (data.length && !password.salt) {
password.salt = data[0].salt;
}
if (Platform.OS === "ios" && !password.key && password.password) {
const key = await Sodium.deriveKey(password.password, password.salt);
try {
return await Sodium.decryptMulti(key, data);
} catch (e) {
const fallbackKey = await Sodium.deriveKeyFallback?.(
password.password,
password.salt as string
);
if (Platform.OS === "ios" && fallbackKey) {
DatabaseLogger.info("Using fallback key for decryption");
}
if (fallbackKey) {
return await Sodium.decryptMulti(fallbackKey, data);
} else {
throw e;
}
}
}
return await Sodium.decryptMulti(password, data);
}
export function parseAlgorithm(alg: string) {
if (!alg) return {};
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
return {
encryptionAlgorithm: enc,
kdfAlgorithm: kdf,
compressionAlgorithm: compressionAlg,
isCompress: compressed === "1",
base64_variant: base64variant
};
}
export async function encrypt(password: SerializedKey, plainText: string) {
const result = await Sodium.encrypt<"base64">(password, {
type: "plain",
data: plainText
});
return {
...result,
alg: getAlgorithm(7)
};
}
export async function encryptMulti(
password: SerializedKey,
plainText: string[]
) {
const results = await Sodium.encryptMulti<"base64">(
password,
plainText.map((item) => ({
type: "plain",
data: item
}))
);
return !results
? []
: results.map((result) => ({
...result,
alg: getAlgorithm(7)
}));
}

View File

@@ -16,48 +16,50 @@ 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 "./logger";
import { database } from "@notesnook/common";
import { logger as dbLogger } from "@notesnook/core";
import { Platform } from "react-native";
import * as Gzip from "react-native-gzip";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import { logger as dbLogger, ICompressor } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import {
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler
} from "@streetwriters/kysely";
import filesystem from "../filesystem";
import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";
import { getDatabaseKey } from "./encryption";
import { Platform } from "react-native";
import * as Gzip from "react-native-gzip";
import SettingsService from "../../services/settings";
import { strings } from "@notesnook/intl";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import { FileStorage } from "../filesystem";
import { getDatabaseKey } from "./encryption";
import "./logger";
import { RNSqliteDriver } from "./sqlite.kysely";
import { Storage } from "./storage";
export async function setupDatabase(password) {
export async function setupDatabase(password?: string) {
const key = await getDatabaseKey(password);
if (!key) throw new Error(strings.databaseSetupFailed());
console.log("Opening database with key:", !!key);
database.host({
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co",
MONOGRAPH_HOST: "https://monogr.ph",
...(SettingsService.getProperty("serverUrls") || {})
});
database.setup({
storage: Storage,
eventsource: Platform.OS === "ios" ? EventSource : AndroidEventSource,
fs: filesystem,
compressor: {
compress: Gzip.deflate,
decompress: Gzip.inflate
},
eventsource: (Platform.OS === "ios"
? EventSource
: AndroidEventSource) as any,
fs: FileStorage,
compressor: async () =>
({
compress: Gzip.deflate,
decompress: Gzip.inflate
} as ICompressor),
batchSize: 100,
sqliteOptions: {
dialect: (name) => ({

View File

@@ -26,7 +26,7 @@ import { CompiledQuery } from "@streetwriters/kysely";
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
import { strings } from "@notesnook/intl";
type Config = { dbName: string; async: boolean; location: string };
type Config = { dbName: string; async: boolean; location?: string };
export class RNSqliteDriver implements Driver {
private connection?: DatabaseConnection;

View File

@@ -16,58 +16,52 @@ 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 { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { IStorage } from "@notesnook/core";
import { MMKVInstance } from "react-native-mmkv-storage";
import {
decrypt,
decryptMulti,
deriveCryptoKey,
deriveCryptoKeyFallback,
encrypt,
encryptMulti,
generateCryptoKey,
getCryptoKey,
getRandomBytes,
hash,
removeCryptoKey
generateCryptoKeyFallback
} from "./encryption";
import { MMKV } from "./mmkv";
export class KV {
/**
* @type {typeof MMKV}
*/
storage = null;
constructor(storage) {
storage: MMKVInstance;
constructor(storage: MMKVInstance) {
this.storage = storage;
}
async read(key) {
if (!key) return null;
let data = this.storage.getString(key);
if (!data) return null;
async read<T>(key: string, isArray?: boolean) {
if (!key) return undefined;
const data = this.storage.getString(key);
if (!data) return undefined;
try {
let parse = JSON.parse(data);
return parse;
return JSON.parse(data) as T;
} catch (e) {
return data;
return data as T;
}
}
async write(key, data) {
async write<T>(key: string, data: T) {
this.storage.setString(
key,
typeof data === "string" ? data : JSON.stringify(data)
);
return true;
}
async readMulti(keys) {
async readMulti<T>(keys: string[]) {
if (keys.length <= 0) {
return [];
} else {
try {
let data = await this.storage.getMultipleItemsAsync(
const data = await this.storage.getMultipleItemsAsync<any>(
keys.slice(),
"string"
);
@@ -79,24 +73,24 @@ export class KV {
obj = value;
}
return [key, obj];
});
}) as [string, T][];
} catch (e) {
console.log(e);
return [];
}
}
}
async remove(key) {
return this.storage.removeItem(key);
async remove(key: string) {
this.storage.removeItem(key);
}
async removeMulti(keys) {
if (!keys) return true;
return this.storage.removeItems(keys);
async removeMulti(keys: string[]) {
if (!keys) return;
this.storage.removeItems(keys);
}
async clear() {
return this.storage.clearStore();
this.storage.clearStore();
}
async getAllKeys() {
@@ -113,54 +107,46 @@ export class KV {
return keys;
}
async writeMulti(items) {
return this.storage.setMultipleItemsAsync(items, "object");
async writeMulti(items: [string, any][]) {
await this.storage.setMultipleItemsAsync(items, "object");
}
}
const DefaultStorage = new KV(MMKV);
async function requestPermission() {
if (Platform.OS === "ios") return true;
return true;
}
async function checkAndCreateDir(path) {
let dir =
Platform.OS === "ios"
? RNFetchBlob.fs.dirs.DocumentDir + path
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
try {
let exists = await RNFetchBlob.fs.exists(dir);
let isDir = await RNFetchBlob.fs.isDir(dir);
if (!exists || !isDir) {
await RNFetchBlob.fs.mkdir(dir);
}
} catch (e) {
await RNFetchBlob.fs.mkdir(dir);
}
return dir;
}
export default {
read: (key) => DefaultStorage.read(key),
write: (key, value) => DefaultStorage.write(key, value),
readMulti: (keys) => DefaultStorage.readMulti(keys),
remove: (key) => DefaultStorage.remove(key),
clear: () => DefaultStorage.clear(),
getAllKeys: () => DefaultStorage.getAllKeys(),
writeMulti: (items) => DefaultStorage.writeMulti(items),
removeMulti: (keys) => DefaultStorage.removeMulti(keys),
export const Storage: IStorage = {
write<T>(key: string, data: T): Promise<void> {
return DefaultStorage.write(key, data);
},
writeMulti<T>(entries: [string, T][]): Promise<void> {
return DefaultStorage.writeMulti(entries);
},
readMulti<T>(keys: string[]): Promise<[string, T][]> {
return DefaultStorage.readMulti(keys);
},
read<T>(key: string, isArray?: boolean): Promise<T | undefined> {
return DefaultStorage.read(key, isArray);
},
remove(key: string): Promise<void> {
return DefaultStorage.remove(key);
},
removeMulti(keys: string[]): Promise<void> {
return DefaultStorage.removeMulti(keys);
},
clear(): Promise<void> {
return DefaultStorage.clear();
},
getAllKeys(): Promise<string[]> {
return DefaultStorage.getAllKeys();
},
hash,
getCryptoKey,
encrypt,
encryptMulti,
decrypt,
decryptMulti,
getRandomBytes,
checkAndCreateDir,
requestPermission,
deriveCryptoKey,
getCryptoKey,
removeCryptoKey,
hash,
generateCryptoKey,
encryptMulti
generateCryptoKeyFallback,
deriveCryptoKeyFallback
};

View File

@@ -27,13 +27,9 @@ import RNFetchBlob from "react-native-blob-util";
*/
export async function compressToBase64(path, type) {
const { width: screenWidth, scale } = Dimensions.get("window");
console.log("COMPRESSING TO BASE64...");
return new Promise((resolve) => {
console.log(path, "image path...");
Image.getSize(path, async (width) => {
console.log("image width", width);
const response = await ImageResizer.createResizedImage(
path,
screenWidth * scale,
@@ -55,10 +51,12 @@ export async function compressToBase64(path, type) {
: response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs
.unlink(response.uri.replace("file://", ""))
.catch(console.log);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(() => {
/* empty */
});
resolve(base64);
});
@@ -80,6 +78,8 @@ export async function compressToFile(path, type) {
onlyScaleDown: true
}
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(() => {
/* empty */
});
return response.uri;
}

View File

@@ -16,7 +16,6 @@ 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 Sodium from "@ammarahmed/react-native-sodium";
import { getFileNameWithExtension } from "@notesnook/core";
import { strings } from "@notesnook/intl";
@@ -25,12 +24,12 @@ import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { subscribe, zip } from "react-native-zip-archive";
import filesystem from ".";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { ToastManager, presentSheet } from "../../services/event-manager";
import { presentSheet, ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import Storage from "../database/storage";
import { createCacheDir, exists } from "./io";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
@@ -51,23 +50,23 @@ export async function downloadAllAttachments() {
/**
* Downloads provided attachments to a .zip file
* on user's device.
* @param {string[]} attachments
* @param {string[]} attachmentIds
* @param onProgress
* @returns
*/
export async function downloadAttachments(attachments) {
export async function downloadAttachments(attachmentIds: string[]) {
await createCacheDir();
if (!attachments || !attachments.length) return;
if (!attachmentIds || !attachmentIds.length) return;
const groupId = `download-all-${Date.now()}`;
let outputFolder;
if (Platform.OS === "android") {
// Ask the user to select a directory to store the file
let file = await ScopedStorage.openDocumentTree(true);
const file = await ScopedStorage.openDocumentTree(true);
outputFolder = file.uri;
if (!outputFolder) return;
} else {
outputFolder = await Storage.checkAndCreateDir("/downloads/");
outputFolder = await filesystem.checkAndCreateDir("/downloads/");
}
// Create the folder to zip;
@@ -83,8 +82,10 @@ export async function downloadAttachments(attachments) {
await RNFetchBlob.fs.mkdir(zipSourceFolder);
const isCancelled = () => {
if (useAttachmentStore.getState().downloading[groupId]?.canceled) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (useAttachmentStore.getState().downloading?.[groupId]?.canceled) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
useAttachmentStore.getState().setDownloading({
groupId,
current: 0,
@@ -97,19 +98,21 @@ export async function downloadAttachments(attachments) {
}
};
for (let i = 0; i < attachments.length; i++) {
for (let i = 0; i < attachmentIds.length; i++) {
if (isCancelled()) return;
let attachment = await db.attachments.attachment(attachments[i]);
const attachment = await db.attachments.attachment(attachmentIds[i]);
if (!attachment) continue;
const hash = attachment.hash;
try {
useAttachmentStore.getState().setDownloading({
groupId: groupId,
current: i + 1,
total: attachments.length,
total: attachmentIds.length,
filename: attachment.hash
});
// Download to cache
let uri = await downloadAttachment(hash, false, {
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true,
groupId: groupId
@@ -140,7 +143,7 @@ export async function downloadAttachments(attachments) {
useAttachmentStore.getState().setDownloading({
current: 0,
total: 1,
message: `${strings.savingZipFile()}... ${strings.pleaseWait()}`,
message: strings.savingZipFile(),
groupId
});
// If all goes well, zip the notesnook-attachments folder in cache.
@@ -150,9 +153,7 @@ export async function downloadAttachments(attachments) {
groupId,
current: progress,
total: 1,
message: `${strings.savingZipFile()} (${(progress * 100).toFixed(
1
)}%)... ${strings.pleaseWait()}`
message: strings.savingZipFile((progress * 100).toFixed(1))
});
});
await zip(zipSourceFolder, zipOutputFile);
@@ -186,59 +187,85 @@ export async function downloadAttachments(attachments) {
});
releasePermissions(outputFolder);
sub?.remove();
ToastManager.error(e, "Error zipping attachments");
ToastManager.error(e as Error, "Error zipping attachments");
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
/* empty */
});
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
/* empty */
});
}
}
export default async function downloadAttachment(
hash,
hashOrId: string,
global = true,
options = {
silent: false,
cache: false,
throwError: false,
groupId: undefined,
base64: false,
text: false
options?: {
silent?: boolean;
cache?: boolean;
throwError?: boolean;
groupId?: string;
base64?: boolean;
text?: boolean;
}
) {
await createCacheDir();
let attachment = await db.attachments.attachment(hash);
const attachment = await db.attachments.attachment(hashOrId);
if (!attachment) {
DatabaseLogger.log("Attachment not found");
DatabaseLogger.log("Attachment not found", {
hash: hashOrId
});
return;
}
let folder = {};
if (!options.cache) {
let folder: {
uri: string;
} | null = null;
if (!options?.cache) {
if (Platform.OS === "android") {
folder = await ScopedStorage.openDocumentTree();
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir("/downloads/");
folder = {
uri: await filesystem.checkAndCreateDir("/downloads/")
};
}
}
try {
useAttachmentStore.getState().setDownloading({
groupId: options?.groupId || attachment.hash,
current: 0,
total: 1,
filename: attachment.filename
});
await db
.fs()
.downloadFile(
options.groupId || attachment.hash,
options?.groupId || attachment.hash,
attachment.hash,
attachment.chunkSize
);
useAttachmentStore.getState().setDownloading({
groupId: options?.groupId || attachment.hash,
current: 1,
total: 1,
filename: attachment.filename,
success: true
});
if (!(await exists(attachment.hash))) {
DatabaseLogger.log("Attachment does not exist after download.");
return;
}
if (options.base64 || options.text) {
if (options?.base64 || options?.text) {
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
return await db.attachments.read(
attachment.hash,
@@ -246,14 +273,16 @@ export default async function downloadAttachment(
);
}
let filename = getFileNameWithExtension(
const filename = await getFileNameWithExtension(
attachment.filename,
attachment.mimeType
);
let key = await db.attachments.decryptKey(attachment.key);
const key = await db.attachments.decryptKey(attachment.key);
let info = {
if (!key) return;
const info = {
iv: attachment.iv,
salt: attachment.salt,
length: attachment.size,
@@ -261,18 +290,18 @@ export default async function downloadAttachment(
hash: attachment.hash,
hashType: attachment.hashType,
mime: attachment.mimeType,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
fileName: options?.cache ? undefined : filename,
uri: options?.cache ? undefined : folder?.uri,
chunkSize: attachment.chunkSize,
appGroupId: IOS_APPGROUPID
};
let fileUri = await Sodium.decryptFile(
key,
info,
options.cache ? "cache" : "file"
options?.cache ? "cache" : "file"
);
if (!options.silent) {
if (!options?.silent) {
ToastManager.show({
heading: strings.network.downloadSuccess(),
message: strings.network.fileDownloaded(filename),
@@ -280,15 +309,15 @@ export default async function downloadAttachment(
});
}
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
if (Platform.OS === "ios" && !options?.cache) {
fileUri = folder?.uri + `/${filename}`;
}
if (!options.silent) {
if (!options?.silent) {
presentSheet({
title: strings.network.fileDownloaded(),
paragraph: strings.fileSaved(filename, Platform.OS),
icon: "download",
context: global ? null : attachment.hash,
context: global ? "global" : attachment.hash,
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
});
}
@@ -298,14 +327,26 @@ export default async function downloadAttachment(
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
.catch(() => {
/* empty */
});
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
.catch(() => {
/* empty */
});
}
useAttachmentStore.getState().setDownloading({
groupId: options?.groupId || attachment.hash,
current: 0,
total: 0,
filename: attachment.filename,
success: false
});
DatabaseLogger.error(e);
useAttachmentStore.getState().remove(attachment.hash);
if (options.throwError) {
if (options?.throwError) {
throw e;
}
}

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 { RequestOptions } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
@@ -26,7 +27,13 @@ import { DatabaseLogger, db } from "../database";
import { createCacheDir, exists } from "./io";
import { ABYTES, cacheDir, getUploadedFileSize, parseS3Error } from "./utils";
export async function downloadFile(filename, requestOptions, cancelToken) {
export async function downloadFile(
filename: string,
requestOptions: RequestOptions,
cancelToken: {
cancel: (reason?: string) => Promise<void>;
}
) {
if (!requestOptions) {
DatabaseLogger.log(
`Error downloading file: ${filename}, reason: No requestOptions`
@@ -36,9 +43,11 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(`Downloading ${filename}`);
await createCacheDir();
let { url, headers, chunkSize } = requestOptions;
let tempFilePath = `${cacheDir}/${filename}_temp`;
let originalFilePath = `${cacheDir}/${filename}`;
const { url, headers, chunkSize } = requestOptions;
const tempFilePath = `${cacheDir}/${filename}_temp`;
const originalFilePath = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
DatabaseLogger.log(`File Exists already: ${filename}`);
@@ -46,6 +55,8 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
}
const attachment = await db.attachments.attachment(filename);
if (!attachment) return false;
const size = await getUploadedFileSize(filename);
if (size === -1) {
@@ -71,21 +82,21 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
throw new Error(error);
}
let res = await fetch(url, {
const resolveUrlResponse = await fetch(url, {
method: "GET",
headers
});
if (!res.ok) {
if (!resolveUrlResponse.ok) {
DatabaseLogger.log(
`Error downloading file: ${filename}, ${res.status}, ${res.statusText}, reason: Unable to resolve download url`
`Error downloading file: ${filename}, ${resolveUrlResponse.status}, ${resolveUrlResponse.statusText}, reason: Unable to resolve download url`
);
throw new Error(
`${res.status}: ${strings.failedToResolvedDownloadUrl()}`
`${resolveUrlResponse.status}: ${strings.failedToResolvedDownloadUrl()}`
);
}
const downloadUrl = await res.text();
const downloadUrl = await resolveUrlResponse.text();
if (!downloadUrl) {
DatabaseLogger.log(
@@ -95,12 +106,12 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
}
DatabaseLogger.log(`Download starting: ${filename}`);
let request = RNFetchBlob.config({
const request = RNFetchBlob.config({
path: tempFilePath,
IOSBackgroundTask: true,
overwrite: true
})
.fetch("GET", downloadUrl, null)
.fetch("GET", downloadUrl)
.progress(async (recieved, total) => {
useAttachmentStore
.getState()
@@ -109,15 +120,16 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(`Downloading: ${filename}, ${recieved}/${total}`);
});
cancelToken.cancel = () => {
cancelToken.cancel = async (reason) => {
useAttachmentStore.getState().remove(filename);
request.cancel();
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
DatabaseLogger.log(`Download cancelled: ${filename}`);
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
/* empty */
});
DatabaseLogger.log(`Download cancelled: ${reason} ${filename}`);
};
let response = await request;
console.log(response.info().headers);
const response = await request;
const contentType =
response.info().headers?.["content-type"] ||
@@ -128,11 +140,13 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
throw new Error(`[${error.Code}] ${error.Message}`);
}
let status = response.info().status;
const status = response.info().status;
useAttachmentStore.getState().remove(filename);
if (exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
if (await exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
/* empty */
});
}
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
@@ -143,11 +157,14 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
return status >= 200 && status < 300;
} catch (e) {
if (e.message !== "canceled" && !e.message.includes("NoSuchKey")) {
if (
(e as Error).message !== "canceled" &&
!(e as Error).message.includes("NoSuchKey")
) {
const toast = {
heading: strings.downloadError(),
message: e.message,
type: "error",
heading: strings.downloadError((e as Error).message),
message: (e as Error).message,
type: "error" as const,
context: "global"
};
ToastManager.show(toast);
@@ -156,17 +173,20 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
}
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(tempFilePath).catch(console.log);
RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
DatabaseLogger.error(e, {
url,
headers
RNFetchBlob.fs.unlink(tempFilePath).catch(() => {
/* empty */
});
RNFetchBlob.fs.unlink(originalFilePath).catch(() => {
/* empty */
});
DatabaseLogger.error(e, "Download failed: ", {
url
});
return false;
}
}
export async function checkAttachment(hash) {
export async function checkAttachment(hash: string) {
const internetState = await NetInfo.fetch();
const isInternetReachable =
internetState.isConnected && internetState.isInternetReachable;
@@ -176,7 +196,7 @@ export async function checkAttachment(hash) {
try {
const size = await getUploadedFileSize(hash);
console.log("File Size", size);
if (size === -1) return { success: true };
if (size === 0)
@@ -184,7 +204,7 @@ export async function checkAttachment(hash) {
failed: `File length is 0. Please upload this file again from the attachment manager. (File hash: ${hash})`
};
} catch (e) {
return { failed: e?.message };
return { failed: (e as Error)?.message };
}
return { success: true };
}

View File

@@ -17,24 +17,41 @@ 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 { IFileStorage } from "@notesnook/core";
import { checkAttachment, downloadFile } from "./download";
import {
bulkExists,
clearCache,
clearFileStorage,
deleteCacheFileByName,
deleteCacheFileByPath,
deleteFile,
exists,
readEncrypted,
writeEncryptedBase64,
getCacheSize,
hashBase64,
readEncrypted,
writeEncryptedBase64
} from "./io";
import { uploadFile } from "./upload";
import {
cancelable,
checkAndCreateDir,
getUploadedFileSize,
requestPermission
} from "./utils";
export default {
checkAttachment,
clearCache,
deleteCacheFileByName,
deleteCacheFileByPath,
bulkExists,
getCacheSize
} from "./io";
import { uploadFile } from "./upload";
import { cancelable, getUploadedFileSize } from "./utils";
getCacheSize,
requestPermission,
checkAndCreateDir,
getUploadedFileSize
};
export default {
export const FileStorage: IFileStorage = {
readEncrypted,
writeEncryptedBase64,
hashBase64,
@@ -44,10 +61,5 @@ export default {
exists,
clearFileStorage,
getUploadedFileSize,
checkAttachment,
clearCache,
deleteCacheFileByName,
deleteCacheFileByPath,
bulkExists,
getCacheSize
bulkExists
};

View File

@@ -18,6 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import {
FileEncryptionMetadataWithHash,
FileEncryptionMetadataWithOutputType,
Output,
RequestOptions
} from "@notesnook/core";
import { DataFormat, SerializedKey } from "@notesnook/crypto";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { eSendEvent } from "../../services/event-manager";
@@ -25,17 +32,21 @@ import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { ABYTES, cacheDir, cacheDirOld, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
export async function readEncrypted<TOutputFormat extends DataFormat>(
filename: string,
key: SerializedKey,
cipherData: FileEncryptionMetadataWithOutputType<TOutputFormat>
) {
await migrateFilesFromCache();
DatabaseLogger.log("Read encrypted file...");
let path = `${cacheDir}/${filename}`;
const path = `${cacheDir}/${filename}`;
try {
if (!(await exists(filename))) {
return false;
return;
}
let output = await Sodium.decryptFile(
const output = await Sodium.decryptFile(
key,
{
...cipherData,
@@ -47,15 +58,16 @@ export async function readEncrypted(filename, key, cipherData) {
DatabaseLogger.log("File decrypted...");
return output;
return output as Output<TOutputFormat>;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
RNFetchBlob.fs.unlink(path).catch(() => {
/* empty */
});
DatabaseLogger.error(e);
return false;
}
}
export async function hashBase64(data) {
export async function hashBase64(data: string) {
const hash = await Sodium.hashFile({
type: "base64",
data,
@@ -67,61 +79,79 @@ export async function hashBase64(data) {
};
}
export async function writeEncryptedBase64(data, key) {
export async function writeEncryptedBase64(
data: string,
encryptionKey: SerializedKey,
mimeType: string
): Promise<FileEncryptionMetadataWithHash> {
await createCacheDir();
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
const filepath = cacheDir + `/${getRandomId("imagecache_")}`;
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
let output = await Sodium.encryptFile(key, {
const output = await Sodium.encryptFile(encryptionKey, {
uri: Platform.OS === "ios" ? filepath : "file://" + filepath,
type: "url"
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log("encrypted file output: ", output);
output.size = output.length;
delete output.length;
RNFetchBlob.fs.unlink(filepath).catch(() => {
/* empty */
});
return {
...output,
alg: "xcha-stream"
};
}
export async function deleteFile(filename, data) {
export async function deleteFile(
filename: string,
requestOptions?: RequestOptions
): Promise<boolean> {
await createCacheDir();
let delFilePath = cacheDir + `/${filename}`;
if (!data) {
if (!filename) return;
RNFetchBlob.fs.unlink(delFilePath).catch(console.log);
const localFilePath = cacheDir + `/${filename}`;
if (!requestOptions) {
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
/* empty */
});
return true;
}
let { url, headers } = data;
const { url, headers } = requestOptions;
try {
let response = await RNFetchBlob.fetch("DELETE", url, headers);
let status = response.info().status;
let ok = status >= 200 && status < 300;
const response = await RNFetchBlob.fetch("DELETE", url, headers);
const status = response.info().status;
const ok = status >= 200 && status < 300;
if (ok) {
RNFetchBlob.fs.unlink(delFilePath).catch(console.log);
RNFetchBlob.fs.unlink(localFilePath).catch(() => {
/* empty */
});
}
return ok;
} catch (e) {
console.log("delete file: ", e, url, headers);
DatabaseLogger.error(e, "Delete file", {
url: url
});
return false;
}
}
export async function clearFileStorage() {
try {
let files = await RNFetchBlob.fs.ls(cacheDir);
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
const files = await RNFetchBlob.fs.ls(cacheDir);
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (let file of files) {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
for (const file of files) {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(() => {
/* empty */
});
}
for (let file of oldCache) {
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
for (const file of oldCache) {
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(() => {
/* empty */
});
}
} catch (e) {
console.log("clearFileStorage", e);
DatabaseLogger.error(e, "clearFileStorage");
}
}
@@ -142,62 +172,71 @@ export async function migrateFilesFromCache() {
const migratedFilesPath = cacheDir + "/.migrated_1";
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
if (migrated) {
console.log("Files migrated already");
return;
}
let files = await RNFetchBlob.fs.ls(cacheDir);
console.log("Files to migrate:", files.join(","));
const files = await RNFetchBlob.fs.ls(cacheDir);
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (let file of oldCache) {
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (const file of oldCache) {
if (file.startsWith("org.") || file.startsWith("com.")) continue;
RNFetchBlob.fs
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
.catch(console.log);
console.log("Moved", file);
.catch(() => {
/* empty */
});
}
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
} catch (e) {
console.log("migrateFilesFromCache", e);
DatabaseLogger.error(e, "migrateFilesFromCache");
}
}
export async function clearCache() {
await RNFetchBlob.fs.unlink(cacheDir).catch(console.log);
await RNFetchBlob.fs.unlink(cacheDir).catch(() => {
/* empty */
});
await createCacheDir();
eSendEvent("cache-cleared");
}
export async function deleteCacheFileByPath(path) {
await RNFetchBlob.fs.unlink(path).catch(console.log);
export async function deleteCacheFileByPath(path: string) {
await RNFetchBlob.fs.unlink(path).catch(() => {
/* empty */
});
}
export async function deleteCacheFileByName(name) {
export async function deleteCacheFileByName(name: string) {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${name}`;
await RNFetchBlob.fs.unlink(appGroupPath).catch(console.log);
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(console.log);
await RNFetchBlob.fs.unlink(appGroupPath).catch(() => {
/* empty */
});
await RNFetchBlob.fs.unlink(`${cacheDir}/${name}`).catch(() => {
/* empty */
});
}
export async function deleteDCacheFiles() {
const files = await RNFetchBlob.fs.ls(cacheDir);
for (const file of files) {
if (file.includes("_dcache")) {
await RNFetchBlob.fs.unlink(file).catch(console.log);
if (file.includes("_dcache") || file.startsWith("NN_")) {
await RNFetchBlob.fs.unlink(file).catch(() => {
/* empty */
});
}
}
}
export async function exists(filename) {
let path = `${cacheDir}/${filename}`;
export async function exists(filename: string) {
const path = `${cacheDir}/${filename}`;
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
@@ -211,6 +250,7 @@ export async function exists(filename) {
if (exists || existsInAppGroup) {
const attachment = await db.attachments.attachment(filename);
if (!attachment) return false;
const totalChunks = Math.ceil(attachment.size / attachment.chunkSize);
const totalAbytes = totalChunks * ABYTES;
const expectedFileSize = attachment.size + totalAbytes;
@@ -225,7 +265,9 @@ export async function exists(filename) {
);
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);
.catch(() => {
/* empty */
});
return false;
}
@@ -234,7 +276,7 @@ export async function exists(filename) {
return exists;
}
export async function bulkExists(files) {
export async function bulkExists(files: string[]) {
try {
await createCacheDir();
const cacheFiles = await RNFetchBlob.fs.ls(cacheDir);
@@ -243,7 +285,7 @@ export async function bulkExists(files) {
if (Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupFiles = await RNFetchBlob.fs.ls(iosAppGroup);
missingFiles = missingFiles.filter(
@@ -261,9 +303,9 @@ export async function getCacheSize() {
await createCacheDir();
const stat = await RNFetchBlob.fs.lstat(`file://` + cacheDir);
let total = 0;
console.log("Total files", stat.length);
stat.forEach((s) => {
total += parseInt(s.size);
stat.forEach((file) => {
total += parseInt(file.size as unknown as string);
});
return total;
}

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 { RequestOptions } from "@notesnook/core";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { ToastManager } from "../../services/event-manager";
@@ -24,11 +25,22 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { IOS_APPGROUPID } from "../../utils/constants";
import { DatabaseLogger, db } from "../database";
import { createCacheDir } from "./io";
import { cacheDir, checkUpload, getUploadedFileSize } from "./utils";
import {
cacheDir,
checkUpload,
FileSizeResult,
getUploadedFileSize
} from "./utils";
export async function uploadFile(filename, requestOptions, cancelToken) {
export async function uploadFile(
filename: string,
requestOptions: RequestOptions,
cancelToken: {
cancel: (reason?: string) => Promise<void>;
}
) {
if (!requestOptions) return false;
let { url, headers } = requestOptions;
const { url, headers } = requestOptions;
await createCacheDir();
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
@@ -39,7 +51,7 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
if (!exists && Platform.OS === "ios") {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
filePath = appGroupPath;
@@ -54,14 +66,15 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
const fileSize = (await RNFetchBlob.fs.stat(filePath)).size;
let remoteFileSize = await getUploadedFileSize(filename);
if (remoteFileSize === -1) return false;
if (remoteFileSize > 0 && remoteFileSize === fileSize) {
const remoteFileSize = await getUploadedFileSize(filename);
if (remoteFileSize === FileSizeResult.Error) return false;
if (remoteFileSize > FileSizeResult.Empty && remoteFileSize === fileSize) {
DatabaseLogger.log(`File ${filename} is already uploaded.`);
return true;
}
let uploadUrlResponse = await fetch(url, {
const uploadUrlResponse = await fetch(url, {
method: "PUT",
headers
});
@@ -78,7 +91,8 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.info(`Starting upload: ${filename}`);
let uploadRequest = RNFetchBlob.config({
const uploadRequest = RNFetchBlob.config({
//@ts-ignore
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
})
.fetch(
@@ -98,14 +112,14 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
);
});
cancelToken.cancel = () => {
cancelToken.cancel = async () => {
useAttachmentStore.getState().remove(filename);
uploadRequest.cancel();
};
let uploadResponse = await uploadRequest;
let status = uploadResponse.info().status;
let uploaded = status >= 200 && status < 300;
const uploadResponse = await uploadRequest;
const status = uploadResponse.info().status;
const uploaded = status >= 200 && status < 300;
useAttachmentStore.getState().remove(filename);
@@ -118,12 +132,13 @@ export async function uploadFile(filename, requestOptions, cancelToken) {
);
}
const attachment = await db.attachments.attachment(filename);
if (!attachment) return false;
await checkUpload(filename, requestOptions.chunkSize, attachment.size);
DatabaseLogger.info(`File upload status: ${filename}, ${status}`);
return uploaded;
} catch (e) {
useAttachmentStore.getState().remove(filename);
ToastManager.error(e, "File upload failed");
ToastManager.error(e as Error, "File upload failed");
DatabaseLogger.error(e, "File upload failed", {
filename
});

View File

@@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { hosts, RequestOptions } from "@notesnook/core";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import * as ScopedStorage from "react-native-scoped-storage";
import { DatabaseLogger, db } from "../database";
import { hosts } from "@notesnook/core";
export const ABYTES = 17;
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
@@ -31,18 +31,13 @@ export const cacheDir =
? RNFetchBlob.fs.dirs.LibraryDir + "/.cache"
: RNFetchBlob.fs.dirs.DocumentDir + "/.cache";
export function getRandomId(prefix) {
export function getRandomId(prefix: string) {
return Math.random()
.toString(36)
.replace("0.", prefix || "");
}
/**
*
* @param {string | undefined} data
* @returns
*/
export function parseS3Error(data) {
export function parseS3Error(data?: string) {
const xml = typeof data === "string" ? data : null;
const error = {
@@ -70,11 +65,19 @@ export function parseS3Error(data) {
}
}
export function cancelable(operation) {
export function cancelable(
operation: (
filename: string,
requestOptions: RequestOptions,
cancelToken: {
cancel: (reason?: string) => Promise<void>;
}
) => Promise<boolean>
) {
const cancelToken = {
cancel: () => {}
cancel: async (reason?: string) => {}
};
return (filename, requestOptions) => {
return (filename: string, requestOptions: RequestOptions) => {
return {
execute: () => operation(filename, requestOptions, cancelToken),
cancel: async () => {
@@ -84,29 +87,35 @@ export function cancelable(operation) {
};
}
export function copyFileAsync(source, dest) {
export function copyFileAsync(source: string, dest: string) {
return new Promise((resolve, reject) => {
ScopedStorage.copyFile(source, dest, (e, r) => {
//@ts-ignore
ScopedStorage.copyFile(source, dest, (e: any, r: any) => {
if (e) {
reject(e);
return;
}
resolve();
resolve(true);
});
});
}
export async function releasePermissions(path) {
export async function releasePermissions(path: string) {
if (Platform.OS === "ios") return;
const uris = await ScopedStorage.getPersistedUriPermissions();
for (let uri of uris) {
for (const uri of uris) {
if (path.startsWith(uri)) {
await ScopedStorage.releasePersistableUriPermission(uri);
}
}
}
export async function getUploadedFileSize(hash) {
export const FileSizeResult = {
Empty: 0,
Error: -1
};
export async function getUploadedFileSize(hash: string, retry = 0) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
@@ -114,17 +123,37 @@ export async function getUploadedFileSize(hash) {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
if (
!attachmentInfo.ok ||
attachmentInfo.headers?.get("content-length") === null
) {
if (retry < 3) {
DatabaseLogger.log(`Retrying file size check: ${hash}, ${retry}`);
return getUploadedFileSize(hash, retry + 1);
}
throw new Error(
`File size check failed: ${hash}, ${
attachmentInfo.status
}, ${attachmentInfo.headers?.get("content-length")}`
);
}
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
attachmentInfo.headers?.get("content-length") as string
);
return isNaN(contentLength) ? 0 : contentLength;
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
} catch (e) {
DatabaseLogger.error(e);
return -1;
return FileSizeResult.Error;
}
}
export async function checkUpload(filename, chunkSize, expectedSize) {
export async function checkUpload(
filename: string,
chunkSize: number,
expectedSize: number
) {
const size = await getUploadedFileSize(filename);
const totalChunks = Math.ceil(size / chunkSize);
const decryptedLength = size - totalChunks * ABYTES;
@@ -138,3 +167,25 @@ export async function checkUpload(filename, chunkSize, expectedSize) {
: undefined;
if (error) throw new Error(error);
}
export async function requestPermission() {
if (Platform.OS === "ios") return true;
return true;
}
export async function checkAndCreateDir(path: string) {
const dir =
Platform.OS === "ios"
? RNFetchBlob.fs.dirs.DocumentDir + path
: RNFetchBlob.fs.dirs.SDCardDir + "/Notesnook/" + path;
try {
const exists = await RNFetchBlob.fs.exists(dir);
const isDir = await RNFetchBlob.fs.isDir(dir);
if (!exists || !isDir) {
await RNFetchBlob.fs.mkdir(dir);
}
} catch (e) {
await RNFetchBlob.fs.mkdir(dir);
}
return dir;
}

View File

@@ -17,9 +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/>.
*/
function info(context: string, ...logs: unknown[]) {
console.log(`${new Date().toLocaleDateString()}::info::${context}:`, ...logs);
}
function info(context: string, ...logs: unknown[]) {}
function error(context: string, ...logs: unknown[]) {
console.log(

View File

@@ -41,7 +41,9 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
await sleep(500);
}
if (item.type === "link") {
Linking.openURL(item.data).catch(console.log);
Linking.openURL(item.data).catch(() => {
/* empty */
});
} else if (item.type === "promo") {
presentSheet({
component: (

View File

@@ -112,14 +112,19 @@ const AppLockedOverlay = () => {
if (
!biometricsAuthEnabled ||
!(await BiometricService.isBiometryAvailable())
)
) {
return;
}
biometricUnlockAwaitingUserInput.current = true;
if (Platform.OS === "android") {
const activityName = await NotesnookModule.getActivityName();
if (activityName !== "MainActivity") return;
if (
activityName !== "MainActivity" &&
activityName !== "NotePreviewConfigureActivity"
)
return;
}
useSettingStore.getState().setRequestBiometrics(true);
@@ -180,7 +185,6 @@ const AppLockedOverlay = () => {
DatabaseLogger.info("Locking app on entering foreground");
useUserStore.getState().lockApp(true);
}
if (
!(
biometricUnlockAwaitingUserInput.current ||

View File

@@ -63,9 +63,11 @@ const Actions = ({
attachment,
close,
setAttachments,
fwdRef
fwdRef,
context
}: {
attachment: Attachment;
context: string;
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
close?: () => void;
fwdRef: RefObject<ActionSheetRef>;
@@ -79,7 +81,6 @@ const Actions = ({
const [loading, setLoading] = useState<{
name?: string;
}>({});
const actions = [
{
name: strings.network.download(),
@@ -88,7 +89,7 @@ const Actions = ({
await db.fs().cancel(attachment.hash);
useAttachmentStore.getState().remove(attachment.hash);
}
downloadAttachment(attachment.hash, false);
downloadAttachment(attachment.hash, context === "global");
fwdRef.current?.hide();
},
icon: "download"
@@ -181,18 +182,17 @@ const Actions = ({
relations
.map((relation) => relation.fromId)
.forEach(async (id) => {
const tab = useTabStore.getState().getTabForNote(id);
if (tab !== undefined) {
const isFocused = useTabStore.getState().currentTab === tab;
useTabStore.getState().forEachNoteTab(id, async (tab) => {
const isFocused = useTabStore.getState().currentTab === tab.id;
if (isFocused) {
eSendEvent(eOnLoadNote, {
item: await db.notes.note(id),
forced: true
});
} else {
editorController.current.commands.setLoading(true, tab);
editorController.current.commands.setLoading(true, tab.id);
}
}
});
});
close?.();
},
@@ -375,6 +375,7 @@ Actions.present = (
setAttachments={set}
close={close}
attachment={attachment}
context={context || "global"}
/>
)
});

View File

@@ -31,6 +31,7 @@ import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
import { strings } from "@notesnook/intl";
import { Pressable } from "../ui/pressable";
function getFileExtension(filename: string) {
const ext = /^.+\.([^.]+)$/.exec(filename);
@@ -69,8 +70,7 @@ export const AttachmentItem = ({
};
return errorOnly && attachment && !attachment?.failed ? null : (
<TouchableOpacity
activeOpacity={0.9}
<Pressable
onPress={onPress}
style={{
flexDirection: "row",
@@ -186,6 +186,6 @@ export const AttachmentItem = ({
)}
</>
)}
</TouchableOpacity>
</Pressable>
);
};

View File

@@ -206,7 +206,7 @@ export const AttachmentDialog = ({
errorOnly={currentFilter === "errors"}
attachments={attachments}
id={index}
context="global"
context={!isSheet ? "global" : "attachments-list"}
/>
);
@@ -336,10 +336,8 @@ export const AttachmentDialog = ({
onPress() {
if (!attachments) return;
presentDialog({
title: strings.doAction(
"attachment",
attachments.placeholders.length,
"download"
title: strings.doActions.download.attachment(
attachments.placeholders.length
),
positiveText: strings.network.download(),
positivePress: async () => {
@@ -390,10 +388,8 @@ export const AttachmentDialog = ({
onPress={() => {
if (!attachments) return;
presentDialog({
title: strings.doAction(
"attachment",
attachments.placeholders.length,
"download"
title: strings.doActions.download.attachment(
attachments.placeholders.length
),
positiveText: strings.network.download(),
positivePress: async () => {
@@ -532,7 +528,9 @@ export const AttachmentDialog = ({
setAttachments(results);
setLoading(false);
})
.catch(console.log);
.catch(() => {
/* empty */
});
}}
/>
)

View File

@@ -68,11 +68,15 @@ export const ChangePassword = () => {
}
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (!result.error)
const result = await BackupService.run(
false,
"change-password-dialog",
"partial"
);
if (result.error) {
throw new Error(strings.backupFailed() + `: ${result.error}`);
}
await db.user.clearSessions();
await db.user.changePassword(oldPassword.current, password.current);
ToastManager.show({
heading: strings.passwordChangedSuccessfully(),

View File

@@ -17,8 +17,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { db } from "../../common/database/index";
import useTimer from "../../hooks/use-timer";
import {
@@ -26,19 +29,14 @@ import {
presentSheet,
ToastManager
} from "../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { eCloseSheet } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import { Pressable } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import { ScrollView } from "react-native-actions-sheet";
import { strings } from "@notesnook/intl";
const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
const { colors } = useThemeColors();
@@ -164,7 +162,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
textAlign: "center"
}}
>
{strings["2faCodeHelpText"][currentMethod.method]() ||
{strings["2faCodeHelpText"][currentMethod.method]?.() ||
strings.select2faCodeHelpText()}
</Paragraph>

View File

@@ -70,7 +70,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
switch (step) {
case LoginSteps.emailAuth: {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(
async (mfa, callback) => {

View File

@@ -38,7 +38,7 @@ import { useAppState } from "../../hooks/use-app-state";
export interface BaseDialogProps extends PropsWithChildren {
animation?: "fade" | "none" | "slide" | undefined;
visible: boolean;
visible?: boolean;
onRequestClose?: () => void;
onShow?: () => void;
premium?: boolean;
@@ -75,9 +75,9 @@ const BaseDialog = ({
background
}: BaseDialogProps) => {
const floating = useIsFloatingKeyboard();
const appState = useAppState();
const lockEvents = useRef(false);
const [internalVisible, setIntervalVisible] = useState(true);
const locked = useUserStore((state) => state.appLocked);
useEffect(() => {
return () => {
@@ -86,26 +86,20 @@ const BaseDialog = ({
}, []);
useEffect(() => {
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.canLockAppInBackground()) {
if (appState === "background") {
setIntervalVisible(false);
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
setIntervalVisible(true);
unsub();
setTimeout(() => {
lockEvents.current = false;
});
}
if (locked) {
setIntervalVisible(false);
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
setIntervalVisible(true);
unsub();
setTimeout(() => {
lockEvents.current = false;
});
}
}
});
}
}, [appState]);
}, [locked]);
const Wrapper = useSafeArea ? SafeAreaView : View;

View File

@@ -80,8 +80,9 @@ export const Dialog = ({ context = "global" }) => {
return;
}
}
hide();
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
};
const show = useCallback(
@@ -104,13 +105,14 @@ export const Dialog = ({ context = "global" }) => {
eUnSubscribeEvent(eOpenSimpleDialog, show);
eUnSubscribeEvent(eCloseSimpleDialog, hide);
};
}, [show]);
}, [hide, show]);
const hide = () => {
const hide = React.useCallback(() => {
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
};
dialogInfo.onClose?.();
}, [dialogInfo]);
const onNegativePress = async () => {
if (dialogInfo.onClose) {

View File

@@ -25,7 +25,7 @@ import {
View
} from "react-native";
import { Image as ImageType } from "react-native-image-crop-picker";
import { useThemeColors } from "../../../../../../packages/theme/dist";
import { useThemeColors } from "@notesnook/theme";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
@@ -180,7 +180,7 @@ AttachImage.present = (response: ImageType[], context?: string) => {
close={close}
onAttach={(result) => {
resolved = true;
console.log("closing");
resolve(result);
close?.();
}}

View File

@@ -179,6 +179,7 @@ const ColorPicker = ({
title: title.current,
colorCode: selectedColor
});
if (!id) return;
useRelationStore.getState().update();
useMenuStore.getState().setColorNotes();
setVisible(false);

View File

@@ -188,7 +188,7 @@ const PDFPreview = () => {
}}
color={colors.static.white}
>
{strings.loadingWithProgress(progress.percent)}
{strings.loadingWithProgress(progress?.percent)}
</Paragraph>
</Animated.View>
) : (
@@ -307,9 +307,7 @@ const PDFPreview = () => {
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {
console.log(`Link pressed: ${uri}`);
}}
onPressLink={(uri) => {}}
style={{
flex: 1,
width: width,

View File

@@ -62,7 +62,7 @@ export default function Progress() {
eSubscribeEvent(PROGRESS_EVENTS.start, (options: ProgressOptions) => {
setProgress(options.progress);
cancelCallback.current = options.cancelCallback;
console.log("options", options.fillBackground);
setData({
title: options.title,
paragraph: options.paragraph,
@@ -164,7 +164,7 @@ export default function Progress() {
/>
</View>
{data?.canHideProgress ? null : (
{!data?.canHideProgress ? null : (
<Button
title={cancelCallback.current ? "Cancel" : "Hide"}
type="secondaryAccented"

View File

@@ -311,8 +311,10 @@ export class VaultDialog extends Component {
loading: true
});
try {
let verified = await db.user.verifyPassword(this.password);
if (!(await db.user.getUser())) verified = true;
let verified = true;
if (await db.user.getUser()) {
verified = await db.user.verifyPassword(this.password);
}
if (verified) {
let noteIds = [];
if (this.state.deleteAll) {
@@ -320,19 +322,20 @@ export class VaultDialog extends Component {
const relations = await db.relations.from(vault, "note").get();
noteIds = relations.map((item) => item.toId);
}
await db.vault.delete(this.state.deleteAll);
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
if (this.state.deleteAll) {
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
}
eSendEvent("vaultUpdated");
this.setState({
@@ -403,7 +406,6 @@ export class VaultDialog extends Component {
} else {
await db.vault.add(this.state.note.id);
console.log("update note event...");
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
@@ -457,7 +459,7 @@ export class VaultDialog extends Component {
async _deleteNote() {
try {
await db.vault.remove(this.state.note.id, this.password);
await deleteItems([this.state.note.id], "note");
await deleteItems("note", [this.state.note.id]);
this.close();
} catch (e) {
this._takeErrorAction(e);
@@ -517,7 +519,6 @@ export class VaultDialog extends Component {
});
this.close();
} else {
eSendEvent("vaultUpdated");
ToastManager.show({
heading: strings.vaultCreated(),
type: "success",
@@ -525,6 +526,7 @@ export class VaultDialog extends Component {
});
this.close();
}
eSendEvent("vaultUpdated");
}
_permanantUnlock() {
@@ -540,7 +542,6 @@ export class VaultDialog extends Component {
this.close();
})
.catch((e) => {
console.log("Error", e);
this._takeErrorAction(e);
});
}

View File

@@ -78,10 +78,10 @@ class ExceptionHandler extends React.Component<{
}
export const withErrorBoundry = (Element: React.ElementType, name: string) => {
return function ErrorBoundary() {
return function ErrorBoundary(props: any) {
return (
<ExceptionHandler component={name}>
<Element />
<Element {...props} />
</ExceptionHandler>
);
};

View File

@@ -33,7 +33,6 @@ import { eScrollEvent } from "../../utils/events";
import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus";
import { Title } from "./title";
import { useNavigation } from "@react-navigation/native";
type HeaderRightButton = {
title: string;
@@ -55,7 +54,7 @@ export const Header = ({
onSearch
}: {
onLeftMenuButtonPress?: () => void;
renderedInRoute: RouteName;
renderedInRoute?: RouteName;
id?: string;
title: string;
headerRightButtons?: HeaderRightButton[];

View File

@@ -19,9 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
//@ts-ignore
import { useThemeColors } from "@notesnook/theme";
import Menu from "react-native-reanimated-material-menu";
import { Menu } from "react-native-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import {
HeaderRightButton,
@@ -42,7 +41,7 @@ export const RightMenus = ({
onSearch
}: {
headerRightButtons?: HeaderRightButton[];
renderedInRoute: RouteName;
renderedInRoute?: RouteName;
id?: string;
onPressDefaultRightButton?: () => void;
search?: boolean;
@@ -97,14 +96,16 @@ export const RightMenus = ({
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -40
marginTop: 35
}}
onRequestClose={() => {
//@ts-ignore
menuRef.current?.hide();
}}
anchor={
<IconButton
onPress={() => {
//@ts-ignore
menuRef.current?.show();
}}
name="dots-vertical"
@@ -116,9 +117,10 @@ export const RightMenus = ({
{headerRightButtons.map((item) => (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
type="plain"
buttonType={{
@@ -127,6 +129,7 @@ export const RightMenus = ({
key={item.title}
title={item.title}
onPress={async () => {
//@ts-ignore
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();

View File

@@ -41,7 +41,7 @@ export const Title = ({
isHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
renderedInRoute: string;
renderedInRoute?: string;
id?: string;
}) => {
const { colors } = useThemeColors();

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import { DataURL } from "@notesnook/core";
import { DataURL, getFileNameWithExtension } from "@notesnook/core";
import type { ImageAttributes } from "@notesnook/editor";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
@@ -34,6 +34,11 @@ import {
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import RNFetchBlob from "react-native-blob-util";
import Share from "react-native-share";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../stores/use-setting-store";
const ImagePreview = () => {
const { colors } = useThemeColors("dialog");
@@ -41,6 +46,8 @@ const ImagePreview = () => {
const [image, setImage] = useState<string>();
const [loading, setLoading] = useState(false);
const imageRef = useRef<ImageAttributes>();
const insets = useGlobalSafeAreaInsets();
const [showHeader, setShowHeader] = useState(true);
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
@@ -73,16 +80,86 @@ const ImagePreview = () => {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
if (!uri) {
setLoading(false);
return;
}
const attachment = await db.attachments.attachment(hash);
const path = `${cacheDir}/${
"NN_" + (await getFileNameWithExtension(hash, attachment?.mimeType))
}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, path).catch(() => {
/* empty */
});
setImage("file://" + path);
setLoading(false);
}, 100);
};
const close = () => {
const close = React.useCallback(() => {
image &&
RNFetchBlob.fs.unlink(image.replace("file://", "")).catch(() => {
/* empty */
});
setImage(undefined);
setVisible(false);
};
}, [image]);
const renderHeader = React.useCallback(
() => (
<View
style={{
paddingTop: insets.top,
backgroundColor: "rgba(0,0,0,0.3)",
position: "absolute",
zIndex: 999,
display: showHeader ? "flex" : "none"
}}
>
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 50,
paddingHorizontal: 12,
gap: 10
}}
>
<IconButton
name="share"
color="white"
style={{
borderWidth: 0
}}
onPress={async () => {
useSettingStore
.getState()
.setAppDidEnterBackgroundForAction(true);
await Share.open({
url: image
}).catch(() => {
/* empty */
});
useSettingStore
.getState()
.setAppDidEnterBackgroundForAction(false);
}}
/>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
/>
</View>
</View>
),
[close, image, insets.top, showHeader]
);
return (
visible && (
@@ -90,6 +167,7 @@ const ImagePreview = () => {
background="black"
animation="slide"
visible={true}
useSafeArea={false}
onRequestClose={close}
transparent
>
@@ -130,43 +208,25 @@ const ImagePreview = () => {
/>
</View>
) : (
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
renderHeader={() => (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 50,
paddingHorizontal: 24,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
marginTop: Platform.OS === "android" ? 30 : 0
}}
>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
/>
</View>
)}
imageUrls={[
{
url: image as string
}
]}
/>
<>
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
onClick={() => {
setShowHeader(!showHeader);
}}
renderHeader={renderHeader}
imageUrls={[
{
url: image as string
}
]}
/>
</>
)}
</View>
</BaseDialog>

View File

@@ -17,30 +17,23 @@ 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 { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import {
Linking,
ScrollView,
TouchableOpacity,
useWindowDimensions,
View
} from "react-native";
import { Linking, ScrollView, useWindowDimensions, View } from "react-native";
import { SwiperFlatList } from "react-native-swiper-flatlist";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { getElevationStyle } from "../../utils/elevation";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation";
import { eOpenLoginDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { AuthMode } from "../auth";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import Navigation from "../../services/navigation";
import { eOpenLoginDialog } from "../../utils/events";
import { AuthMode } from "../auth";
import { eSendEvent } from "../../services/event-manager";
import { strings } from "@notesnook/intl";
const Intro = ({ navigation }) => {
const { colors } = useThemeColors();
@@ -90,7 +83,7 @@ const Intro = ({ navigation }) => {
>
{item.headings?.map((heading) => (
<Heading
key={heading}
key={heading()}
style={{
marginBottom: 5
}}
@@ -135,7 +128,6 @@ const Intro = ({ navigation }) => {
style={{
width: deviceMode !== "mobile" ? width / 2 : "100%",
backgroundColor: colors.secondary.background,
marginBottom: 20,
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
@@ -145,7 +137,7 @@ const Intro = ({ navigation }) => {
marginTop: deviceMode !== "mobile" ? 50 : null,
paddingTop: insets.top + 10,
paddingBottom: insets.top + 10,
minHeight: height * 0.7
minHeight: height * 0.7 - (insets.top + insets.bottom)
}}
>
<SwiperFlatList

View File

@@ -57,10 +57,7 @@ export const SectionHeader = React.memo<
}: SectionHeaderProps) {
const { colors } = useThemeColors();
const { fontScale } = useWindowDimensions();
const groupBy =
strings.groupByStrings[
groupOptions.groupBy as keyof typeof strings.groupByStrings
]?.();
const groupBy = strings.groupByStrings[groupOptions.groupBy]();
const isCompactModeEnabled = useIsCompactModeEnabled(
dataType as "note" | "notebook"
);

View File

@@ -81,7 +81,8 @@ const ReminderItem = React.memo(
<View
style={{
flexDirection: "row",
flexWrap: "wrap"
flexWrap: "wrap",
marginTop: 5
}}
>
{item.disabled ? (
@@ -149,10 +150,8 @@ const ReminderItem = React.memo(
fontSize={SIZE.xs}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
marginTop: 5
height: 25,
alignSelf: "flex-start"
}}
/>
</View>

View File

@@ -70,6 +70,7 @@ const SelectionWrapper = ({
}
const onLongPress = () => {
if (isSheet) return;
if (useSelectionStore.getState().selectionMode !== item.type) {
useSelectionStore.getState().setSelectionMode(item.type);
}
@@ -78,7 +79,7 @@ const SelectionWrapper = ({
return (
<Pressable
customColor={isSheet ? colors.primary.hover : "transparent"}
customColor={isSheet ? colors.secondary.background : "transparent"}
testID={testID}
onLongPress={onLongPress}
onPress={onPress}

View File

@@ -34,12 +34,13 @@ import {
Reminder,
Tag,
TrashItem,
VirtualizedGrouping
VirtualizedGrouping,
getSortValue
} from "@notesnook/core";
import { getSortValue } from "@notesnook/core";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
import { eSendEvent } from "../../services/event-manager";
import { RouteName } from "../../stores/use-navigation-store";
import { eOpenJumpToDialog } from "../../utils/events";
@@ -48,7 +49,6 @@ import { NoteWrapper } from "../list-items/note/wrapper";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import ReminderItem from "../list-items/reminder";
import TagItem from "../list-items/tag";
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
type ListItemWrapperProps<TItem = Item> = {
group?: GroupingKey;
@@ -153,9 +153,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
},
items?.cacheItem(index) ? 100 : 0
);
} catch (e) {
console.log("Error", e);
}
} catch (e) {}
})();
}, [index, items, refreshItem]);
@@ -297,7 +295,6 @@ function getDate(item: Item, groupType?: GroupingKey): number {
groupType
? db.settings.getGroupOptions(groupType)
: {
groupBy: "default",
sortBy: "dateEdited",
sortDirection: "desc"
},

View File

@@ -119,7 +119,6 @@ export const PricingPlans = ({
setLoading(false);
} catch (e) {
setLoading(false);
console.log("error getting sku", e);
}
}, [promo?.promoCode]);
@@ -129,7 +128,10 @@ export const PricingPlans = ({
if (code.startsWith("com.streetwriters.notesnook")) {
skuId = code;
} else {
skuId = await db.offers?.getCode(code.split(":")[0], Platform.OS);
skuId = await db.offers?.getCode(
code.split(":")[0],
Platform.OS as "ios" | "android"
);
}
const products = await PremiumService.getProducts();
@@ -168,7 +170,6 @@ export const PricingPlans = ({
});
return true;
} catch (e) {
console.log("PROMOCODE ERROR:", code, e);
return false;
}
};
@@ -228,7 +229,6 @@ export const PricingPlans = ({
});
} catch (e) {
setBuying(false);
console.log(e);
}
};

View File

@@ -141,7 +141,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
close();
eSendEvent(eOnLoadNote, {
item: item,
presistTab: true
newTab: true
});
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);

View File

@@ -137,14 +137,13 @@ export const Items = ({ item, buttons, close }) => {
style={{
alignSelf: "flex-start",
paddingHorizontal: 0,
width: topBarItemWidth
flex: 1
}}
>
<View
onPress={item.func}
style={{
height: topBarItemWidth,
width: topBarItemWidth,
justifyContent: "center",
alignItems: "center",
textAlign: "center",
@@ -169,7 +168,7 @@ export const Items = ({ item, buttons, close }) => {
<Paragraph
textBreakStrategy="simple"
size={SIZE.xxs + 1}
size={SIZE.xxs}
style={{ textAlign: "center" }}
>
{item.title}
@@ -242,7 +241,7 @@ export const Items = ({ item, buttons, close }) => {
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
paddingRight: 25,
gap: 5
gap: 15
}}
>
{topBarItems.map(renderTopBarItem)}

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef } from "react";
import {
@@ -26,7 +27,7 @@ import {
Platform,
View
} from "react-native";
import Menu from "react-native-reanimated-material-menu/src/Menu";
import { Menu } from "react-native-material-menu";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastManager } from "../../services/event-manager";
@@ -46,7 +47,6 @@ import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
import { strings } from "@notesnook/intl";
export const SelectionHeader = React.memo(
({
@@ -101,10 +101,11 @@ export const SelectionHeader = React.memo(
};
const deleteItem = async () => {
if (!type) return;
presentDialog({
title: strings.deleteItems(type as string, selectedItemsList.length),
paragraph: strings.deleteItemsConfirmation(
type as string,
title: strings.doActions.delete.unknown(type, selectedItemsList.length),
paragraph: strings.actionConfirmations.delete.unknown(
type,
selectedItemsList.length
),
positiveText: strings.delete(),
@@ -219,14 +220,16 @@ export const SelectionHeader = React.memo(
style={{
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -20
marginTop: 35
}}
onRequestClose={() => {
//@ts-ignore
menuRef.current?.hide();
}}
anchor={
<IconButton
onPress={() => {
//@ts-ignore
menuRef.current?.show();
}}
name="dots-vertical"
@@ -293,15 +296,35 @@ export const SelectionHeader = React.memo(
{
title: strings.moveToTrash(),
onPress: async () => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
const selection = useSelectionStore.getState();
if (!selection.selectionMode) return;
await deleteItems(
selection.selectionMode as ItemType,
selection.selectedItemsList
);
selection.clearSelection();
selection.setSelectionMode(undefined);
},
visible: type !== "trash",
visible: type === "note" || type === "notebook",
icon: "delete"
},
{
title: strings.doActions.delete.unknown(
type!,
selectedItemsList.length
),
onPress: async () => {
const selection = useSelectionStore.getState();
if (!selection.selectionMode) return;
await deleteItems(
selection.selectionMode as ItemType,
selection.selectedItemsList
);
selection.clearSelection();
selection.setSelectionMode(undefined);
},
visible:
type !== "trash" && type !== "note" && type !== "notebook",
icon: "delete"
},
{
@@ -320,9 +343,10 @@ export const SelectionHeader = React.memo(
!item.visible ? null : (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
borderRadius: 0,
alignSelf: "flex-start",
width: "100%"
}}
type="plain"
buttonType={{
@@ -332,6 +356,7 @@ export const SelectionHeader = React.memo(
key={item.title}
title={item.title}
onPress={async () => {
//@ts-ignore
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();

View File

@@ -81,7 +81,7 @@ export const AddNotebookSheet = ({
id: notebook?.id
});
if (parentNotebook) {
if (parentNotebook && id) {
await db.relations.add(parentNotebook, {
type: "notebook",
id: id
@@ -94,17 +94,17 @@ export const AddNotebookSheet = ({
useNotebookStore.getState().refresh();
const parent =
parentNotebook?.id || (await getParentNotebookId(notebook?.id || id));
parentNotebook?.id ||
(await getParentNotebookId(notebook?.id || (id as string)));
eSendEvent(eOnNotebookUpdated, parent);
if (notebook) {
setImmediate(() => {
console.log(parent, notebook.id);
eSendEvent(eOnNotebookUpdated, notebook.id);
});
}
if (!notebook && showMoveNotesOnComplete) {
if (!notebook && showMoveNotesOnComplete && id) {
MoveNotes.present(await db.notebooks.notebook(id));
} else {
close?.(true);

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, {
RefObject,
@@ -49,13 +50,11 @@ import DialogHeader from "../../dialog/dialog-header";
import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import { NotebookItem } from "./notebook-item";
import { useNotebookItemSelectionStore } from "./store";
import { AddNotebookSheet } from "../add-notebook";
import Input from "../../ui/input";
import { presentDialog } from "../../dialog/functions";
import { strings } from "@notesnook/intl";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations

View File

@@ -90,13 +90,12 @@ export const NotebookItem = ({
for (const key in keys) {
nextState[key] = !state.initialState[key] ? undefined : "deselected";
}
console.log("Single item selection");
state.setSelection({
[item.id]: "selected",
...nextState
});
} else {
console.log("Multi item selection");
state.markAs(item, "selected");
}
};

View File

@@ -134,8 +134,10 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
) : (
<>
<Input
key="code-input"
fwdRef={codeInputRef}
placeholder={strings.verifyNewEmail()}
placeholder={strings.code()}
defaultValue=""
onChangeText={(code) => {
emailChangeData.current.code = code;
}}

View File

@@ -16,8 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import { EVENTS } from "@notesnook/core";
import { EVENTS, Note } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
@@ -25,27 +25,18 @@ import { FlatList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import {
TabItem,
useTabStore
} from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { eUnlockNote } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
type TabItem = {
id: number;
noteId?: string;
previewTab?: boolean;
locked?: boolean;
noteLocked?: boolean;
readonly?: boolean;
pinned?: boolean;
};
const TabItemComponent = (props: {
tab: TabItem;
@@ -53,7 +44,7 @@ const TabItemComponent = (props: {
close?: (ctx?: string | undefined) => void;
}) => {
const { colors } = useThemeColors();
const [item, update] = useDBItem(props.tab.noteId, "note");
const [item, update] = useDBItem(props.tab.session?.noteId, "note");
useEffect(() => {
const syncCompletedSubscription = db.eventManager?.subscribe(
@@ -85,11 +76,11 @@ const TabItemComponent = (props: {
onPress={() => {
if (!props.isFocused) {
useTabStore.getState().focusTab(props.tab.id);
if (props.tab.locked) {
if (props.tab.session?.locked) {
eSendEvent(eUnlockNote);
}
if (!props.tab.noteId) {
if (!props.tab.session?.noteId) {
setTimeout(() => {
editorController?.current?.commands?.focus(props.tab.id);
}, 300);
@@ -97,11 +88,6 @@ const TabItemComponent = (props: {
}
props.close?.();
}}
onLongPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
previewTab: false
});
}}
>
<View
style={{
@@ -112,9 +98,9 @@ const TabItemComponent = (props: {
flexShrink: 1
}}
>
{props.tab.noteLocked ? (
{props.tab.session?.noteLocked ? (
<>
{props.tab.locked ? (
{props.tab.session?.locked ? (
<Icon size={SIZE.md} name="lock" />
) : (
<Icon size={SIZE.md} name="lock-open-outline" />
@@ -122,7 +108,9 @@ const TabItemComponent = (props: {
</>
) : null}
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
{props.tab.session?.readonly ? (
<Icon size={SIZE.md} name="pencil-lock" />
) : null}
<Paragraph
color={
@@ -130,15 +118,10 @@ const TabItemComponent = (props: {
? colors.selected.paragraph
: colors.primary.paragraph
}
style={{
fontFamily: props.tab.previewTab
? "OpenSans-Italic"
: "OpenSans-Regular"
}}
numberOfLines={1}
size={SIZE.md}
>
{props.tab.noteId
{props.tab.session?.noteId
? item?.title || strings.untitledNote()
: strings.newNote()}
</Paragraph>
@@ -157,8 +140,7 @@ const TabItemComponent = (props: {
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
onPress={() => {
useTabStore.getState().updateTab(props.tab.id, {
pinned: !props.tab.pinned,
previewTab: false
pinned: !props.tab.pinned
});
}}
top={0}
@@ -197,6 +179,7 @@ export default function EditorTabs({
}: {
close?: (ctx?: string | undefined) => void;
}) {
const { colors } = useThemeColors();
const [tabs, currentTab] = useTabStore((state) => [
state.tabs,
state.currentTab
@@ -233,25 +216,13 @@ export default function EditorTabs({
}}
>
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
<Button
<IconButton
onPress={() => {
useTabStore.getState().newTab();
setTimeout(() => {
editorController?.current?.commands?.focus(
useTabStore.getState().currentTab
);
}, 500);
close?.();
}}
title={strings.newTab()}
icon="plus"
style={{
flexDirection: "row",
justifyContent: "flex-start",
borderRadius: 100,
height: 35
}}
iconSize={SIZE.lg}
name="plus"
color={colors.primary.accent}
/>
</View>

View File

@@ -171,7 +171,7 @@ const ExportNotesSheet = ({
>
<DialogHeader
icon="export"
title={`${strings.export()} ${strings.notes(ids.length)}`}
title={strings.exportNotes(ids.length)}
/>
</View>
@@ -333,13 +333,17 @@ const ExportNotesSheet = ({
if (Platform.OS === "ios") {
Share.open({
url: result?.fileDir + result.fileName
}).catch(console.log);
}).catch(() => {
/* empty */
});
} else {
FileViewer.open(result.filePath, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
} as any).catch(console.log);
} as any).catch(() => {
/* empty */
});
}
}}
/>

View File

@@ -62,7 +62,9 @@ export const ShareComponent = ({ uri, name, padding }) => {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(console.log);
}).catch(() => {
/* empty */
});
}}
/>
</View>

View File

@@ -22,24 +22,22 @@ import {
VirtualizedGrouping,
createInternalLink
} from "@notesnook/core";
import type { LinkAttributes } from "@notesnook/editor";
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { useDBItem } from "../../../hooks/use-db-item";
import { editorController } from "../../../screens/editor/tiptap/utils";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import type { LinkAttributes } from "@notesnook/editor";
import {
EditorEvents,
editorController
} from "../../../screens/editor/tiptap/utils";
import { strings } from "@notesnook/intl";
const ListNoteItem = ({
id,
@@ -194,7 +192,7 @@ export default function LinkNote(props: {
}
: undefined
);
editorController.current?.postMessage(EditorEvents.resolve, {
editorController.current?.postMessage(NativeEvents.resolve, {
data: {
href: link,
title: selectedNote.title

View File

@@ -113,11 +113,9 @@ const ManageTagsSheet = (props: {
.sorted()
.then((items) => {
setTags(items);
console.log("searched tags");
});
} else {
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
console.log("items loaded tags", items.placeholders.length);
setTags(items);
});
}

View File

@@ -43,7 +43,7 @@ const NewFeature = ({
version
}: {
features: FeatureType[];
version?: string | null;
version?: string;
}) => {
const { colors } = useThemeColors();
@@ -57,7 +57,7 @@ const NewFeature = ({
}}
>
<Heading color={colors.secondary.heading} size={SIZE.md}>
{!version ? `${strings.newVersion()} ` : `v${version} `}Highlights 🎉
{strings.newVersionHighlights(version)}
</Heading>
<Seperator />
@@ -122,7 +122,7 @@ NewFeature.present = () => {
component: (
<NewFeature
features={features}
version={SettingsService.getProperty("version")}
version={SettingsService.getProperty("version") || undefined}
/>
),
disableClosing: true

View File

@@ -206,7 +206,7 @@ export const NotebookSheet = () => {
backgroundInteractionEnabled
gestureEnabled
>
<View
{/* <View
style={{
position: "absolute",
right: 24 + normalize(50),
@@ -214,17 +214,11 @@ export const NotebookSheet = () => {
}}
>
<Pressable
testID={notesnook.buttons.add}
testID="add-notebook-button"
type="secondary"
onPress={() => {
if (!notebook) return;
AddNotebookSheet.present(
undefined,
notebook,
undefined,
undefined,
false
);
}}
style={{
borderRadius: 100
@@ -245,7 +239,7 @@ export const NotebookSheet = () => {
/>
</View>
</Pressable>
</View>
</View> */}
<View
style={{
@@ -307,8 +301,8 @@ export const NotebookSheet = () => {
}}
onPress={async () => {
await deleteItems(
useItemSelectionStore.getState().getSelectedItemIds(),
"notebook"
"notebook",
useItemSelectionStore.getState().getSelectedItemIds()
);
useSelectionStore.getState().clearSelection();
useItemSelectionStore.setState({
@@ -404,6 +398,27 @@ export const NotebookSheet = () => {
height: 40 * fontScale
}}
/>
<IconButton
testID="add-notebook-button"
name="notebook-plus"
onPress={() => {
if (!notebook) return;
AddNotebookSheet.present(
undefined,
notebook,
undefined,
undefined,
false
);
}}
color={colors.primary.icon}
size={22}
style={{
width: 40 * fontScale,
height: 40 * fontScale
}}
/>
</>
)}
</View>

View File

@@ -121,8 +121,8 @@ const PublishNoteSheet = ({
}}
>
<DialogHeader
title={`${strings.publish()} ${strings.note()}`}
paragraph={strings.publishDesc()}
title={strings.publishNote()}
paragraph={strings.publishNoteDesc()}
/>
{publishing ? (
@@ -150,7 +150,7 @@ const PublishNoteSheet = ({
</View>
) : (
<>
{isPublished && (
{isPublished && publishUrl ? (
<View
style={{
flexDirection: "row",
@@ -204,7 +204,7 @@ const PublishNoteSheet = ({
name="content-copy"
/>
</View>
)}
) : null}
<TouchableOpacity
onPress={() => {

View File

@@ -17,12 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { sanitizeFilename } from "@notesnook/common";
import { strings } from "@notesnook/intl";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { createRef } from "react";
import { Platform, View } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import FileViewer from "react-native-file-viewer";
import * as ScopedStorage from "react-native-scoped-storage";
import Share from "react-native-share";
import { db } from "../../../common/database";
import filesystem from "../../../common/filesystem";
import {
eSubscribeEvent,
eUnSubscribeEvent,
@@ -30,8 +35,6 @@ import {
} from "../../../services/event-manager";
import { clearMessage } from "../../../services/message";
import SettingsService from "../../../services/settings";
import { db } from "../../../common/database";
import Storage from "../../../common/database/storage";
import { eOpenRecoveryKeyDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
@@ -41,9 +44,6 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import { QRCode } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import RNFetchBlob from "react-native-blob-util";
import { sanitizeFilename } from "@notesnook/common";
import { strings } from "@notesnook/intl";
class RecoveryKeySheet extends React.Component {
constructor(props) {
@@ -126,7 +126,7 @@ class RecoveryKeySheet extends React.Component {
"base64"
);
} else {
path = await Storage.checkAndCreateDir("/");
path = await filesystem.checkAndCreateDir("/");
await RNFetchBlob.fs.writeFile(path + fileName, data, "base64");
}
ToastManager.show({
@@ -157,7 +157,7 @@ class RecoveryKeySheet extends React.Component {
if (!file) return;
path = file.uri;
} else {
path = await Storage.checkAndCreateDir("/");
path = await filesystem.checkAndCreateDir("/");
await RNFetchBlob.fs.writeFile(path + fileName, this.state.key, "utf8");
path = path + fileName;
}
@@ -191,13 +191,17 @@ class RecoveryKeySheet extends React.Component {
Share.open({
url: path,
failOnCancel: false
}).catch(console.log);
}).catch(() => {
/* empty */
});
} else {
FileViewer.open(path, {
showOpenWithDialog: true,
showAppsSuggestions: true,
shareFile: true
}).catch(console.log);
}).catch(() => {
/* empty */
});
}
} catch (e) {
console.error(e);

View File

@@ -273,7 +273,7 @@ const ListNoteItem = ({
listType,
noteInternalLinks.length
]);
console.log(noteInternalLinks);
const renderBlock = React.useCallback(
(block: ContentBlock) => (
<ListBlockItem
@@ -419,7 +419,6 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
id={index}
items={items}
onSelect={(note, blockId) => {
console.log(note.id, blockId);
eSendEvent(eOnLoadNote, {
item: note,
blockId: blockId

View File

@@ -16,8 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { VirtualizedGrouping } from "@notesnook/core";
import { Item, ItemReference } from "@notesnook/core";
import { Item, ItemReference, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
@@ -37,8 +37,6 @@ import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { PressableProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { isStringLiteralOrJsxExpression } from "typescript";
import { strings } from "@notesnook/intl";
type RelationsListProps = {
actionSheetRef: RefObject<ActionSheetRef>;
@@ -90,14 +88,14 @@ export const RelationsList = ({
sortDirection: "desc"
})
.then((grouped) => {
setItems(grouped);
setTimeout(() => {
setItems(grouped);
}, 300);
});
}, [relationType, referenceType, item?.id, item?.type]);
}, [relationType, referenceType, item?.id, item?.type, updater]);
return (
<View
style={{ paddingHorizontal: 12, height: hasNoRelations ? 300 : "100%" }}
>
<View style={{ paddingHorizontal: 12, height: "100%" }}>
<SheetProvider context="local" />
<DialogHeader
title={title}
@@ -125,7 +123,9 @@ export const RelationsList = ({
// width="100%"
type="inverted"
icon="plus"
title={strings.addItem(referenceType)}
title={strings.addItem(
referenceType as "notebook" | "tag" | "reminder" | "note"
)}
/>
</View>
) : (

View File

@@ -68,19 +68,19 @@ export default function ReminderNotify({
const QuickActions = [
{
title: `5 ${strings.timeShort.minute}`,
title: `5 ${strings.timeShort.minute()}`,
time: 5
},
{
title: `15 ${strings.timeShort.minute}`,
title: `15 ${strings.timeShort.minute()}`,
time: 15
},
{
title: `30 ${strings.timeShort.minute}`,
title: `30 ${strings.timeShort.minute()}`,
time: 30
},
{
title: `1 ${strings.timeShort.hour}`,
title: `1 ${strings.timeShort.hour()}`,
time: 60
}
];

View File

@@ -124,9 +124,11 @@ export default function ReminderSheet({
const referencedItem = reference ? (reference as Note) : null;
const title = useRef<string | undefined>(
reminder?.title || referencedItem?.title
!reminder ? referencedItem?.title : reminder?.title
);
const details = useRef<string | undefined>(
!reminder ? referencedItem?.headline : reminder?.description
);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>();
@@ -543,7 +545,7 @@ export default function ReminderSheet({
/>
{reminderMode === ReminderModes.Permanent ? null : (
<ScrollView
<RNScrollView
style={{
flexDirection: "row",
marginTop: 12,
@@ -554,9 +556,9 @@ export default function ReminderSheet({
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={strings.reminderNotificationModes[
mode as keyof typeof strings.reminderNotificationModes
]()}
title={strings.reminderNotificationModes(
mode as keyof typeof ReminderNotificationModes
)}
style={{
marginRight: 12,
borderRadius: 100
@@ -588,7 +590,7 @@ export default function ReminderSheet({
}}
/>
))}
</ScrollView>
</RNScrollView>
)}
</ScrollView>

View File

@@ -38,7 +38,7 @@ const Sort = ({ type, screen }) => {
);
const updateGroupOptions = async (_groupOptions) => {
const groupType = screen === "Notes" ? "home" : type + "s";
console.log("updateGroupOptions for group", groupType, "in", screen);
await db.settings.setGroupOptions(groupType, _groupOptions);
setGroupOptions(_groupOptions);
setTimeout(() => {

View File

@@ -43,7 +43,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
let notes = version?.notes
? version.notes.replace("Thank you for using Notesnook!", "").split("- ")
: ["Bug fixes and performance improvements"];
notes = notes?.map((n) => n.replace(/\n/g, ""));
notes = notes?.map((n) => n.replace(/\n|<br>/g, ""));
const isGithubRelease = Config.GITHUB_RELEASE === "true";
const getSupportedAbi = () => {
@@ -178,18 +178,20 @@ export const Update = ({ version: appVersion, fwdRef }) => {
{version.body}
</Paragraph>
) : null}
{notes.map((item) => (
<Paragraph
key={item}
color={colors.secondary.paragraph}
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>
))}
{notes.map((item) =>
item && item !== "" ? (
<Paragraph
key={item}
color={colors.secondary.paragraph}
style={{
marginBottom: 5
}}
selectable
>
{item}
</Paragraph>
) : null
)}
</ScrollView>
<Seperator />
<Button
@@ -215,7 +217,9 @@ export const Update = ({ version: appVersion, fwdRef }) => {
marginTop: 10
}}
onPress={() => {
Linking.openURL(GITHUB_PAGE_URL).catch(console.log);
Linking.openURL(GITHUB_PAGE_URL).catch(() => {
/* empty */
});
}}
>
{strings.readReleaseNotes()}

View File

@@ -154,7 +154,9 @@ export const Toast = ({ context = "global" }) => {
>
<Icon
name={
toastOptions.type === "success"
toastOptions.icon
? toastOptions.icon
: toastOptions.type === "success"
? "check"
: toastOptions.type === "info"
? "information"

View File

@@ -1,158 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { ActivityIndicator, DimensionValue, ViewStyle } from "react-native";
import Animated, {
FadeIn,
FadeOut,
Layout,
LightSpeedInLeft
} from "react-native-reanimated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { SIZE } from "../../../utils/size";
import NativeTooltip from "../../../utils/tooltip";
import { ButtonProps } from "../button";
import { Pressable, useButton } from "../pressable";
import Heading from "../typography/heading";
import Paragraph from "../typography/paragraph";
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
export const AnimatedButton = ({
height = 45,
width = null,
onPress,
loading = false,
title = null,
icon,
fontSize = SIZE.sm,
type = "transparent",
iconSize = SIZE.md,
style = {},
accentColor = "accent",
accentText = "light",
onLongPress,
tooltipText,
textStyle,
iconPosition = "left",
buttonType,
bold,
iconColor,
fwdRef,
...restProps
}: ButtonProps) => {
const { text } = useButton({
type,
accent: accentColor,
text: accentText
});
const textColor = buttonType?.text ? buttonType.text : text;
const Component = bold ? Heading : Paragraph;
return (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
layout={Layout.springify()}
>
<Pressable
{...restProps}
fwdRef={fwdRef}
onPress={onPress}
onLongPress={(event) => {
if (onLongPress) {
onLongPress(event);
return;
}
if (tooltipText) {
NativeTooltip.show(event, tooltipText, NativeTooltip.POSITIONS.TOP);
}
}}
disabled={loading}
type={type}
accentColor={accentColor}
accentText={accentText}
customColor={buttonType?.color}
customSelectedColor={buttonType?.selected}
customOpacity={buttonType?.opacity}
customAlpha={buttonType?.alpha}
style={{
height: height,
width: (width as DimensionValue) || undefined,
paddingHorizontal: 12,
borderRadius: 5,
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
...(style as ViewStyle)
}}
>
{loading ? (
<ActivityIndicator color={textColor} size={fontSize + 4} />
) : null}
{icon && !loading && iconPosition === "left" ? (
<AnimatedIcon
exiting={FadeOut.duration(100)}
entering={LightSpeedInLeft}
layout={Layout.springify()}
name={icon}
style={{
marginRight: 0
}}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
/>
) : null}
{!title ? null : (
<Component
layout={Layout.springify()}
animated={true}
color={textColor as string}
size={fontSize}
numberOfLines={1}
style={[
{
marginLeft:
icon || (loading && iconPosition === "left") ? 5 : 0,
marginRight:
icon || (loading && iconPosition === "right") ? 5 : 0
},
textStyle
]}
>
{title}
</Component>
)}
{icon && !loading && iconPosition === "right" ? (
<Icon
name={icon}
style={{
marginLeft: 0
}}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
/>
) : null}
</Pressable>
</Animated.View>
);
};

View File

@@ -70,7 +70,6 @@ interface InputProps extends TextInputProps {
};
buttons?: React.ReactNode;
onBlurInput?: () => void;
onPress?: () => void;
height?: number;
fontSize?: number;
onFocusInput?: () => void;

View File

@@ -263,20 +263,22 @@ export const Pressable = ({
const getStyle = useCallback(
({ pressed }: PressableStateCallbackType): ViewStyle | ViewStyle[] => [
{
backgroundColor: pressed
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
: hexToRGBA(primaryColor, opacity || 1),
backgroundColor:
pressed && !disabled
? RGB_Linear_Shade(alpha, hexToRGBA(selectedColor, opacity || 1))
: hexToRGBA(primaryColor, opacity || 1),
width: "100%",
alignSelf: "center",
borderRadius: noborder ? 0 : br,
justifyContent: "center",
alignItems: "center",
marginBottom: 0,
borderColor: pressed
? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor
: borderColor || "transparent",
borderColor:
pressed && !disabled
? customSelectedColor
? getColorLinearShade(customSelectedColor, 0.3, false)
: borderSelectedColor || borderColor
: borderColor || "transparent",
borderWidth: borderWidth
},
style,
@@ -298,7 +300,8 @@ export const Pressable = ({
borderColor,
borderWidth,
style,
growFactor
growFactor,
disabled
]
);

View File

@@ -51,7 +51,6 @@ export const ReminderTime = ({
<Button
title={time}
key={reminder.id}
height={20}
icon="bell"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
@@ -67,12 +66,10 @@ export const ReminderTime = ({
marginRight: 0
}}
style={{
height: "auto",
borderRadius: 5,
marginRight: 5,
borderWidth: 0.5,
borderColor: colors.primary.border,
paddingHorizontal: 6,
marginBottom: 5,
...(style as ViewStyle)
}}
{...props}

View File

@@ -59,8 +59,9 @@ const SheetWrapper = ({
const smallTablet = deviceMode === "smallTablet";
const dimensions = useSettingStore((state) => state.dimensions);
const insets = useGlobalSafeAreaInsets();
const appState = useAppState();
const lockEvents = useRef(false);
const locked = useUserStore((state) => state.appLocked);
let width = dimensions.width > 600 ? 600 : 500;
const style = React.useMemo(() => {
@@ -99,24 +100,21 @@ const SheetWrapper = ({
};
useEffect(() => {
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.canLockAppInBackground()) {
if (appState === "background") {
const ref = fwdRef || localRef;
ref?.current?.hide();
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
ref?.current?.show();
unsub();
lockEvents.current = false;
}
});
}
if (locked) {
const ref = fwdRef || localRef;
ref?.current?.hide();
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
ref?.current?.show();
unsub();
lockEvents.current = false;
}
});
}
}
}, [appState, fwdRef]);
}, [locked, fwdRef]);
return (
<ScopedThemeProvider value="sheet">

View File

@@ -18,15 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import { DimensionValue, View } from "react-native";
import { SvgXml } from "./lazy";
export const SvgView = ({
width = 250,
height = 250,
src
}: {
width?: number | string;
height?: number | number;
width?: DimensionValue;
height?: DimensionValue;
src?: string;
}) => {
if (!src) return null;

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