Compare commits

...

947 Commits

Author SHA1 Message Date
Abdullah Atta
59945411e8 editor: improve task lists 2024-03-20 10:59:43 +05:00
Abdullah Atta
0f2554b9b3 web: fix assigning a color removes that color from all other notes 2024-03-20 07:24:29 +05:00
Abdullah Atta
181739dcd2 web: fix changes in callout heading not causing toc update 2024-03-20 07:16:34 +05:00
Abdullah Atta
ab8d283059 web: fix toc items not indented 2024-03-20 07:16:07 +05:00
Abdullah Atta
468f7c6a56 editor: add block-id on nodes inside callout 2024-03-20 07:15:48 +05:00
Abdullah Atta
c36a76cfdc editor: disable conflicting simple checklist input rule 2024-03-20 06:55:15 +05:00
Ammar Ahmed
57834bc9f1 mobile: fix tab title 2024-03-20 06:42:28 +05:00
Abdullah Atta
cad28f3436 web: fix image picker shown when there are 0 images 2024-03-19 22:44:21 +05:00
Abdullah Atta
cfb8b1ef0b editor: add support for copy/pasting code blocks with highlighting 2024-03-19 22:40:44 +05:00
Abdullah Atta
b626577730 web: fix crash on copying from codeblock 2024-03-19 22:32:43 +05:00
Abdullah Atta
e8ed3e2d8b web: only show content blocks with direct links in referenced in tab 2024-03-19 22:15:40 +05:00
Abdullah Atta
0ac3e4b67f core: fix extraction of blocks with links 2024-03-19 22:15:20 +05:00
Abdullah Atta
6de0112600 editor: fix hover popup appearing in wrong position 2024-03-19 22:14:42 +05:00
Abdullah Atta
4b8c35b6af editor: fix outline list has no data-block-id attribute 2024-03-19 21:56:38 +05:00
Abdullah Atta
c72ee27aa4 editor: fix DOMException when removing react node views 2024-03-19 16:29:53 +05:00
Abdullah Atta
875436a1ad editor: minor refactors 2024-03-19 12:30:05 +05:00
Abdullah Atta
1ede80c33c editor: fix crash on select all + backspace with lists 2024-03-19 12:29:56 +05:00
Abdullah Atta
e6de183f77 editor: fix crash on removing task list 2024-03-19 12:29:37 +05:00
Ammar Ahmed
514302d20d editor: enable block id on checklist and callout 2024-03-19 12:03:07 +05:00
Abdullah Atta
14946e092d editor: fix react node views for react 18 2024-03-19 12:02:25 +05:00
Ammar Ahmed
d9fafc7703 editor: always show note internal link button in toolbar 2024-03-19 11:29:48 +05:00
Abdullah Atta
5e8ab57271 editor: handle link open on click 2024-03-18 12:09:03 +05:00
Abdullah Atta
00c648341b ui: fix weird zIndex animation on popups 2024-03-18 12:05:18 +05:00
Abdullah Atta
4063c573ec editor: update tiptap & other packages 2024-03-18 12:00:23 +05:00
Abdullah Atta
72f908bc90 editor: fix newline added in callout on typing anything 2024-03-18 11:20:51 +05:00
Abdullah Atta
4eebe10f2e editor: fix => turning into a callout 2024-03-18 11:18:41 +05:00
Abdullah Atta
6c0d6127ba editor: fix image size resetting on resize 2024-03-18 11:13:05 +05:00
Abdullah Atta
4c9381599f web: fix images not getting attached 2024-03-18 11:12:36 +05:00
Abdullah Atta
8650ed2ba5 web: fix attachment delete dialog not rendering any buttons 2024-03-18 10:27:31 +05:00
Abdullah Atta
600953baa7 core: fix attachment deletion 2024-03-18 10:27:31 +05:00
Ammar Ahmed
8967280e54 mobile: use contentBlocksWithLinks 2024-03-18 10:22:59 +05:00
Ammar Ahmed
57a2181e62 core: fix duplicate backlinks shown on note 2024-03-18 10:22:59 +05:00
Ammar Ahmed
911cacd9e9 mobile: cleanup 2024-03-18 10:22:59 +05:00
Ammar Ahmed
361be50e3f mobile: show error if attachment download fails 2024-03-18 10:22:59 +05:00
Ammar Ahmed
6a306ddda2 core: support video,audio,document filter in attachments 2024-03-18 10:22:59 +05:00
Ammar Ahmed
1d6045f5ac mobile: fix all attachments showin in note attachments 2024-03-18 10:22:59 +05:00
Abdullah Atta
3d286f6899 editor: #dialogContainer > .dialogContainer 2024-03-18 10:20:56 +05:00
Abdullah Atta
859c35e387 web: fix editor dom structure 2024-03-18 10:20:31 +05:00
Abdullah Atta
8d16611124 web: fix editor toolbar is not sticky 2024-03-18 08:31:14 +05:00
Ammar Ahmed
36afdf182c editor: fix collapsing a callout collapses all parent callouts 2024-03-16 13:10:16 +05:00
Ammar Ahmed
ad448b567b mobile: fix webpack config 2024-03-16 10:35:21 +05:00
Ammar Ahmed
e80f329f33 mobile: release v3.0.10 2024-03-16 09:54:23 +05:00
Ammar Ahmed
0c6ece1e5b mobile: open note in new tab 2024-03-16 09:43:17 +05:00
Ammar Ahmed
5b2a72c8d6 mobile: fix incorrect tab content by default 2024-03-16 08:03:36 +05:00
Ammar Ahmed
fc3a788575 mobile: fix incorrect button type in dialog 2024-03-16 07:23:10 +05:00
Ammar Ahmed
fd61dbfe32 mobile: fix publish button hidden on tablet 2024-03-15 11:04:04 +05:00
Ammar Ahmed
1a11bea05e mobile: cleanup 2024-03-15 10:54:27 +05:00
Ammar Ahmed
43e7c8c7f9 mobile: update deps 2024-03-15 10:51:36 +05:00
Ammar Ahmed
db90d4c443 mobile: fix app lock shows on session expired sheet 2024-03-15 10:50:53 +05:00
Ammar Ahmed
f375c7d3e4 mobile: support showing diff of conflicted note 2024-03-15 10:50:26 +05:00
Ammar Ahmed
fc5aeeb221 editor: add highlight diff extension 2024-03-15 10:49:37 +05:00
Ammar Ahmed
31685d12bd mobile: remove redundant code 2024-03-15 09:02:15 +05:00
Ammar Ahmed
5ffa11a7f6 mobile: render title after loaded 2024-03-15 08:51:24 +05:00
Ammar Ahmed
08e8035fcb mobile: bump android version 2024-03-15 08:08:16 +05:00
Ammar Ahmed
76b3c5ed7a mobile: fix button action 2024-03-15 08:07:11 +05:00
Ammar Ahmed
29468916e3 mobile: check if editorController.current is undefined 2024-03-15 08:05:22 +05:00
Ammar Ahmed
dde5dcd8fb mobile: cleanup 2024-03-15 07:44:33 +05:00
Ammar Ahmed
381184f8c4 mobile: add readonly editor 2024-03-15 07:43:25 +05:00
Abdullah Atta
5a78d9e517 web: make opfs safer by allowing only 1 operation at a time 2024-03-15 07:14:08 +05:00
Abdullah Atta
9edf5402c9 web: fix more tests 2024-03-14 13:07:01 +05:00
Abdullah Atta
74b49f24a8 editor: fix some images not loading 2024-03-14 13:07:01 +05:00
Abdullah Atta
dfcbf3045a web: fix lock/unlock from properties not working 2024-03-14 13:07:01 +05:00
Abdullah Atta
345922e74a web: fix title of locked note not saving 2024-03-14 13:07:01 +05:00
Abdullah Atta
21475c0514 web: update url on switching between sessions 2024-03-14 13:07:01 +05:00
Abdullah Atta
d498a7a2b9 web: hide tags input in new session 2024-03-14 13:07:01 +05:00
Abdullah Atta
e627c3326c web: bring back edited date in footer 2024-03-14 13:07:01 +05:00
Abdullah Atta
ad35e83269 web: fix typing fast in title input caused some characters to miss 2024-03-14 13:07:01 +05:00
Ammar Ahmed
38f04ca70a mobile: fix webpack config 2024-03-14 11:57:59 +05:00
Abdullah Atta
eeeda75408 global: update lockfiles 2024-03-14 11:11:52 +05:00
Abdullah Atta
5aec94f8a9 editor: fix type errors 2024-03-14 11:11:39 +05:00
Ammar Ahmed
ea274c5adf mobile: release v3.0.9 2024-03-14 10:56:12 +05:00
Abdullah Atta
81408f7763 web: show error if backup import fails 2024-03-14 10:53:23 +05:00
Ammar Ahmed
15dc333969 mobile: readd editor overlay 2024-03-14 10:44:55 +05:00
Ammar Ahmed
44a07c7cf6 mobile: fix tab bugs 2024-03-14 10:43:49 +05:00
Abdullah Atta
114338ab80 web: bump version to 3.0.7 2024-03-14 10:42:22 +05:00
Abdullah Atta
64e0129107 editor: do not add data-block-id attribute if no blockid 2024-03-14 10:37:28 +05:00
Abdullah Atta
749eea4979 editor: highlight active block for 5 seconds 2024-03-14 10:37:06 +05:00
Abdullah Atta
7ba785aa32 core: fix imports 2024-03-14 10:27:58 +05:00
Abdullah Atta
b4ec3fbbf1 web: improve internal links ui 2024-03-14 10:21:36 +05:00
Abdullah Atta
c0ff54afbb core: do not wrap text in content blocks 2024-03-14 10:13:19 +05:00
Abdullah Atta
6807871bb0 web: ignore realtime sync if it doesn't update content or note title 2024-03-14 10:04:00 +05:00
Abdullah Atta
6f07a3c03c web: fix behavior of editor action bar 2024-03-14 10:03:28 +05:00
Abdullah Atta
539c270c18 web: always show note statistics of active editor 2024-03-14 10:03:05 +05:00
Abdullah Atta
a7b190116a web: fix diff viewer not showing images 2024-03-14 10:02:42 +05:00
Abdullah Atta
ecba9f87be web: fix concurrent opfs reads throwing errors 2024-03-14 10:02:21 +05:00
Abdullah Atta
b11539d20f web: await promises 2024-03-14 10:01:47 +05:00
Abdullah Atta
66469b1fd5 web: use database updated event to add editor reactivity 2024-03-14 10:01:34 +05:00
Abdullah Atta
e098d65008 core: fix attachment not unlinked from notes on removal 2024-03-14 09:59:22 +05:00
Abdullah Atta
b837b9728e core: publish database updated events on content remove/update 2024-03-14 09:59:02 +05:00
Abdullah Atta
959f34c05c core: refactor database update event logic 2024-03-14 09:58:36 +05:00
Abdullah Atta
7465d4d149 core: add method to rebuild trash cache 2024-03-14 09:57:47 +05:00
Abdullah Atta
670e2bbd6d core: simplify remove from all notebooks logic 2024-03-14 09:57:02 +05:00
Abdullah Atta
7e4120e242 core: publish database updated event on relations unlink 2024-03-14 09:55:01 +05:00
Abdullah Atta
a76b26e64c core: fix note not unlinked from vault on unlock 2024-03-14 09:54:21 +05:00
Abdullah Atta
84d91d70d1 core: rebuild trash cache on sync complete 2024-03-14 09:54:00 +05:00
Abdullah Atta
cc2ba899e8 web: reopen notes after permanently deleting an attachment 2024-03-14 06:42:09 +05:00
Abdullah Atta
047efd5079 web: fix content is not updated for new notes in realtime sync 2024-03-14 06:41:39 +05:00
Abdullah Atta
3ae297ca04 web: update editor sessions in realtime 2024-03-14 06:41:17 +05:00
Abdullah Atta
82c7bcd0db web: do not show internal links, notebooks etc in properties for trash preview 2024-03-14 06:40:19 +05:00
Abdullah Atta
00e087023a web: fix editor title not changing on realtime sync 2024-03-14 06:39:39 +05:00
Abdullah Atta
c254f453ce web: special icons for readonly and trash preview tabs 2024-03-14 06:39:12 +05:00
Abdullah Atta
fb903f48e7 web: fix trash note preview is not readonly 2024-03-14 06:38:30 +05:00
Abdullah Atta
248d2a87b3 core: standarize database updated event 2024-03-13 11:01:50 +05:00
Abdullah Atta
268f5f012b global: upgrade typescript to 5.4.2 2024-03-13 09:01:12 +05:00
Abdullah Atta
ba0e8d5d3f web: fix type errors 2024-03-13 09:01:12 +05:00
Abdullah Atta
925cc7a3bc core: sort & group reminders in sqlite 2024-03-13 09:01:12 +05:00
Ammar Ahmed
e39e5a7bf0 mobile: minor cleanup 2024-03-13 08:59:57 +05:00
Ammar Ahmed
57de6fd014 mobile: fix search in share extension 2024-03-12 11:55:57 +05:00
Ammar Ahmed
679f6644f7 editor: do not focus editor on end search 2024-03-12 11:55:57 +05:00
Ammar Ahmed
b5e18fe350 mobile: fix editor tab bugs 2024-03-12 11:55:57 +05:00
Ammar Ahmed
9156c57b39 mobile: fix use-db-item hook not working 2024-03-12 11:55:57 +05:00
Ammar Ahmed
02bf88252c mobile: fix attachment exists check when uploading file 2024-03-12 11:55:57 +05:00
Ammar Ahmed
fe54930332 mobile: fix attachment progress not working 2024-03-12 11:55:57 +05:00
Ammar Ahmed
b7d72437de mobile: fix note with attachments not saved from share extension 2024-03-12 11:55:57 +05:00
Ammar Ahmed
2250299608 mobile: fix share extension append to exisitng note not working 2024-03-12 11:55:57 +05:00
Ammar Ahmed
1807109805 common: up version when migrating toolbar 2024-03-12 11:55:57 +05:00
Abdullah Atta
8b47626173 core: fix table x has no column y errors 2024-03-12 08:24:53 +05:00
Ammar Ahmed
ee9474afb7 mobile: blur editor on back 2024-03-12 08:24:53 +05:00
Abdullah Atta
a9b4c5e256 web: use dnd-kit for smoother drag/drop in tabs 2024-03-12 08:24:53 +05:00
Abdullah Atta
d41f049cee web: always sort pinned tabs first 2024-03-12 08:24:53 +05:00
Abdullah Atta
0e43408bff web: fix pinned tabs getting unpinned after app refresh 2024-03-12 08:24:53 +05:00
Ammar Ahmed
1c0a8e00a6 mobile: add useNoteLocked hook 2024-03-12 08:24:53 +05:00
Ammar Ahmed
c106db2422 mobile: fix ui of linked blocks 2024-03-12 08:24:53 +05:00
Ammar Ahmed
23734558dd mobile: fix jump to block 2024-03-12 08:24:53 +05:00
Ammar Ahmed
8ca8f54c33 mobile: fix content block ui 2024-03-12 08:24:53 +05:00
Ammar Ahmed
cad349ca12 mobile: fix loading placeholder position 2024-03-12 08:24:53 +05:00
Ammar Ahmed
ac7fb315d6 mobile: fix colors and size of icons in editor header 2024-03-12 08:24:53 +05:00
Ammar Ahmed
d7302a2cf0 mobile: use flatlist for rendering tabs 2024-03-12 08:24:53 +05:00
Ammar Ahmed
3254a1ff30 mobile: show keyboard when opening new tab or empty tab 2024-03-12 08:24:52 +05:00
Ammar Ahmed
2cab3dd486 mobile: make tab active on focus 2024-03-12 08:24:52 +05:00
Ammar Ahmed
e2767f2f77 mobile: fix tabs 2024-03-12 08:24:52 +05:00
Ammar Ahmed
4382576bf9 mobile: fix minor bugs in tabs 2024-03-12 08:24:52 +05:00
Ammar Ahmed
e89f7e3819 mobile: fix duplicate imports 2024-03-12 08:24:31 +05:00
Abdullah Atta
bced2b7872 web: bring back editor margins toggle in action bar 2024-03-12 08:24:31 +05:00
Abdullah Atta
7c6596f826 web: minor ui fixes 2024-03-12 08:24:31 +05:00
Abdullah Atta
1afad07b2d web: fix section subtitle horizontal margin 2024-03-12 08:24:31 +05:00
Abdullah Atta
43ccb398c0 web: add placeholder in table of contents 2024-03-12 08:24:30 +05:00
Abdullah Atta
5fbeb152e5 web: update diff content on tab switch 2024-03-12 08:24:30 +05:00
Abdullah Atta
d5e7fbabaa web: minor refactor 2024-03-12 08:24:30 +05:00
Abdullah Atta
33ff702295 web: fix properties section horizontal margin 2024-03-12 08:24:30 +05:00
Abdullah Atta
f3322cc8f1 core: remove note from vault if its content is not locked 2024-03-12 08:24:30 +05:00
Abdullah Atta
4356d10028 web: fix locked notes getting overwritten with unencrypted content 2024-03-12 08:24:30 +05:00
Abdullah Atta
371984a366 web: fix diff session not opening after app refresh 2024-03-12 08:24:30 +05:00
Abdullah Atta
e25016354e editor: fix search decorations not cleared on search end 2024-03-12 08:24:30 +05:00
Abdullah Atta
8fe4bddb85 web: add all missing functionality in note linking dialog 2024-03-12 08:24:30 +05:00
Abdullah Atta
fc121ef396 web: remove custom search state logic & use editor commands 2024-03-12 08:24:30 +05:00
Abdullah Atta
73a1295e29 editor: fix text on selected search result not visible 2024-03-12 08:24:30 +05:00
Abdullah Atta
8abf57ae24 editor: use global search store for managing search state 2024-03-12 08:24:30 +05:00
Abdullah Atta
a0f14dc3ef editor: minor refactor 2024-03-12 08:24:30 +05:00
Abdullah Atta
ce8cba4f13 web: remove locked session type guard (switch to simple boolean)
the type guard was incorrect
2024-03-12 08:24:30 +05:00
Abdullah Atta
e726bd073b web: add content to new session type 2024-03-12 08:24:30 +05:00
Abdullah Atta
3ee5ea097b web: fix editor title not updating on creating a note 2024-03-12 08:24:30 +05:00
Abdullah Atta
6272e8b805 web: remove preview session type 2024-03-12 08:24:30 +05:00
Abdullah Atta
ae5c323d3b web: ${id}_editorScroll > editorScroll_${id} 2024-03-12 08:24:29 +05:00
Abdullah Atta
afc3ede9b0 web: set session content before saving 2024-03-12 08:24:11 +05:00
Abdullah Atta
7671e0e297 web: add dialog container inside tab 2024-03-12 08:24:11 +05:00
Abdullah Atta
005f2d8ef1 web: handle locked notes properly in real time editor sync 2024-03-12 08:24:11 +05:00
Abdullah Atta
7b20acaa5a web: freeze unhydrated sessions 2024-03-12 08:24:11 +05:00
Abdullah Atta
da938ec348 web: remove unused & commented out code 2024-03-12 08:24:11 +05:00
Abdullah Atta
d32d438eeb web: focus editor on switching tab and toggle active class 2024-03-12 08:23:47 +05:00
Abdullah Atta
e289caf561 web: define editor & session once and pass it everywhere 2024-03-12 08:23:47 +05:00
Abdullah Atta
e8cd0c0a52 web: remove unlocked session type 2024-03-12 08:23:47 +05:00
Abdullah Atta
62bdee7789 web: set session id on saving note 2024-03-12 08:23:47 +05:00
Abdullah Atta
ccb9c492a8 core: add block ids before returning content 2024-03-12 08:23:46 +05:00
Abdullah Atta
4506e5e90a editor: fix range error crash 2024-03-12 08:23:46 +05:00
Abdullah Atta
2cc772106b editor: do not add block id on list items
(because they already contain paragraphs and we don't want duplicates)
2024-03-12 08:23:46 +05:00
Abdullah Atta
a82caf5da2 editor: do not touch editor when its view is destroyed 2024-03-12 08:23:46 +05:00
Abdullah Atta
1cebcb7876 editor: fix selection/focus is lost when editor state is updated 2024-03-12 08:23:46 +05:00
Abdullah Atta
0247903772 editor: fix list type thumbnails in toolbar 2024-03-12 08:23:46 +05:00
Abdullah Atta
1f5164660d editor: hide hover popup on editor destroy 2024-03-12 08:23:46 +05:00
Abdullah Atta
3a835ad90d editor: render interactive popups in popup instead of menu 2024-03-12 08:23:46 +05:00
Abdullah Atta
18c13ed8fe editor: scroll blocks into view only for active editor 2024-03-12 08:23:46 +05:00
Abdullah Atta
35345b9214 editor: query editor dom based on active editor class 2024-03-12 08:23:46 +05:00
Abdullah Atta
2c98b98765 editor: render all popups in popup container 2024-03-12 08:23:46 +05:00
Abdullah Atta
ace224b03c editor: close all popups and dropdowns on switching tabs 2024-03-12 08:23:46 +05:00
Abdullah Atta
23e9b07310 editor: fix placeholder is not visible 2024-03-12 08:23:46 +05:00
Abdullah Atta
fdc5642b3c ui: allow setting popup container 2024-03-12 08:23:46 +05:00
Abdullah Atta
9c81432dfd web: disable properties button in action bar when diff is open 2024-03-12 08:23:46 +05:00
Abdullah Atta
1825796649 web: allow session item size to grow 2024-03-12 08:23:46 +05:00
Abdullah Atta
9c5940e872 web: add support for /notes/:noteId/edit hash route 2024-03-12 08:23:46 +05:00
Abdullah Atta
ad77334e00 core: do not change date modified on items when bulk adding 2024-03-12 08:23:46 +05:00
Abdullah Atta
761b5b7793 editor: make image size clamping responsive 2024-03-12 08:23:45 +05:00
Abdullah Atta
eadf5260c4 web: fix race condition where editor context would be destroyed and updated at the same time 2024-03-12 08:23:45 +05:00
Abdullah Atta
bf62cd93b2 web: create error from ErrorEvent if none found 2024-03-12 08:23:45 +05:00
Abdullah Atta
92c9f31e01 editor: fix crash on loading web clip 2024-03-12 08:23:45 +05:00
Abdullah Atta
3404999bcb web: reenable tags refreshing in editor 2024-03-12 08:23:45 +05:00
Abdullah Atta
e9816ef3a2 editor: fix position of loading placeholder for floating image 2024-03-12 08:23:45 +05:00
Abdullah Atta
f10b7c9b66 editor: fix react node views not rendering on editor load 2024-03-12 08:23:45 +05:00
Abdullah Atta
36cf1fb950 web: fix crash on refreshing editor store 2024-03-12 08:23:45 +05:00
Ammar Ahmed
c1f4a4e3ab mobile: fix randId function duplicate 2024-03-12 08:23:45 +05:00
Abdullah Atta
50a22ddf85 editor: fix crash when resizing window due to a memory leak 2024-03-12 08:23:45 +05:00
Abdullah Atta
d3d74ff7df web: fix temporary tabs cannot be made permanent 2024-03-12 08:23:45 +05:00
Abdullah Atta
ed91e1e22f web: fix jumping tabs when pinning/unpinning tabs 2024-03-12 08:23:45 +05:00
Abdullah Atta
a765d22f4b web: fix crashes when moving tabs 2024-03-12 08:23:45 +05:00
Abdullah Atta
aca0e40fd9 web: fix new sessions can't be opened after app refresh 2024-03-12 08:23:45 +05:00
Abdullah Atta
183613296b web: fix type errors 2024-03-12 08:23:45 +05:00
Ammar Ahmed
3d75791006 editor: remove editor.current usage 2024-03-12 08:23:45 +05:00
Ammar Ahmed
516c214174 mobile: fix editor loading multiple times 2024-03-12 08:23:44 +05:00
Abdullah Atta
541868e681 web: use createInternalLink everywhere 2024-03-12 08:23:44 +05:00
Ammar Ahmed
c18513a04d mobile: finalize note linking 2024-03-12 08:23:44 +05:00
Ammar Ahmed
37220256fa core: parse internal link from href 2024-03-12 08:23:44 +05:00
Ammar Ahmed
e28a4b4e23 core: fix extracting internal links 2024-03-12 08:23:44 +05:00
Abdullah Atta
04d66576cb web: show references & linked notes in properties 2024-03-12 08:23:44 +05:00
Abdullah Atta
63ae8a4a86 web: reset active block after scroll 2024-03-12 08:23:44 +05:00
Abdullah Atta
6e0404fe8b core: add internal links extraction 2024-03-12 08:23:44 +05:00
Abdullah Atta
db79ad3f47 web: add full support for adding internal links 2024-03-12 08:23:44 +05:00
Abdullah Atta
46ac983770 editor: add support for adding/removing internal links 2024-03-12 08:23:44 +05:00
Abdullah Atta
03cc358e61 core: add support for processing internal links in note content 2024-03-12 08:23:43 +05:00
Ammar Ahmed
e457215248 mobile: note linking 2024-03-12 08:23:43 +05:00
Ammar Ahmed
bf393b8015 mobile: update deps 2024-03-12 08:21:28 +05:00
Abdullah Atta
b8eee77646 web: fix type errors and broken imports 2024-03-12 08:21:28 +05:00
Abdullah Atta
4ef4f8798c editor: reset editor on options update 2024-03-12 08:21:28 +05:00
Abdullah Atta
99886fdc96 web: add support for table of contents 2024-03-12 08:21:27 +05:00
Abdullah Atta
80ef5c890a web: add support for tabs 2024-03-12 08:21:03 +05:00
Abdullah Atta
c925ddd462 common: add utility for formatting history session time 2024-03-12 08:19:11 +05:00
Abdullah Atta
73d380d069 mobile: simplify unencrypted content check 2024-03-12 08:19:11 +05:00
Abdullah Atta
a67515118c core: simplify encryption content checks 2024-03-12 08:19:11 +05:00
Abdullah Atta
ad46f82337 core: restoring a history session should create a new version 2024-03-12 08:19:11 +05:00
Abdullah Atta
055a991fcb editor: create editor only once 2024-03-12 08:19:11 +05:00
Ammar Ahmed
d23c8203a3 mobile: implement toc 2024-03-12 08:19:11 +05:00
Ammar Ahmed
3b0ec278d4 mobile: support vault notes in tabs 2024-03-12 08:19:11 +05:00
Ammar Ahmed
55b4c77ad1 mobile: fix tab bugs 2024-03-12 08:19:11 +05:00
Ammar Ahmed
aa74541d6e mobile: add tab history 2024-03-12 08:19:11 +05:00
Ammar Ahmed
5c5e6c7438 mobile: some more fixes 2024-03-12 08:19:11 +05:00
Ammar Ahmed
a1235b48ef mobile: fix numerous issues with tabs 2024-03-12 08:19:10 +05:00
Ammar Ahmed
e015b180a8 mobile: push changes 2024-03-12 08:19:10 +05:00
Ammar Ahmed
e3c37c470d mobile: add editor tabs 2024-03-12 08:19:10 +05:00
Abdullah Atta
855203bd0e core: remove unnecessary notes collection init before migration 2024-03-12 08:19:10 +05:00
Abdullah Atta
6e4700121a web: bump version to 3.0.6 2024-03-12 08:19:10 +05:00
Abdullah Atta
a67b04db7b setup: only disable esbuild rebuild on mobile 2024-03-12 08:19:10 +05:00
Abdullah Atta
0f84d02abc setup: ignore esbuild rebuild script 2024-03-12 08:19:10 +05:00
Abdullah Atta
85c2be9500 mobile: update package lockfile 2024-03-12 08:19:10 +05:00
Abdullah Atta
a5a05f501a editor: fix task items not showing correctly 2024-03-12 08:19:10 +05:00
Abdullah Atta
9bb946d27e ui: fix menu is not scrollable on small screens 2024-03-12 08:19:10 +05:00
Abdullah Atta
cdd089fcf3 web: fix loading skeleton for compact items in list 2024-03-12 08:19:10 +05:00
Ammar Ahmed
49d4d2b6f9 mobile: bump version code 2024-03-12 08:19:09 +05:00
Ammar Ahmed
8d1408997f mobile: release v3.0.8 2024-03-12 08:19:09 +05:00
Ammar Ahmed
d18a8742f1 mobile: improve notebook sheet wording 2024-03-12 08:19:09 +05:00
Ammar Ahmed
d9d229b020 mobile: change sheet button positions 2024-03-12 08:19:09 +05:00
Ammar Ahmed
045527d502 mobile: cleanup 2024-03-12 08:19:09 +05:00
Ammar Ahmed
3c29303ac8 mobile: reminder notification shows even after removing reminders 2024-03-12 08:19:09 +05:00
Ammar Ahmed
98c6ee7904 mobile: simplify reminder sheet ui 2024-03-12 08:19:09 +05:00
Ammar Ahmed
a1dc396564 mobile: remove usage of customStyle prop 2024-03-12 08:19:09 +05:00
Abdullah Atta
4767597fc1 core: fix settings are not migrated 2024-03-12 08:19:09 +05:00
Ammar Ahmed
1e05512c98 mobile: update deps 2024-03-12 08:19:09 +05:00
Ammar Ahmed
68908e0e87 mobile: fix tags not showing properly 2024-03-12 08:19:09 +05:00
Ammar Ahmed
1808b84b0b mobile: cleanup 2024-03-12 08:19:09 +05:00
Ammar Ahmed
1a5595e3db mobile: update Pods 2024-03-12 08:19:08 +05:00
Ammar Ahmed
a96250b937 mobile: release v3.0.7 beta 2024-03-12 08:19:08 +05:00
Ammar Ahmed
19f4fbfcf7 mobile: fix sorting attachments 2024-03-12 08:19:08 +05:00
Abdullah Atta
35c8848b89 web: fix height of attachments table 2024-03-12 08:19:08 +05:00
Abdullah Atta
970f51595b core: do not include deleted attachments in filters 2024-03-12 08:19:08 +05:00
Abdullah Atta
b31d279e15 web: bump version to 3.0.5 2024-03-12 08:19:08 +05:00
Ammar Ahmed
f708eacdc3 mobile: fix mobile sheet ui 2024-03-12 08:19:08 +05:00
Abdullah Atta
7827a35de9 web: show error if there's no internet on updating profile 2024-03-12 08:19:08 +05:00
Abdullah Atta
5e0217bcb1 web: allow clearing database on database disk image is malformed 2024-03-12 08:19:08 +05:00
Abdullah Atta
54debb035a web: fix error due to changed file picker return 2024-03-12 08:19:08 +05:00
Abdullah Atta
3e42d3020d web: fix attachFile- when saving web clip 2024-03-12 08:19:08 +05:00
Ammar Ahmed
c7ab4c9ccf mobile: enable auto sync 2024-03-12 08:19:08 +05:00
Ammar Ahmed
d684c3a8b2 mobile: finalize image upload 2024-03-12 08:19:08 +05:00
Abdullah Atta
3feee27ad6 web: add image compression 2024-03-12 08:19:08 +05:00
Abdullah Atta
4add55adf2 web: add support for multiselecting attachments 2024-03-12 08:19:08 +05:00
ammarahm-ed
4ba90510ea mobile: handle uploading multiple files 2024-03-12 08:19:07 +05:00
ammarahm-ed
4b693b301f mobile: add optional image compression on upload 2024-03-12 08:19:07 +05:00
Abdullah Atta
a9fcd11e11 web: fix sync icon spin direction 2024-03-12 08:19:07 +05:00
Abdullah Atta
01ee348870 core: fix migration for db version > 6 2024-03-12 08:19:07 +05:00
Abdullah Atta
c0cec22e10 core: only get attachment id when processing content 2024-03-12 08:19:07 +05:00
Abdullah Atta
12b87cceeb web: enable auto sync 2024-03-12 08:19:07 +05:00
Abdullah Atta
b9cd40937d web: edit profile directly 2024-03-12 08:19:07 +05:00
Ammar Ahmed
1efae55a85 mobile: add user profile 2024-03-12 08:19:07 +05:00
Ammar Ahmed
932cf0c877 core: fetch user profile before updating profile 2024-03-12 08:19:07 +05:00
Abdullah Atta
0ba6be4fe6 web: add support for setting user profile 2024-03-12 08:19:06 +05:00
Abdullah Atta
cdaa3184a9 core: add support for setting user profile 2024-03-12 08:18:50 +05:00
Ammar Ahmed
03445bb45a mobile: cleanup 2024-03-12 08:18:50 +05:00
Ammar Ahmed
9d649c9bce mobile: fix note assigned to multiple colors 2024-03-12 08:18:50 +05:00
Ammar Ahmed
9a34e4884a mobile: small ui fixes 2024-03-12 08:18:50 +05:00
Ammar Ahmed
df0b51f5ec mobile: ui bug fixes 2024-03-12 08:18:50 +05:00
Ammar Ahmed
2554e5d63a mobile: user profile 2024-03-12 08:18:50 +05:00
Abdullah Atta
5479d800cf core: fix migration errors & compact deleted items 2024-03-12 08:17:21 +05:00
Ammar Ahmed
f729b8f908 mobile: release v3.0.6 2024-03-12 08:17:21 +05:00
Ammar Ahmed
af21da8f2e mobile: fix toolbar migration 2024-03-12 08:17:21 +05:00
Ammar Ahmed
9d13e9f700 core: check for deleted attachments in pending attachments 2024-03-12 08:17:21 +05:00
Ammar Ahmed
d9932a68d9 mobile: fix backup sheet never closes on manual backups 2024-03-12 08:17:21 +05:00
Ammar Ahmed
17a9d1851f mobile: allow sort by title in all groupings 2024-03-12 08:17:20 +05:00
Abdullah Atta
17f68b5315 desktop: disable os encryption of keystore in portable app 2024-03-12 08:16:11 +05:00
Abdullah Atta
b1ce214571 web: migrate toolbar on first use 2024-03-12 08:16:11 +05:00
Abdullah Atta
cfeba093d9 common: add toolbar migration 2024-03-12 08:16:11 +05:00
Abdullah Atta
69038d8edd global: update lockfiles 2024-03-12 08:16:10 +05:00
Abdullah Atta
934faf3b87 web: bump version to 3.0.4-beta 2024-03-12 08:16:01 +05:00
Abdullah Atta
71272d51c9 common: get note's locked status from its content 2024-03-12 08:16:01 +05:00
Abdullah Atta
b295e79677 core: always delete vault references on vault deletion 2024-03-12 08:16:01 +05:00
Abdullah Atta
74789675eb web: fix note not selected after switching from locked note 2024-03-12 08:16:01 +05:00
Abdullah Atta
1234d289eb core: fix sync tests 2024-03-12 08:16:01 +05:00
Abdullah Atta
723c29bad6 core: fix database image is malformed error when deleting vault 2024-03-12 08:16:01 +05:00
Abdullah Atta
343f1f320c core: fix database image is malformed error on unlocking a note 2024-03-12 08:16:01 +05:00
Abdullah Atta
50e70b90db web: fix sync not running after login 2024-03-12 08:16:01 +05:00
Abdullah Atta
a2fd5b266c web: refresh nav items on deleting a tag 2024-03-12 08:16:01 +05:00
Abdullah Atta
ebf32f0603 core: fix database image is malformed error 2024-03-12 08:16:01 +05:00
Abdullah Atta
4dae3a424c core: speed up backup restore 2024-03-12 08:16:01 +05:00
Ammar Ahmed
086aa53212 mobile: fix color of + button on hidden items
in side menu
2024-03-12 08:16:01 +05:00
Abdullah Atta
6d4b53b489 core: fix deleted items getting imported during backup restore 2024-03-12 08:16:00 +05:00
Abdullah Atta
c09064d2d6 core: fix duplicate colors created during backup restore 2024-03-12 08:16:00 +05:00
Ammar Ahmed
024be4ce89 mobile: fix readonly mode 2024-03-12 08:16:00 +05:00
Ammar Ahmed
513d6b3d72 editor: fix keyboard opens on drag in editor 2024-03-12 08:16:00 +05:00
Ammar Ahmed
513b4016b9 mobile: clear selection after deleting items 2024-03-12 08:16:00 +05:00
Ammar Ahmed
8de57a1b5d mobile: fix reminder ui in selection mode 2024-03-12 08:16:00 +05:00
Ammar Ahmed
34aba64bde mobile: remove pass min limit on vault 2024-03-12 08:16:00 +05:00
Abdullah Atta
eedfe38b26 web: clear editor if opened note is unlocked 2024-03-12 08:16:00 +05:00
Abdullah Atta
36406875fc core: remove note's vault relation regardless of vault 2024-03-12 08:15:43 +05:00
Abdullah Atta
854c2323e6 core: don't unlink vault notes on vault remove 2024-03-12 08:15:43 +05:00
Abdullah Atta
1c3694df9b web: fix vault settings not updating on deleting vault 2024-03-12 08:15:43 +05:00
Abdullah Atta
305b62b1a2 core: fix sqlite crash on calling delete with empty array 2024-03-12 08:15:43 +05:00
Abdullah Atta
5550a9f592 web: fix unnecessary rerendering of notebook header 2024-03-12 08:15:43 +05:00
Abdullah Atta
a97d917c7a web: disable auto sync 2024-03-12 08:15:42 +05:00
Abdullah Atta
a55e188ba8 core: enable sync push 2024-03-12 08:15:42 +05:00
Abdullah Atta
f0a70ac4da web: add drag/drop tests 2024-03-12 08:15:42 +05:00
Abdullah Atta
4d675a87a6 web: fix cannot navigate into nested notebook by hovering on it 2024-03-12 08:15:42 +05:00
Abdullah Atta
38c0a5a034 editor: fix react error 409 2024-03-12 08:15:42 +05:00
Abdullah Atta
607e901673 web: expand parent when a child notebook is added 2024-03-12 08:15:42 +05:00
Muhammad Ali
35b44c69b9 web: add support for item drag/droping (#3230)
* web: basic drag drop function for single note

* web: added check for noteId

* web: add support for item drag drop

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-03-12 08:15:42 +05:00
Ammar Ahmed
d920c73acb mobile: disable auto sync 2024-03-12 08:15:42 +05:00
Ammar Ahmed
34f909e37f editor: fix callout margins 2024-03-12 08:15:42 +05:00
Ammar Ahmed
a33970250e mobile: fix keyboard jump on tap in dropdown 2024-03-12 08:15:42 +05:00
Abdullah Atta
297ca467d4 editor: fix rtl for outline list 2024-03-12 08:15:42 +05:00
Abdullah Atta
eeeb0908dd editor: add callout component 2024-03-12 08:15:42 +05:00
Ammar Ahmed
359a5ead1b mobile: release v3.0.5 beta 2024-03-12 08:15:42 +05:00
Ammar Ahmed
92fe455fc3 mobile: update toolbar 2024-03-12 08:15:41 +05:00
Abdullah Atta
31b2fe7e85 editor: add support for simple checklists 2024-03-12 08:15:41 +05:00
Ammar Ahmed
4b9f284ed9 editor: implement simple checklists 2024-03-12 08:15:25 +05:00
Abdullah Atta
85c812f271 web: show error if saving a note fails 2024-03-12 08:15:25 +05:00
Abdullah Atta
4aa4167351 core: fix database image is malformed error on updating locked note 2024-03-12 08:15:25 +05:00
Abdullah Atta
a8dfd91d12 web: bump version to 3.0.3-beta 2024-03-12 08:15:25 +05:00
Abdullah Atta
d23321aa68 desktop: include preload script when comparing bundles 2024-03-12 08:15:25 +05:00
Abdullah Atta
6c4ac1f2e7 core: add more lookup tests 2024-03-12 08:15:25 +05:00
Abdullah Atta
35e8b4bad6 core: allow testing with persisted database 2024-03-12 08:15:25 +05:00
Abdullah Atta
3285664e9f core: return items in same order when using .records 2024-03-12 08:15:24 +05:00
Abdullah Atta
7839bc2b6d core: improve notes lookup performance 2024-03-12 08:15:24 +05:00
Abdullah Atta
65f725dd00 core: refactor test setup & use better-sqlite-multiple-cipher 2024-03-12 08:15:24 +05:00
Abdullah Atta
b1f8b3d8f8 web: destroy sql database on app close 2024-03-12 08:15:24 +05:00
Abdullah Atta
83b6cbd98e core: fix database image is malformed error 2024-03-12 08:15:24 +05:00
Abdullah Atta
89737d9f90 core: fix note headling getting removed 2024-03-12 08:15:24 +05:00
Abdullah Atta
3b0e873c31 web: fix notebook indentation 2024-03-12 08:15:24 +05:00
Abdullah Atta
4bcd82fd7a core: fix sorting of newly created notes 2024-03-12 08:15:24 +05:00
Abdullah Atta
f5de8caa0d web: allow sorting by title everywhere 2024-03-12 08:15:24 +05:00
Abdullah Atta
13b821e061 core: add support for sorting by title & date together 2024-03-12 08:15:24 +05:00
Abdullah Atta
479f60518b web: add child notebook under opened notebook 2024-03-12 08:15:24 +05:00
Abdullah Atta
a71ac363bb web: rerender subnotebooks on root notebook change 2024-03-12 08:15:24 +05:00
Abdullah Atta
3b49a06d29 web: hide sort button in notebook view 2024-03-12 08:15:24 +05:00
Abdullah Atta
e443f3a8a3 web: await notes context refreshing 2024-03-12 08:15:24 +05:00
Abdullah Atta
6bb67318d8 web: clear keystore when clearing database 2024-03-12 08:15:24 +05:00
Abdullah Atta
0e79a5bfff web: allow clearing keystore 2024-03-12 08:15:24 +05:00
Abdullah Atta
20e7ff88b0 web: fix empty root notebook indentation 2024-03-12 08:15:24 +05:00
Abdullah Atta
e36f57850f web: fix monograph publish status not updating 2024-03-12 08:15:24 +05:00
Abdullah Atta
f76446440b web: fix nested notebook navigation from shortcuts 2024-03-12 08:15:23 +05:00
Abdullah Atta
873c3824e5 web: fix color cannot be renamed 2024-03-12 08:15:23 +05:00
Abdullah Atta
0db0b56be6 web: allow non-pro users to customize sidebar 2024-03-12 08:15:23 +05:00
Abdullah Atta
d3a7ec6b8c web: reimplement app lock & web key store 2024-03-12 08:15:23 +05:00
Ammar Ahmed
9d58dd9bab mobile: fix app lock on ios 2024-03-12 08:15:23 +05:00
Ammar Ahmed
409f5f1706 mobile: v3.0.4 2024-03-12 08:15:23 +05:00
Ammar Ahmed
1cd0c7b80a editor: fix color picker missing prop 2024-03-12 08:15:23 +05:00
Ammar Ahmed
bd6a2b2c48 mobile: improve verification flow before changing app lock 2024-03-12 08:15:23 +05:00
Ammar Ahmed
065a7fc683 mobile: fix reordering in side menu 2024-03-12 08:15:23 +05:00
Ammar Ahmed
adc55f719d editor: fix keyboard focus issues on mobile 2024-03-12 08:15:23 +05:00
Ammar Ahmed
4a266487b5 mobile: fix bugs 2024-03-12 08:15:23 +05:00
Ammar Ahmed
d7d5623cd6 mobile: fix sidemenu shortcuts 2024-03-12 08:15:22 +05:00
Abdullah Atta
5d21d3b621 web: remove security key config on app lock disable 2024-03-12 08:15:22 +05:00
Abdullah Atta
e622a379fd core: fix object id date parsing 2024-03-12 08:15:22 +05:00
Abdullah Atta
34acf8488e web: fix cr.isIndexedDBSupported is not a function 2024-03-12 08:15:22 +05:00
Ammar Ahmed
6d45a324db mobile: v3.0.3 beta 2024-03-12 08:15:22 +05:00
Ammar Ahmed
c8ccd31fe0 mobile: fix backup failed on iOS 2024-03-12 08:15:22 +05:00
Ammar Ahmed
6a2734acff mobile: bump version 2024-03-12 08:15:22 +05:00
Ammar Ahmed
e48aecc198 mobile: fix issue reporting 2024-03-12 08:15:22 +05:00
Ammar Ahmed
129dec46b3 mobile: fix rendering slow in list 2024-03-12 08:15:22 +05:00
Abdullah Atta
cbb360b714 ui: fix right clicking when menu is open doesn't close it 2024-03-12 08:15:22 +05:00
Abdullah Atta
eb9b67c0ca ui: fix hover highlighting 2 items in menu 2024-03-12 08:15:22 +05:00
Abdullah Atta
c71c3d834c web: fix color renaming and add color remove support 2024-03-12 08:15:22 +05:00
Abdullah Atta
3ccfad63bf web: add readonly indicator on notes 2024-03-12 08:15:22 +05:00
Abdullah Atta
806ce833cf web: fix compact view not affecting notes 2024-03-12 08:15:22 +05:00
Abdullah Atta
854a4d4f52 editor: fix Position -1 is outside of fragment error 2024-03-12 08:15:22 +05:00
Ammar Ahmed
9d16428781 mobile: fix migration bugs 2024-03-12 08:15:21 +05:00
Abdullah Atta
d3d4ab2f25 core: minor refactoring 2024-03-12 08:11:12 +05:00
Abdullah Atta
4575b0b516 core: migrate deleted items as well 2024-03-12 08:11:12 +05:00
Abdullah Atta
62226a186a web: refresh app on sync completed 2024-03-12 08:11:12 +05:00
Abdullah Atta
28e21acfc3 core: fix continuous invalid_grant error if login fails 2024-03-12 08:11:11 +05:00
Abdullah Atta
6d24a2e429 web: bump to v3.0.2-beta 2024-03-12 08:11:00 +05:00
Abdullah Atta
5a95ca5be8 web: hide window/tab title on app lock 2024-03-12 08:11:00 +05:00
Abdullah Atta
1ad1b91cc6 core: add support for creating backups of legacy collections 2024-03-12 08:11:00 +05:00
Abdullah Atta
a77a4f552d web: fix P.item is undefined error 2024-03-12 08:11:00 +05:00
Abdullah Atta
dade84ff7f core: remove deleteReason property 2024-03-12 08:11:00 +05:00
Ammar Ahmed
da5a7c7137 Update ios.publish.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-03-12 08:11:00 +05:00
Ammar Ahmed
e21d182cec mobile: cleanup gradle.properties 2024-03-12 08:11:00 +05:00
Ammar Ahmed
189f46cb3d mobile: fix share extension 2024-03-12 08:11:00 +05:00
Ammar Ahmed
ee98d87b11 mobile: beta release 2024-03-12 08:11:00 +05:00
Ammar Ahmed
d3a1aa0b0d mobile: fix notebook move to trash 2024-03-12 08:10:59 +05:00
Ammar Ahmed
b0e304bcea mobile: cleanup 2024-03-12 08:10:59 +05:00
Abdullah Atta
e84afd37a8 desktop: disable downgrades 2024-03-12 08:10:59 +05:00
Ammar Ahmed
64851302f4 mobile: stop app updates check in beta 2024-03-12 08:10:59 +05:00
Ammar Ahmed
dddbc671fb mobile: minor fixes 2024-03-12 08:10:59 +05:00
Abdullah Atta
5ea0254849 web: improve database deletion 2024-03-12 08:10:59 +05:00
Abdullah Atta
7ec9380fc9 web: reload page on reset from error boundary 2024-03-12 08:10:59 +05:00
Abdullah Atta
c9fb213fbb web: locked status not updating on note 2024-03-12 08:10:59 +05:00
Abdullah Atta
9a48f50a79 web: only save note's content through vault 2024-03-12 08:10:59 +05:00
Abdullah Atta
395dd8fb9e core: fix adding note to vault should return its id 2024-03-12 08:10:59 +05:00
Abdullah Atta
fcb88a9764 core: fix database resetting 2024-03-12 08:10:59 +05:00
Abdullah Atta
7d70cd2d3d theme: fix error input variant 2024-03-12 08:10:59 +05:00
Abdullah Atta
e5fbc61c26 web: allow typing custom date when adding reminder 2024-03-12 08:10:59 +05:00
Abdullah Atta
daf201cd6b common: use upcoming reminder when resolving reminders on note 2024-03-12 08:10:59 +05:00
Ammar Ahmed
6210dbc52a mobile: fix reminders 2024-03-12 08:10:59 +05:00
Ammar Ahmed
80f88f85ba mobile: fix notebooks & colors 2024-03-12 08:10:58 +05:00
Abdullah Atta
027e343841 core: set mode to "sync" for migrations during sync 2024-03-12 08:10:58 +05:00
Abdullah Atta
fc2f4ee57d desktop: patch better-sqlite3-multiple-ciphers to get prebuilt binaries 2024-03-12 08:10:58 +05:00
Abdullah Atta
0bd477ce9e web: bump to 3.0.1-beta 2024-03-12 08:10:58 +05:00
Abdullah Atta
145e778397 core: enable sync during tests 2024-03-12 08:10:58 +05:00
Abdullah Atta
0287257905 core: improve backup importing 2024-03-12 08:10:58 +05:00
Abdullah Atta
4b916dff47 core: add support for nested transactions 2024-03-12 08:10:58 +05:00
Abdullah Atta
7d18656033 web: use single transaction for importing a backup 2024-03-12 08:10:58 +05:00
Abdullah Atta
a935b677df web: make sqlite slightly faster 2024-03-12 08:09:52 +05:00
Ammar Ahmed
40ca5a3b4c mobile: fix release build 2024-03-12 08:09:52 +05:00
Abdullah Atta
4f9cd8dbb7 core: fix type errors 2024-03-12 08:09:51 +05:00
Ammar Ahmed
0d08318185 mobile: minor fixes 2024-03-12 08:09:51 +05:00
Ammar Ahmed
961dd2ef30 core: fix migration bugs 2024-03-12 08:09:21 +05:00
Ammar Ahmed
f7a5dbc71c mobile: fix saving user key 2024-03-12 08:09:21 +05:00
Abdullah Atta
303ac1db56 web: fix monograph publishing popup 2024-03-12 08:09:21 +05:00
Abdullah Atta
a421df8f1b web: ui fixes 2024-03-12 08:09:21 +05:00
Abdullah Atta
a8a9f56e02 web: fix app lock not getting disabled 2024-03-12 08:09:21 +05:00
Ammar Ahmed
236d17620d mobile: minor fixes 2024-03-12 08:09:21 +05:00
Ammar Ahmed
946b2a8555 mobile: locked notes 2024-03-12 08:09:21 +05:00
Abdullah Atta
6c868fb589 web: fix indexedDB not getting detected 2024-03-12 08:09:21 +05:00
Abdullah Atta
8bdd4b2638 core: destroy kysely on error 2024-03-12 08:09:21 +05:00
Abdullah Atta
d4deb90616 crypto: move libsodium & sodium-native to peer deps 2024-03-12 08:09:20 +05:00
Abdullah Atta
15744feb41 global: pin node version to 20 2024-03-12 08:09:20 +05:00
Abdullah Atta
e75005df89 web: various fixes 2024-03-12 08:09:20 +05:00
Abdullah Atta
b1fac8f9ce common: fix note resolution 2024-03-12 08:09:20 +05:00
Abdullah Atta
4ef77c5924 core: fix database migration 2024-03-12 08:09:20 +05:00
Abdullah Atta
9f1db293a0 core: disable sync push 2024-03-12 08:09:20 +05:00
Abdullah Atta
03ac543ae0 web: trigger error boundary on unhandled errors 2024-03-12 08:09:20 +05:00
Abdullah Atta
cf929ba1ce web: use safeStorage to encrypt secrets on desktop 2024-03-12 08:09:20 +05:00
Abdullah Atta
f3928db1ab desktop: use better-sqlite3 on desktop for SQLite 2024-03-12 08:09:20 +05:00
Abdullah Atta
e92dbadd8e desktop: fix autoupdater 2024-03-12 08:09:20 +05:00
Abdullah Atta
8eca2cb2b8 desktop: add api to delete files 2024-03-12 08:09:20 +05:00
Abdullah Atta
f0c5951eb0 desktop: expose electron's safeStorage api via trpc 2024-03-12 08:09:20 +05:00
Abdullah Atta
c3d18e4177 desktop: enable native modules rebuild during build 2024-03-12 08:09:20 +05:00
Ammar Ahmed
847037b2f7 core: return decrypted content on unlock note 2024-03-12 08:09:20 +05:00
Ammar Ahmed
6227e50dec mobile: applock with pin and password 2024-03-12 08:09:20 +05:00
Ammar Ahmed
89b6030bc3 mobile: minor fixes 2024-03-12 08:09:19 +05:00
Ammar Ahmed
248f692c2b mobile: sqlite-multiple-ciphers patch 2024-03-12 08:09:19 +05:00
Ammar Ahmed
6bac78117f mobile: refactor 2024-03-12 08:09:19 +05:00
Ammar Ahmed
e3ce44878e mobile: fix reorderable side menu 2024-03-12 08:09:19 +05:00
Ammar Ahmed
5f99783683 mobile: yearly reminders 2024-03-12 08:09:19 +05:00
Abdullah Atta
3d4cc1c4d7 web: add support for yearly reminders 2024-03-12 08:09:19 +05:00
Abdullah Atta
a2a05c5d8c core: add support for yearly reminders 2024-03-12 08:09:19 +05:00
Ammar Ahmed
c8eea0a0e4 mobile: stop bg fetch events when toggled off 2024-03-12 08:09:18 +05:00
Ammar Ahmed
8736a58a02 mobile: enable experimental background sync 2024-03-12 08:09:18 +05:00
Abdullah Atta
53c75ca6ef web: add support for hiding items from side bar 2024-03-12 08:09:18 +05:00
Abdullah Atta
506fc227c3 web: add support for reordering navigation menu items 2024-03-12 08:09:18 +05:00
Ammar Ahmed
4f40bdd262 mobile: cleanup 2024-03-12 08:09:18 +05:00
Ammar Ahmed
7bcd1df35a core: add sidebar order settings 2024-03-12 08:09:18 +05:00
Ammar Ahmed
1e4d63f84f mobile: reorder sidebar items 2024-03-12 08:09:18 +05:00
Abdullah Atta
7e7bb1b1b3 web: add support for custom colors 2024-03-12 08:09:18 +05:00
Ammar Ahmed
8e612db63f mobile: allow custom colors 2024-03-12 08:09:04 +05:00
Ammar Ahmed
140468cf3d mobile: migrate app lock 2024-03-12 08:09:04 +05:00
Abdullah Atta
114466e3a6 web: remove cbor-x package 2024-03-12 08:09:04 +05:00
Abdullah Atta
6c90653937 web: add support for at rest encryption and app lock 2024-03-12 08:09:03 +05:00
Ammar Ahmed
aec4c510f1 mobile: at rest encryption 2024-03-12 08:04:34 +05:00
Ammar Ahmed
8722157f33 mobile: simplify tests 2024-03-12 08:04:10 +05:00
Abdullah Atta
15b2aa6445 core: remove dummy test files 2024-03-12 08:04:10 +05:00
Abdullah Atta
7bddfa096f core: migrate key value storage to sqlite 2024-03-12 08:04:09 +05:00
Ammar Ahmed
7048c7e8db mobile: update sheet component 2024-03-12 08:03:42 +05:00
Abdullah Atta
508a259884 core: fix moving nested notebooks to trash 2024-03-12 08:03:42 +05:00
Abdullah Atta
8c5c9d447f global: move item resolution logic to @notesnook/common 2024-03-12 08:03:42 +05:00
Ammar Ahmed
fd951c3aaa mobile: add theme switch animation 2024-03-12 08:03:42 +05:00
Abdullah Atta
a026eb4a04 web: fix all type errors 2024-03-12 08:03:42 +05:00
Abdullah Atta
b02c43c99d web: fix duplicate imports 2024-03-12 08:03:12 +05:00
Abdullah Atta
c2be3a4f4d core: fix type error 2024-03-12 08:03:12 +05:00
Abdullah Atta
2b4155ad1d core: add tests for sync merger 2024-03-12 08:03:11 +05:00
Abdullah Atta
829a8a7ef8 core: migrate vault to a database collection 2024-03-12 08:03:11 +05:00
Ammar Ahmed
77c3e41564 mobile: fix exporter and attachment download 2024-03-12 08:03:11 +05:00
Ammar Ahmed
8e932b7ca0 global: fix type errors during build 2024-03-12 08:03:11 +05:00
Abdullah Atta
6e5c3e83d8 web: use simple transform stream for export progress 2024-03-12 08:03:11 +05:00
Abdullah Atta
a3db77285e web: fix exporting 2024-03-12 08:03:11 +05:00
Ammar Ahmed
0d864dfa10 mobile: add patches 2024-03-12 08:03:11 +05:00
Abdullah Atta
ae3d900af6 web: update export note snapshots for linux 2024-03-12 08:03:11 +05:00
Abdullah Atta
992a1d5231 web: fix reminder tests 2024-03-12 08:03:11 +05:00
Abdullah Atta
0a01ef6357 web: refresh notebooks after adding a new notebook 2024-03-12 08:03:11 +05:00
Abdullah Atta
7c6e79d78d web: fix force redirect to home on navigating to a color 2024-03-12 08:03:11 +05:00
Abdullah Atta
6084bb78a1 web: fix crash when sorting tags 2024-03-12 08:03:11 +05:00
Abdullah Atta
df987f6e94 web: fix crash when removing a notebook 2024-03-12 08:03:10 +05:00
Ammar Ahmed
a1e237a82a mobile: sync with latest core changes 2024-03-12 08:03:10 +05:00
Abdullah Atta
1e97e81fe7 core: remove unnecessary console.log 2024-03-12 08:03:10 +05:00
Ammar Ahmed
3ecda367f8 mobile: fix search to use filtered selector for notes 2024-03-12 08:03:10 +05:00
Abdullah Atta
afa4432521 core: prevent recursive sync after pushing items 2024-03-12 08:03:10 +05:00
Abdullah Atta
b24bc4fda0 web: simplify sync status 2024-03-12 08:03:10 +05:00
Abdullah Atta
0cca379a97 web: fix previewing trashed notes 2024-03-12 08:03:10 +05:00
Abdullah Atta
ca9e701386 core: fix trash 2024-03-12 08:03:10 +05:00
Abdullah Atta
48d79acbd3 web: improve sync test reliability 2024-03-12 08:03:10 +05:00
Abdullah Atta
2dabc62df3 core: auto sync not triggered on note update 2024-03-12 08:03:10 +05:00
Abdullah Atta
2ab7564622 web: fix note history tests 2024-03-12 08:01:37 +05:00
Abdullah Atta
0c0f19b23f web: fix selection logic in list 2024-03-12 08:01:37 +05:00
Abdullah Atta
1e5847e09e web: set testId on auth input fields 2024-03-12 08:01:37 +05:00
Abdullah Atta
e6d0061ca0 core: fix note content is not saving 2024-03-12 08:01:37 +05:00
Abdullah Atta
35b56fe8d2 core: fix data is not iterable error on backup restore 2024-03-12 08:01:37 +05:00
Abdullah Atta
4cd7f604df global: update lockfiles 2024-03-12 08:01:37 +05:00
Abdullah Atta
e65c36eff7 core: add retry if a test fails 2024-03-12 08:01:37 +05:00
Abdullah Atta
2e39e96846 core: fix invalid object-id generation for some timestamps 2024-03-12 08:01:37 +05:00
Abdullah Atta
357019a807 core: fix sync & monographs tests 2024-03-12 08:01:36 +05:00
Abdullah Atta
bb014cbb36 core: fix auto sync starting even when auto sync is disabled 2024-03-12 08:01:20 +05:00
Abdullah Atta
5d0e7ea28b core: fix sync tests 2024-03-12 08:00:59 +05:00
Abdullah Atta
96903c512b core: remove unnecessary migration success log 2024-03-12 08:00:59 +05:00
Abdullah Atta
b9acb8a213 core: fix Crypto is not supported on this platform. error during tests 2024-03-12 08:00:59 +05:00
Abdullah Atta
48e625480c core: make all the core tests pass again 2024-03-12 08:00:59 +05:00
Abdullah Atta
bc3dce6ecf web: fix crash when note has no title 2024-03-12 08:00:59 +05:00
Abdullah Atta
0da883b40a core: set default title if note has no title 2024-03-12 08:00:59 +05:00
Abdullah Atta
56d230191f web: update kysely patch 2024-03-12 08:00:59 +05:00
Abdullah Atta
abdb9626d0 web: run patch-package after npm i 2024-03-12 08:00:59 +05:00
Abdullah Atta
567eb789fc web: more fixes 2024-03-12 08:00:58 +05:00
Abdullah Atta
aa85b57e75 web: fix many type errors 2024-03-12 08:00:58 +05:00
Abdullah Atta
b5d9a83e1b global: update lockfiles 2024-03-12 07:58:39 +05:00
Abdullah Atta
ee2d0912fd theme: fix tinycolor2 not found error 2024-03-12 07:58:39 +05:00
Abdullah Atta
22cb2f1829 web: fix notes search in tags, favorites etc 2024-03-12 07:58:39 +05:00
Abdullah Atta
c3f76fa7c9 web: update wa-sqlite 2024-03-12 07:58:39 +05:00
Abdullah Atta
6923801426 web: use memory tempStore for sqlite 2024-03-12 07:58:39 +05:00
Abdullah Atta
210b6ba10c web: add support for exporting sql database 2024-03-12 07:58:39 +05:00
Abdullah Atta
b3bf5ff7a9 core: fix FTS5 SQLite search 2024-03-12 07:58:39 +05:00
Ammar Ahmed
ab1ff2f0bb mobile: cleanup 2024-03-12 07:58:38 +05:00
Ammar Ahmed
a813a5dd92 mobile: fix types 2024-03-12 07:58:38 +05:00
Ammar Ahmed
b85fc2d473 mobile: refactor zustand stores 2024-03-12 07:58:38 +05:00
Ammar Ahmed
112b44802f mobile: bug fixes 2024-03-12 07:58:38 +05:00
Ammar Ahmed
c121d4a010 core: add fn to get cache item 2024-03-12 07:58:07 +05:00
Abdullah Atta
26c7f04317 web: only auto align title & headline 2024-03-12 07:58:07 +05:00
Abdullah Atta
1fe3e5102b web: getKey -> key 2024-03-12 07:58:07 +05:00
Abdullah Atta
e0d44e58e6 web: fix resolved item data misaligned with items 2024-03-12 07:58:07 +05:00
Abdullah Atta
3103a7b97c core: fix many issues with virtualized grouping 2024-03-12 07:58:07 +05:00
Ammar Ahmed
ef802bfa5c core: early return when items are empty 2024-03-12 07:58:07 +05:00
Abdullah Atta
a1c2f3f679 web: fix random crashes when scrolling in list 2024-03-12 07:58:07 +05:00
Abdullah Atta
c7843effc6 web: replace react-virtual with react-virtuouso 2024-03-12 07:58:07 +05:00
Ammar Ahmed
1bd9ecca37 mobile: fix build ang bugs 2024-03-12 07:58:07 +05:00
Abdullah Atta
2f55f36802 web: hide attachments access if user is logged out 2024-03-12 07:58:07 +05:00
Abdullah Atta
af5b05df70 web: allow core to specify custom sql db name 2024-03-12 07:58:07 +05:00
Abdullah Atta
1b2479cad2 web: close idb connection on sqlite connection close 2024-03-12 07:58:07 +05:00
Abdullah Atta
b92b4bff4c core: clear database on logout 2024-03-12 07:58:06 +05:00
Ammar Ahmed
e8301907cd core: add tempStore option to DB 2024-03-12 07:58:06 +05:00
Abdullah Atta
2c3b400b5d core: migrate items sequentially during sync 2024-03-12 07:58:06 +05:00
Abdullah Atta
8350d24822 core: fix build error 2024-03-12 07:58:06 +05:00
Abdullah Atta
934f77d369 web: execute only 1 query at a time 2024-03-12 07:58:06 +05:00
Abdullah Atta
e8a9301434 web: fix diff viewer crashing the app 2024-03-12 07:58:06 +05:00
Abdullah Atta
58b51201d9 core: do not stop sync if a conflict occurs 2024-03-12 07:58:06 +05:00
Abdullah Atta
02bc3a9db4 core: allow only 1 transaction at a time 2024-03-12 07:58:06 +05:00
Abdullah Atta
b777546bc9 core: simplify item key derivation in virtualized grouping 2024-03-12 07:58:06 +05:00
Abdullah Atta
5b96b956e8 web: improve list item rendering performance 2024-03-12 07:58:06 +05:00
Abdullah Atta
e90f2d7087 core: always send realtime sync event 2024-03-12 07:58:06 +05:00
Abdullah Atta
8c1e0d2414 core: fix content date edited not updating 2024-03-12 07:58:06 +05:00
Abdullah Atta
007c9859dc core: put items in chunks of 200 2024-03-12 07:58:06 +05:00
Abdullah Atta
c72cac1213 web: set name prop on fields 2024-03-12 07:58:06 +05:00
Abdullah Atta
f8b6dd6d1a core: implement sync v2 2024-03-12 07:58:05 +05:00
Ammar Ahmed
5e71df03af mobile: use index based grouping 2024-03-12 07:55:47 +05:00
Abdullah Atta
e3f74f7f6f web: changes to use new grouping api 2024-03-12 07:55:47 +05:00
Abdullah Atta
5a759268b8 core: implement new high perf indexed based grouping 2024-03-12 07:55:47 +05:00
Ammar Ahmed
a9d98f73e4 mobile: fix share extension in release 2024-03-12 07:55:21 +05:00
Abdullah Atta
65dcd6e035 web: fix router types 2024-03-12 07:55:21 +05:00
Abdullah Atta
921b802aa0 web: enable vite hot reloading 2024-03-12 07:55:21 +05:00
Abdullah Atta
0b767de51b web: fix minor build issues 2024-03-12 07:55:21 +05:00
Abdullah Atta
e9bd553116 web: migrate everything to react@18 2024-03-12 07:55:21 +05:00
Abdullah Atta
d4fcf1687a web: fix notebook tests 2024-03-12 07:55:21 +05:00
Ammar Ahmed
996fb6d60f mobile: share extension cleanup 2024-03-12 07:55:21 +05:00
Ammar Ahmed
fd6d259d6c mobile: cleanup 2024-03-12 07:55:21 +05:00
Ammar Ahmed
4f16d172f8 mobile: migrate share extension 2024-03-12 07:55:21 +05:00
Ammar Ahmed
b94c3ac182 mobile: fix navigation 2024-03-12 07:55:21 +05:00
Abdullah Atta
ea383c80f2 web: send noteId when decrypting real-time locked content 2024-03-12 07:55:21 +05:00
Abdullah Atta
1b769f23cb web: fix tag tests 2024-03-12 07:55:20 +05:00
Ammar Ahmed
d5feab78c8 mobile: pass tests 2024-03-12 07:55:20 +05:00
Abdullah Atta
043099d217 web: fix html export snapshot 2024-03-12 07:55:20 +05:00
Abdullah Atta
670a8a05d1 core: fix md with frontmatter export 2024-03-12 07:55:20 +05:00
Ammar Ahmed
01ea478e46 core: fix ts error 2024-03-12 07:55:20 +05:00
Ammar Ahmed
ebd3ed06ab mobile: fix webpack build 2024-03-12 07:55:20 +05:00
Ammar Ahmed
f34591228e mobile: push changes 2024-03-12 07:55:20 +05:00
Abdullah Atta
7247ad4a05 web: fix notes tests 2024-03-12 07:55:20 +05:00
Abdullah Atta
e4fd25cb76 core: prevent duplicate colors based on color code 2024-03-12 07:55:00 +05:00
Abdullah Atta
3c67928583 core: fix error on locking notes 2024-03-12 07:55:00 +05:00
Ammar Ahmed
9aaa915caa mobile: push changes 2024-03-12 07:55:00 +05:00
Abdullah Atta
43318b185b web: reset search results on navigation 2024-03-12 07:55:00 +05:00
Abdullah Atta
015666388f web: reset prepared sql stmt on error 2024-03-12 07:55:00 +05:00
Abdullah Atta
542f7687f5 web: reuse search query 2024-03-12 07:55:00 +05:00
Abdullah Atta
5b744eae3c core: use porter trigram tokenizer for fts 2024-03-12 07:54:59 +05:00
Abdullah Atta
28cd11fdc7 core: properly escape fts query 2024-03-12 07:54:59 +05:00
Abdullah Atta
29ccbd0942 web: add support for assigning tags to notes 2024-03-12 07:54:59 +05:00
Abdullah Atta
30a127d2da core: fix "type" does not exist 2024-03-12 07:54:59 +05:00
Abdullah Atta
0d7973a34c core: fix build error 2024-03-12 07:54:59 +05:00
Ammar Ahmed
487edd7165 mobile: migrate to sqlite 2024-03-12 07:54:59 +05:00
Abdullah Atta
b3fe79b0a7 web: fix note editor properties 2024-03-12 07:53:23 +05:00
Abdullah Atta
966047585c common: type is now optional in getFormattedDate 2024-03-12 07:53:05 +05:00
Abdullah Atta
d57ca9b07c core: return FilteredSelector from attachments.ofNote 2024-03-12 07:53:05 +05:00
Abdullah Atta
e76b7ee0a5 web: show notebook & tag shortcuts when assigning notes 2024-03-12 07:53:05 +05:00
Abdullah Atta
2b42315c53 web: implement virtualized search for all items 2024-03-12 07:53:05 +05:00
Abdullah Atta
947443b4c7 web: use lazy loading to load attachments 2024-03-12 07:53:04 +05:00
Abdullah Atta
2547ab42e9 web: load tag & color title lazily 2024-03-12 07:52:42 +05:00
Abdullah Atta
306aca0645 web: show all nested notebooks as tree when moving notes 2024-03-12 07:52:42 +05:00
Abdullah Atta
f2b2dc33e6 web: add support for nested notebooks 2024-03-12 07:52:42 +05:00
Abdullah Atta
7a8f8dc565 web: use new virtualized grouping for reminders 2024-03-12 07:52:42 +05:00
Abdullah Atta
0528115ad3 web: use new virtualized grouping for trash 2024-03-12 07:52:42 +05:00
Abdullah Atta
4a5ee49db6 web: use new virtualized grouping for monographs 2024-03-12 07:52:42 +05:00
Abdullah Atta
3ca761013d core: add support for fetching trash as grouped 2024-03-12 07:52:42 +05:00
Abdullah Atta
bdeb123f18 web: install missing @hazae41/foras 2024-03-12 07:52:42 +05:00
Abdullah Atta
3f5f84aac0 core: fix attachment extraction for locked notes 2024-03-12 07:52:41 +05:00
Abdullah Atta
01d3bd7810 global: update lockfiles 2024-03-12 07:52:41 +05:00
Abdullah Atta
8e6d6e20d7 web: start initial migration to sqlite 2024-03-12 07:52:41 +05:00
Abdullah Atta
375185a9ea ui: add support for lazily loading menu items 2024-03-12 07:51:38 +05:00
Abdullah Atta
c2e9773486 common: remove total-notes utility 2024-03-12 07:51:38 +05:00
Abdullah Atta
5e0e7b17e2 core: changes according to client needs 2024-03-12 07:51:38 +05:00
Abdullah Atta
aa90684616 core: fix database migration 2024-03-12 07:51:37 +05:00
Abdullah Atta
fa47b4151f core: add support for incremental grouping 2024-03-12 07:51:24 +05:00
Abdullah Atta
eca5db19a2 core: fix size of nanoid in benchmarks 2024-03-12 07:51:24 +05:00
Abdullah Atta
b053b0eab4 core: optimize bson objectid generation & add benchmarks 2024-03-12 07:51:23 +05:00
Abdullah Atta
70da1045d7 core: fix monographs 2024-03-12 07:51:10 +05:00
Abdullah Atta
b5fd2b0751 core: fix sync tests 2024-03-12 07:51:10 +05:00
Abdullah Atta
97f6f09882 core: remove temporary sqlite db 2024-03-12 07:51:10 +05:00
Abdullah Atta
0c773727ee core: migrate everything to sqlite 2024-03-12 07:51:10 +05:00
Abdullah Atta
952ce39f1c core: migrate all database writes to sqlite 2024-03-12 07:49:55 +05:00
Abdullah Atta
057c7f3f5d global: update lockfiles 2024-03-12 07:49:14 +05:00
Abdullah Atta
9439c52801 core: convert everything else to typescript 2024-03-12 07:48:50 +05:00
Abdullah Atta
b1cebbe8f2 core: remove unnecessary sync tests 2024-03-12 07:48:50 +05:00
Abdullah Atta
836a47fd96 core: migrate monographs to typescript 2024-03-12 07:48:50 +05:00
Abdullah Atta
136b2ed457 core: make migration tests less flaky 2024-03-12 07:48:50 +05:00
Abdullah Atta
7f4006de95 core: add tests for migrations 2024-03-12 07:48:49 +05:00
Abdullah Atta
dc96209ed9 core: migrate settings to its own collection 2024-03-12 07:48:09 +05:00
Abdullah Atta
9b0332d5e0 core: get rid of topics; move to sub notebooks 2024-03-12 07:47:20 +05:00
Abdullah Atta
a1300fefd6 core: migrate attachments to use relations for linking to notes 2024-03-12 07:47:20 +05:00
Abdullah Atta
27032e853b core: minor updates and fixes 2024-03-12 07:47:20 +05:00
Abdullah Atta
22646e1398 core: fix types & syncing issues 2024-03-12 07:47:20 +05:00
ammarahm-ed
6e5e7c92c9 mobile: pass all tests 2024-03-12 07:41:34 +05:00
ammarahm-ed
3254109ea5 mobile: fix filesystem not loaded 2024-03-12 07:41:34 +05:00
ammarahm-ed
98ab2bfd8f mobile: fix navigating to tags 2024-03-12 07:41:34 +05:00
ammarahm-ed
6cbf22c163 mobile: migrate to typescript 2024-03-12 07:41:33 +05:00
ammarahm-ed
1714c1abca common: fix ts errors 2024-03-12 07:41:33 +05:00
Abdullah Atta
1bf65c7a53 mobile: use new types from core 2024-03-12 07:41:33 +05:00
Abdullah Atta
0075fde915 web: use new types from core 2024-03-12 07:41:33 +05:00
Abdullah Atta
318c61f531 core: fix more typings 2024-03-12 07:39:14 +05:00
Abdullah Atta
6dc502fd92 core: fix typing errors 2024-03-12 07:38:43 +05:00
Abdullah Atta
db7d7edcb5 core: convert almost everything to typescript 2024-03-12 07:38:43 +05:00
Ammar Ahmed
278840bcfd mobile: fix lint errors 2024-03-12 07:28:59 +05:00
Abdullah Atta
f149ad7c71 ci: update node version to 20 2024-03-12 07:28:59 +05:00
Abdullah Atta
ad29de8a04 config: update nx 2024-03-12 07:28:58 +05:00
Abdullah Atta
0759098414 desktop: set release channel to beta 2024-03-12 07:28:45 +05:00
Ammar Ahmed
528751a3ce mobile: update ios version number 2024-03-12 07:28:45 +05:00
Ammar Ahmed
3dd5bd8c4a ci: update workflow name
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-03-12 07:28:23 +05:00
Ammar Ahmed
1ed84fe1b4 mobile: v3 beta release 2024-03-12 07:28:23 +05:00
Ammar Ahmed
1dcf8daecb ci: update iOS workflow
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-03-12 07:25:38 +05:00
Ammar Ahmed
fc0ac1e02a Update android.publish.internal.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-03-12 07:25:38 +05:00
Ammar Ahmed
a1efab6673 ci: android.publish.yml
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-03-12 07:25:38 +05:00
Abdullah Atta
4a6445e241 desktop: do not set teamId for notarization 2024-03-12 07:25:38 +05:00
Abdullah Atta
3fadb6016c ci: APPLE_API_KEY_ISSUER > APPLE_API_ISSUER 2024-03-12 07:25:38 +05:00
Abdullah Atta
99f469ba38 desktop: fix unknown property 'notarize' error 2024-03-12 07:25:38 +05:00
Abdullah Atta
d1753f449a desktop: use official electron builder notarization 2024-03-12 07:25:38 +05:00
Abdullah Atta
fe19a6b0cc ci: update {upload,download}-artifact to v4 2024-03-12 07:25:38 +05:00
Abdullah Atta
bb67472dee ci: get rid of you may only define one of branches and branches-ignore for a single event 2024-03-12 07:25:38 +05:00
Abdullah Atta
13213cf4cd ci: use m1 for building on mac 2024-03-12 07:25:23 +05:00
Abdullah Atta
b49a890849 ci: use wrangler directly to publish to cf 2024-03-12 07:25:23 +05:00
Abdullah Atta
3eb21afd42 desktop: use app version from autoUpdater 2024-03-12 07:25:23 +05:00
Abdullah Atta
b98021929f ci: update cloudflare action 2024-03-12 07:25:23 +05:00
Abdullah Atta
40ccad1181 desktop: ready beta for deployment 2024-03-12 07:25:23 +05:00
Abdullah Atta
e2db62d551 ci: deploy to correct cf pages project
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-03-12 07:24:45 +05:00
Abdullah Atta
0229641917 core: turn off sync push by default 2024-03-12 07:24:45 +05:00
Abdullah Atta
a3b590bcb7 core: add more login tests 2024-03-12 07:19:59 +05:00
Ammar Ahmed
d4f7e3ef07 mobile: add changelog 2024-03-06 15:31:33 +05:00
Abdullah Atta
16119f6958 setup: optimize bootstrap script 2024-03-06 13:40:42 +05:00
Abdullah Atta
e39c2583a5 setup: only run npm rebuild if required 2024-03-06 13:40:41 +05:00
Abdullah Atta
eecff009dd setup: run npm rebuild after bootstrap 2024-03-06 13:40:41 +05:00
Abdullah Atta
d9ed61a7da setup: ignore scripts during bootstrap 2024-03-06 13:40:41 +05:00
Abdullah Atta
58db265702 setup: use 1 thread on CI 2024-03-06 13:40:41 +05:00
Abdullah Atta
bd39f0cbf8 core: fix e.metadata.hash is not defined 2024-03-06 13:29:59 +05:00
Abdullah Atta
255015108a core: update lockfile 2024-03-06 13:01:00 +05:00
Abdullah Atta
b80f366f8d core: run patch-package after install 2024-03-06 13:00:34 +05:00
Ammar Ahmed
c845c7fce6 mobile: release v2.6.17 2024-03-06 12:59:23 +05:00
Ammar Ahmed
6e7b7ce0e1 mobile: ask user to save recovery key after changing password 2024-03-06 12:33:38 +05:00
Abdullah Atta
9b728902d4 web: bump version to 2.6.16 2024-03-06 12:03:06 +05:00
Abdullah Atta
9a1f54c41a desktop: set proxy on startup 2024-03-06 11:43:50 +05:00
Abdullah Atta
b3251eb016 core: fix login gets stuck on error 2024-03-06 11:28:45 +05:00
Abdullah Atta
f4064b7939 core: patch signalr to exclude electron renderer process as node 2024-03-06 11:28:27 +05:00
Abdullah Atta
f014f08ff4 web: fix export to html test snapshot 2024-03-05 15:41:53 +05:00
Abdullah Atta
dee1cf4b5a web: fix locked note status not updating in list 2024-03-05 15:38:01 +05:00
Abdullah Atta
f35a3d8fa3 web: clear editor on unlocking the note 2024-03-05 15:37:45 +05:00
Abdullah Atta
ca9a8d14fa core: fix sync tests 2024-03-05 15:03:18 +05:00
Abdullah Atta
35c35fa9d7 core: update test snapshots 2024-03-05 14:51:41 +05:00
Abdullah Atta
09fff95d58 editor: fix marks.filter is not a function error on pressing Enter 2024-03-05 14:27:29 +05:00
Abdullah Atta
287307a1ba core: move to json protocol for signalr 2024-03-05 14:23:44 +05:00
Abdullah Atta
d628e62302 web: fix app hangs when downloading logs 2024-03-05 14:18:54 +05:00
Ammar Ahmed
a070bc46ad mobile: check backup was successful 2024-03-05 14:16:35 +05:00
Ammar Ahmed
9027c44511 mobile: request backup before changing password 2024-03-05 14:16:35 +05:00
Abdullah Atta
722aa7b9c6 mobile: update database sync api to use new sync options 2024-03-05 14:16:35 +05:00
Abdullah Atta
a4e777b24b web: update database sync api to use new sync options 2024-03-05 14:16:35 +05:00
Abdullah Atta
af7120e953 web: create backup before changing password 2024-03-05 14:16:35 +05:00
Abdullah Atta
d5adc3bbac web: refactor backup verification logic 2024-03-05 14:16:35 +05:00
Abdullah Atta
aa3d778ad1 core: reduce unnecessary syncing during password change 2024-03-05 14:16:35 +05:00
Abdullah Atta
3465a816c6 web: rename to markdown shortcuts 2024-03-05 13:42:39 +05:00
Ammar Ahmed
3d1726c5aa mobile: toggle markdown shortcuts 2024-03-05 13:42:39 +05:00
Muhammad Ali
488de2d583 web: add setting for toggling markdown in editor 2024-03-05 13:42:39 +05:00
Abdullah Atta
c415b114a6 web: template not disabling when copying as markdown 2024-03-05 13:32:54 +05:00
Abdullah Atta
da36784e9c core: fix content inside span not converting to markdown 2024-03-05 13:32:54 +05:00
Muhammad Ali
78fafdc272 editor: convert google docs checklists on paste 2024-03-05 13:32:54 +05:00
Muhammad Ali
7d57055f73 editor: fix new lines from google docs not being preserved on paste (#3988)
* editor: handle <br> directly under root element

* editor: fix pasting from google docs

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-03-05 12:14:19 +05:00
Ammar Ahmed
b84cc747cb mobile: restore backup with encryption key 2024-03-04 15:56:12 +05:00
Abdullah Atta
9a4f3ccbad web: allow using encryption key to restore backup 2024-03-04 15:56:12 +05:00
Abdullah Atta
68de3cc928 core: allow using encryption key to decrypt backup 2024-03-04 15:56:12 +05:00
Ammar Ahmed
6e135cdd36 mobile: sort reminders 2024-03-04 15:27:42 +05:00
Abdullah Atta
696116ac85 web: add support for sorting reminders 2024-03-04 15:27:42 +05:00
Abdullah Atta
08a13ff35a core: add support for group options for reminders 2024-03-04 15:27:42 +05:00
Abdullah Atta
af1cea81dc theme: use radio focus color from theme 2024-03-04 15:27:42 +05:00
Muhammad Ali
2c22a7ca90 core: add support for sorting reminders by due date 2024-03-04 15:27:42 +05:00
Muhammad Ali
6fd6bd0375 desktop: add proxy settings (#4196) 2024-03-04 15:16:59 +05:00
Muhammad Ali
1a96dc965a desktop: add support for viewing and removing custom dictionary words (#4362)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-03-04 13:47:24 +05:00
Muhammad Ali
9550350f25 desktop: add menu item to copy images in editor (#4373) 2024-03-04 13:27:46 +05:00
Muhammad Ali
0ed605a5b9 editor: fix column toolbar position when table is scrolled horizontally (#4366) 2024-03-04 13:25:46 +05:00
Abdullah Atta
18910d931c config: add .nx to .gitignore 2024-03-04 13:24:25 +05:00
Muhammad Ali
87648b7d8b editor: fix katex math not being rendered correctly (#4353)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-03-04 13:23:27 +05:00
Muhammad Ali
7e7a1aeecb editor: add support for toggling highlight/background color (#4330) 2024-03-04 13:22:25 +05:00
Muhammad Ali
cb353358b7 web: fix editor text not selectable from margins (#4292)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-03-04 13:20:40 +05:00
Muhammad Ali
ba1619711b desktop: fix app autostart in appimage (#4223) 2024-03-04 12:40:51 +05:00
Ammar Ahmed
b3cc9475ad mobile: fix attachment search 2024-03-01 08:22:14 +05:00
Abdullah Atta
ee5c4077fe docs: improve skiff pages import guide 2024-02-13 01:07:14 +05:00
Abdullah Atta
47bbfc618e docs: add guide for migrating from skiff pages 2024-02-12 10:49:40 +05:00
Abdullah Atta
b377329a8b core: fix attachment mime type on read 2024-02-02 16:02:20 +05:00
Abdullah Atta
ae8bcf3865 ci: fix you may only define one of branches and branches-ignore for a single event
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-01-31 12:46:32 +05:00
Abdullah Atta
2f67611829 ci: add workflows for v3 beta publishing 2024-01-31 12:41:08 +05:00
Ammar Ahmed
058c612f4f ci: update default build tools version android
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-01-30 11:41:20 +05:00
Ammar Ahmed
be8df90ccb iOS Publish Workflow (#4194)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-01-30 11:32:48 +05:00
Abdullah Atta
aa730b5b93 web: fix title only txt exports 2024-01-30 00:29:46 +05:00
Abdullah Atta
34bbcf6387 web: add feature highlights for v2.6.15 2024-01-29 23:33:15 +05:00
Abdullah Atta
33c3c78e2f desktop: get rid of configuration.win.signingHashAlgorithms should be an array 2024-01-29 23:27:45 +05:00
Ammar Ahmed
bf144dcda4 mobile: add ExportOptions.plist 2024-01-29 23:23:48 +05:00
Ammar Ahmed
284ebdb86f ci: @notesnook/ios
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-01-29 23:23:01 +05:00
Abdullah Atta
9dac4cf7f4 desktop: set signing has to sha256 2024-01-29 23:22:29 +05:00
Abdullah Atta
fb973a5514 ci: run build job no matter what 2024-01-29 23:22:29 +05:00
Abdullah Atta
f9226510f9 desktop: use full path for FilesCatalog 2024-01-29 23:22:29 +05:00
Abdullah Atta
5c0a964699 ci: add inputs to disable build for specific platforms 2024-01-29 23:22:29 +05:00
Abdullah Atta
41ffaab3dc ci: install azurecodesigning module 2024-01-29 23:22:29 +05:00
Abdullah Atta
764c721f1d desktop: add windows code signing 2024-01-29 23:22:29 +05:00
Ammar Ahmed
3d267f91ef mobile: bump version code 2024-01-29 19:55:41 +05:00
Ammar Ahmed
f10f28a7f8 mobile: release v2.6.15 2024-01-29 19:32:14 +05:00
Ammar Ahmed
8d04f89a18 mobile: remove progress bar 2024-01-29 19:06:01 +05:00
Ammar Ahmed
9d1374dcd4 editor: fix progress not showing in image on mobile 2024-01-29 19:05:49 +05:00
Abdullah Atta
094a65e514 editor: hide progress on image if 0 2024-01-29 18:26:44 +05:00
Ammar Ahmed
372da979e1 mobile: support ignoring edits 2024-01-29 18:26:44 +05:00
Abdullah Atta
d3371326f2 editor: add progress indicator to image 2024-01-29 18:26:44 +05:00
Abdullah Atta
57452bd0e6 core: allow setting custom date edited 2024-01-29 18:26:44 +05:00
Abdullah Atta
993b8460bf web: do not update dateEdited if ignoring edits 2024-01-29 18:26:44 +05:00
Abdullah Atta
081201aa42 editor: add support for ignoring edits 2024-01-29 18:26:44 +05:00
Abdullah Atta
270e6778d1 editor: optimize image loading when fast scrolling 2024-01-29 16:23:59 +05:00
Abdullah Atta
2074320583 editor: revoke blob urls on image unmount 2024-01-29 16:09:05 +05:00
Abdullah Atta
cd90030616 web: fix note title in toolbar not changing 2024-01-29 15:45:13 +05:00
Abdullah Atta
807bd683b7 web: do not show success prompt on unlock cancel 2024-01-29 15:36:01 +05:00
Abdullah Atta
35929026a4 web: monograph.notesnook.com > monogr.ph 2024-01-29 15:22:22 +05:00
Ammar Ahmed
41eb94ee47 mobile: fix exporting locked notes 2024-01-29 11:02:32 +05:00
Ammar Ahmed
ba51e110ad mobile: fix opening webclips 2024-01-29 11:02:32 +05:00
Ammar Ahmed
ecad06d249 mobile: disable rtl mode 2024-01-29 11:02:32 +05:00
Abdullah Atta
19a151f637 editor: fix image query matching all images in editor 2024-01-28 17:17:45 +05:00
Ammar Ahmed
7c4a6f1e17 global: update deps 2024-01-27 23:06:24 +05:00
Ammar Ahmed
bc88e953d1 mobile: make sure tools render in toolbar 2024-01-27 23:05:30 +05:00
Ammar Ahmed
7d89ec676a mobile: fix attachments not loading when added from share ext 2024-01-27 22:50:41 +05:00
Ammar Ahmed
c8f8bcca93 mobile: organize bulk exports 2024-01-27 21:23:29 +05:00
Abdullah Atta
9fa904e849 web: fix exporting of single note 2024-01-27 21:23:29 +05:00
Abdullah Atta
05cc947cb4 web: fix pdf exports 2024-01-27 21:23:29 +05:00
alihamuh
a20a683b17 web: code refactoring 2024-01-27 21:23:29 +05:00
alihamuh
5fc43c931e web: added directory paths for exports 2024-01-27 21:23:29 +05:00
Abdullah Atta
0879c3b4d6 core: fix formatting of single spaced paragarphs in markdown exports (#4186)
* core: update showdown library to support single spaced paragraphs

* core: add tests

* core: update showdown library
2024-01-27 18:59:35 +05:00
Abdullah Atta
2eded71de3 core: fix restored attachments not syncing 2024-01-27 16:06:04 +05:00
Abdullah Atta
9e30ee50b8 core: add logs 2024-01-27 16:06:04 +05:00
Abdullah Atta
08621a5873 core: try to preserve mime type on backup restore 2024-01-27 16:06:04 +05:00
Abdullah Atta
17f5bd28ba web: do not overwrite filename & mimetype on attachment reupload 2024-01-27 16:06:04 +05:00
Ammar Ahmed
4558c0b2eb mobile: fix downloading attachments from editor 2024-01-27 16:06:04 +05:00
Abdullah Atta
a442677e91 editor: fix loading of images that have wrong mime type 2024-01-27 16:06:04 +05:00
Abdullah Atta
95666bee36 editor: remove attachment progress once it reaches 100 2024-01-27 16:06:04 +05:00
Abdullah Atta
c4232ac95a web: do not send hash when updating attachment 2024-01-27 16:06:04 +05:00
Abdullah Atta
0283037289 mobile: changes related to download cancellation 2024-01-27 16:06:04 +05:00
Abdullah Atta
b6fd0df715 web: get rid of all attachment loading logic 2024-01-27 16:06:04 +05:00
Abdullah Atta
f53fb58024 editor: eagerly load image/webclip instead of waiting on client 2024-01-27 16:06:04 +05:00
Abdullah Atta
21d452308d core: simplify download cancellation 2024-01-27 16:06:04 +05:00
Ammar Ahmed
823608cca1 mobile: daily trash cleanup 2024-01-27 16:05:28 +05:00
alihamuh
f3a0b998e4 web: added daily trash interval option 2024-01-27 16:05:28 +05:00
theProton
a47a38b16b web: fix incorrect title on scrolling note (#4137)
Signed-off-by: Parthiv-M <parthivmenon.dev@gmail.com>
2024-01-27 11:44:58 +05:00
Ammar Ahmed
a184c53dd5 mobile: fix loading empty conent in editor on tablets 2024-01-26 23:02:06 +05:00
Abdullah Atta
a3464255ec core: always use attachment mime type from backup if possible 2024-01-26 19:21:55 +05:00
Abdullah Atta
088c6670a9 core: fix crash if note title only contains whitespace 2024-01-26 19:12:41 +05:00
Abdullah Atta
27840b9bd6 editor: fix link pasting (#4178) 2024-01-26 18:57:59 +05:00
Muhammad Ali
ae819504f0 editor: fix input rules not working at the end of note (#4130)
* web: added condition in getDeletedNodes

* editor: some corrections

* editor: merge if conditions

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-01-26 16:43:19 +05:00
Muhammad Ali
af792e394c web: add support for moving locked notes to trash (#4134)
* web: added support for single/multi trash notes

* web: show error when trying to preview a locked note in trash

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-01-26 16:37:54 +05:00
Muhammad Ali
76a4d5c2a5 editor: fix subtasks getting unchecked on drag/drop reorder (#4135)
* editor: add a condition to check nested item state

* editor: force coerce value to boolean

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2024-01-26 16:31:23 +05:00
Muhammad Ali
4e23acd608 web: fix realtime editor sync cannot be disabled (#4140) 2024-01-26 16:22:56 +05:00
Ammar Ahmed
3c65dcdd8b ci: bump android build tools version 2024-01-15 12:31:37 +05:00
Ammar Ahmed
efea6e3972 theme: add missing alpha colors 2024-01-15 12:31:11 +05:00
Ammar Ahmed
185cca7880 mobile: release v2.6.15 2024-01-12 20:10:58 +05:00
Abdullah Atta
fbded93405 global: update lockfiles 2024-01-05 22:10:05 +05:00
Abdullah Atta
674881b873 web: add zip64 support for backups, export & imports (#4079)
* web: use @zip.js/zip.js for reading zip files

* web: always use zip streams for writing zips

* core: fix types

* theme: update lockfile
2024-01-05 22:05:04 +05:00
Abdullah Atta
248fb6dd2d web: bump version to 2.6.14 2024-01-05 21:55:18 +05:00
Abdullah Atta
3211e7a149 global: update lockfiles 2024-01-05 21:54:06 +05:00
Abdullah Atta
08496c699e editor: fix font size changes back after exiting list (fixes #3602) 2024-01-05 21:48:00 +05:00
Abdullah Atta
060fa25814 core: support underline when exporting as md (fix #3990) 2024-01-05 20:53:14 +05:00
Abdullah Atta
a6b1effc77 core: remove relations auto cleanup 2024-01-05 16:24:02 +05:00
Abdullah Atta
5cf9c4808e web: show resubscribe button if subscription is canceled 2024-01-05 15:55:36 +05:00
Muhammad Ali
d5a44067fd web: fix editor header title input not becoming readonly (#4064) 2024-01-05 11:08:16 +05:00
Muhammad Ali
0de463526f editor: add support for +/- font-size using shortcut keys (#4029) 2024-01-05 10:40:29 +05:00
Muhammad Ali
5fd79875ef editor: fix math/formula block getting cut off on mobile (#4023) 2024-01-05 10:39:20 +05:00
Muhammad Ali
bdb19ad325 web: add license to settings (#4020) 2024-01-05 10:38:13 +05:00
Ammar Ahmed
7a12efc823 mobile: release v2.6.14 2023-12-22 11:58:34 +05:00
Abdullah Atta
2f3bddf860 web: bump version to 2.6.13 2023-12-22 11:51:11 +05:00
Abdullah Atta
6127eb97af editor: do not mark task list as completed if there are no items 2023-12-22 11:49:10 +05:00
Abdullah Atta
86386e14f3 web: update test snapshots 2023-12-22 11:41:42 +05:00
Abdullah Atta
9cc17c29f6 web: use Cmd-a on macOS to select all items in list 2023-12-22 11:38:40 +05:00
Abdullah Atta
a31e863c7b global: update lockfiles 2023-12-22 11:16:26 +05:00
Muhammad Ali
15a32ac83a editor: fix pasting a word inside a list adds newlines around it (#3843)
* editor: added openstart, openEnd from old slice

* editor: removed extra import

* editor: update test snapshots

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-12-22 11:11:03 +05:00
Ammar Ahmed
fa5b84ec0d mobile: minor fixes 2023-12-22 11:10:19 +05:00
Muhammad Ali
7905584eac editor: disable invalid tools when inside a code block (#3997)
* editor: removed fontSize property from code-text style

* editor: TO BE CONTINUED

* editor: some comments for testing

* editor: comment removed

* editor: diabled font size and family in code-block

* editor: disable invalid tools when inside code block

* editor: remove unused console.log

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-12-22 10:26:25 +05:00
Abdullah Atta
afc9b96a11 web: make importer error resistant 2023-12-21 09:08:04 +05:00
Abdullah Atta
4e6466fc18 editor: handle md links with parentheses (#3996) 2023-12-19 14:54:22 +05:00
Abdullah Atta
233baf78ce web: handle file picker dialog cancelation 2023-12-19 14:39:45 +05:00
Abdullah Atta
42d0bee37f desktop: bump electron to 25.9.8 2023-12-19 13:07:34 +05:00
Abdullah Atta
ccadab6a10 web: add md + frontmatter to export all options 2023-12-19 12:44:23 +05:00
Muhammad Ali
bfe5a5cb96 editor: correctly style other types of lists in outline list (#3889) 2023-12-19 12:15:36 +05:00
Muhammad Ali
88fc24c4cf editor: fix code blocks pasting in a single line without formatting (#3910)
* editor: added code support for w3school

* editor: fixed newline issue in per tag code
2023-12-19 12:13:59 +05:00
Abdullah Atta
8be4fea7a3 ui: use symbols for shortcut keys on macOS 2023-12-19 11:12:20 +05:00
Muhammad Ali
a3d39d8b6d editor: add Ctrl/Cmd+\ keyboard shortcut for "Clear all formatting" (#3989)
* editor: added short-cut key in keyap

* editor: use Mod key instead of Ctrl

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-12-19 11:11:35 +05:00
Muhammad Ali
ff36f0259a editor: add support for converting md link syntax to link on paste (#3919) 2023-12-18 16:23:56 +05:00
Muhammad Ali
d13654088a editor: add support for checking/unchecking all items in a task list (#3975) 2023-12-18 16:21:44 +05:00
Muhammad Ali
8a6b97c757 editor: add special readonly mode for toolbar (#3913)
* editor: added a toolbar display on mobile in readonly mode

* editor: hide editable tools from readonly toolbar

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-12-18 16:04:02 +05:00
Muhammad Ali
d71e0c109b web: going back in search after editing a notebook opens notebook edit dialog again (#3979)
* web: changed afterAction()

* web: fix back navigation for dialogs that replace

* web: remove unused import

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-12-18 15:58:20 +05:00
Ammar Ahmed
7e345303fe mobile: fix app lock hides login page on enter background 2023-12-18 10:49:57 +05:00
Abdullah Atta
d881bdb77c web: fix attachment dialog not opening on error 2023-12-11 15:09:38 +05:00
Ammar Ahmed
4961102d8f mobile: fix subscriptions not verified on ios 2023-12-11 10:54:45 +05:00
Abdullah Atta
4f2ab4f1da docs: fix incorrect noun (fixes #3934)
Signed-off-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-12-07 09:56:55 +05:00
Ammar Ahmed
7540e34366 mobile: update provisioning profiles 2023-11-27 08:22:47 +05:00
Abdullah Atta
4d7a8b9ea0 web: bump version to 2.6.12 2023-11-25 22:20:39 +05:00
Abdullah Atta
812ecf93c8 web: fix tag tests 2023-11-25 22:18:05 +05:00
Ammar Ahmed
74d99d97bc mobile: release v2.6.12 2023-11-25 15:44:43 +05:00
Muhammad Ali
5bbc453268 web: fix new tags do not appear under "recent" tags (#3827) 2023-11-25 15:39:38 +05:00
Ammar Ahmed
d71b9edc7f mobile: add support for markdown + frontmatter 2023-11-25 15:32:31 +05:00
Abdullah Atta
134904b551 web: add md with frontmatter export 2023-11-25 15:32:31 +05:00
Abdullah Atta
3eeb292d70 core: add new md + frontmatter export format 2023-11-25 15:32:31 +05:00
Abdullah Atta
04125bbb98 web: minor ui fixes 2023-11-25 15:19:50 +05:00
Ammar Ahmed
cfc3a8341c mobile: update mmkv package 2023-11-25 15:19:27 +05:00
Abdullah Atta
cbb0a511a8 core: fix logs sorting 2023-11-25 15:19:27 +05:00
Abdullah Atta
dff1c7ed32 core: optimize logger 2023-11-25 15:19:27 +05:00
Abdullah Atta
7987ef81a6 web: use removeMulti to bulk remove old logs 2023-11-25 15:19:27 +05:00
Ammar Ahmed
8d8d4ee782 mobile: fix app freeze when deleting old logs 2023-11-25 15:19:27 +05:00
Ammar Ahmed
3a50c73cab core: add removeMulti to storage 2023-11-25 15:19:27 +05:00
Abdullah Atta
4ee0071eca core: stop localOnly items from always getting synced 2023-11-25 15:12:33 +05:00
Muhammad Ali
248192f404 editor: hide link popup in readonly mode on mobile (#3853) 2023-11-25 11:29:02 +05:00
Krishna Bharadwaj
e56b766c3a web: open testimonial source links in new tab (#3863)
Signed-off-by: Dharmavineta <krishnabharadwaj1995@gmail.com>
2023-11-25 11:27:24 +05:00
Abdullah Atta
170b2b24ab web: fix logged in status alignment 2023-11-25 11:26:19 +05:00
Ammar Ahmed
50137e0994 mobile: fix subscriptions not fulfilling on ios 2023-11-25 10:32:24 +05:00
Abdullah Atta
8b5babd5f8 desktop: use node.js fetch instead of net.fetch 2023-11-25 10:20:19 +05:00
Abdullah Atta
39e83200c8 web: fix attachment upload progress on desktop 2023-11-25 10:19:57 +05:00
Abdullah Atta
ead0e0107e core: allow deleting unuploaded attachments 2023-11-25 10:19:38 +05:00
Abdullah Atta
1c888bd4fd web: always open announcement link in browser on desktop app 2023-11-25 10:18:20 +05:00
Krishna Bharadwaj
ff92530339 web: hide email address from status bar (#3859) 2023-11-24 11:18:46 +05:00
Ammar Ahmed
ee38032403 mobile: release v2.6.11 2023-11-22 14:45:16 +05:00
Ammar Ahmed
2ce245659a mobile: fix pricing plans 2023-11-22 14:27:20 +05:00
Abdullah Atta
2c635eb4d7 web: bump version to 2.6.11 2023-11-22 10:44:39 +05:00
Abdullah Atta
d653223df0 editor: load images threadsafely 2023-11-22 10:36:48 +05:00
Muhammad Ali
290b0dfc78 web: close reminder dialog on setting snooze time (#3842) 2023-11-21 10:53:53 +05:00
Muhammad Ali
06bb9e654b web: fix weird janky animation when opening dialogs (#3826) 2023-11-21 08:20:26 +05:00
Muhammad Ali
576fe78e33 clipper: hide clip preview button when there's no clip data (#3772) 2023-11-21 08:18:28 +05:00
Abdullah Atta
0c16bfb5ff web: fix issue with service worker not getting registered 2023-11-20 12:34:07 +05:00
Abdullah Atta
a101dbdf31 web: use bloburl when previewing images 2023-11-20 11:12:02 +05:00
Abdullah Atta
682609044f editor: add support for previewing SVG images 2023-11-20 11:12:02 +05:00
Abdullah Atta
c0a7fb3a5c misc: remove unnecessary files 2023-11-20 10:01:08 +05:00
Abdullah Atta
a153aa791c web: bump version to 2.6.10 2023-11-20 09:49:31 +05:00
Abdullah Atta
76b3dd1f96 web: check if file exists before starting upload 2023-11-20 09:41:35 +05:00
Ammar Ahmed
8b9e2c8526 mobile: release v2.6.10 2023-11-20 08:44:42 +05:00
Ammar Ahmed
0dc2d14736 mobile: fix crash on pricing plans page on ios 2023-11-20 08:27:49 +05:00
Abdullah Atta
fc5dada701 editor: fix task items getting unchecked on sort 2023-11-20 08:25:27 +05:00
Abdullah Atta
f34060c4e0 editor: fix task list title getting reset 2023-11-20 08:25:27 +05:00
Abdullah Atta
006a239680 web: bump version to 2.6.9 2023-11-18 15:13:40 +05:00
Abdullah Atta
c6ed4bcab1 web: allow restoring backups when logged out 2023-11-18 15:13:05 +05:00
Abdullah Atta
b0fe967087 desktop: destroy system tray on toggle off setting 2023-11-18 14:53:14 +05:00
Abdullah Atta
3f32673b0a desktop: permanently disable menu bar 2023-11-18 14:45:17 +05:00
Ammar Ahmed
0525cecaee mobile: bump version code on android 2023-11-18 14:38:37 +05:00
Ammar Ahmed
7d84a9582b mobile: allow restoring backups when logged out 2023-11-18 14:38:37 +05:00
Abdullah Atta
289acd8ab6 web: add cross icon for removing tags 2023-11-18 14:35:57 +05:00
Abdullah Atta
bb6d9f604b web: wait 1s for math to render on pdf print 2023-11-18 14:23:57 +05:00
Abdullah Atta
c20a8232cd core: safe check for IS_DESKTOP_APP 2023-11-18 14:23:57 +05:00
Ammar Ahmed
c886212e89 core: add padding to math block in exports 2023-11-18 14:23:57 +05:00
Abdullah Atta
799ae111ae editor: disable resizing of floating images 2023-11-18 13:05:00 +05:00
Abdullah Atta
2a990979a0 editor: fix drag/drop of task items 2023-11-18 13:04:45 +05:00
Ammar Ahmed
2d1071117b mobile: release v2.6.9 2023-11-18 12:17:27 +05:00
Ammar Ahmed
aefce1ee67 mobile: add support /now /time in title 2023-11-18 12:17:27 +05:00
Ammar Ahmed
64d10f5050 mobile: reupload if file size is zero 2023-11-18 12:17:11 +05:00
Abdullah Atta
95b9edca62 web: check if attachment really exists on insertion 2023-11-18 12:17:11 +05:00
Abdullah Atta
06c8422cbe core: prevent duplicate attachments on backup restore 2023-11-18 12:17:11 +05:00
Abdullah Atta
177d8b5112 web: remove rogue console.log 2023-11-18 10:18:53 +05:00
Abdullah Atta
39afa98fce web: lazily load fs module 2023-11-18 10:17:23 +05:00
Abdullah Atta
5bc1ad4ba5 editor: fix url is undefined error in loading images 2023-11-18 10:16:59 +05:00
Abdullah Atta
0b1223976c web: fix builds 2023-11-18 10:03:14 +05:00
Muhammad Ali
3873758fe5 clipper(web): add fallback height & width for web clip(#3766) 2023-11-18 10:03:14 +05:00
Abdullah Atta
3c876cb1d8 global: update lockfiles 2023-11-18 10:03:13 +05:00
Abdullah Atta
0a1a16c197 editor: show placeholder when downloading image 2023-11-18 10:03:13 +05:00
Ammar Ahmed
3c56325fa6 mobile: fix image download 2023-11-18 10:03:13 +05:00
Abdullah Atta
e5db3353ae editor: improve image loading 2023-11-18 10:03:13 +05:00
Muhammad Ali
3416ba4eaa editor: removed a useState method 2023-11-18 10:03:13 +05:00
Muhammad Ali
cdb69bf553 editor: removed updateImage command from useEffect 2023-11-18 10:03:13 +05:00
Abdullah Atta
1aff6d0d55 editor: sync task list state at the very end 2023-11-18 10:03:13 +05:00
Ammar Ahmed
67b7bdcea3 mobile: update iap library to latest 2023-11-18 10:02:45 +05:00
Abdullah Atta
9b6a6de415 editor: update snapshots 2023-11-17 12:50:43 +05:00
Abdullah Atta
8325246a56 web: reduce network check retries to 3 2023-11-17 11:49:52 +05:00
Abdullah Atta
3d11fb5e3f web: specify if session has expired during login 2023-11-17 08:44:17 +05:00
Abdullah Atta
4627a58a52 web: show tags in readonly mode 2023-11-17 08:34:16 +05:00
Muhammad Ali
e557047af1 web: fix pressing enter to add a new tag from list of suggestion fails (#3548)
* web: added ref to control resturn key func

* web: click should open autosuggestions menu

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-17 08:30:13 +05:00
Muhammad Ali
8b4cea7339 editor: make table header row immovable (#3459) 2023-11-16 15:30:32 +05:00
Muhammad Ali
ba5e35ec5c editor: fix resize for floating images (#3643)
* editor: removed maxWidth to make image float resizeable

* editor: fix resize for floating images

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-16 15:26:25 +05:00
aryan katiyar
f53817e94d web: add link to source code in Settings (#3683) 2023-11-16 15:18:11 +05:00
Muhammad Ali
846efc5351 editor: use prosemirror plugin for managing task list state (#3252)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-16 15:12:19 +05:00
Abdullah Atta
0149d00d64 web: add support for storing attachments in opfs or cache storage 2023-11-16 15:01:48 +05:00
Abdullah Atta
275f9730f1 web: allow downloading unuploading attachments 2023-11-16 15:01:48 +05:00
Abdullah Atta
43bc954989 core: allow uploading attachments that are in 0 notes 2023-11-16 15:01:48 +05:00
Abdullah Atta
c88bc722e1 fs: get rid of localforage and use generic interface 2023-11-16 15:01:48 +05:00
Muhammad Ali
ec6fc6839e editor: support datetime shortcuts in task list title & note title (#3746)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-16 14:14:43 +05:00
Abdullah Atta
8383c93292 desktop: only set openAtLogin if not already set 2023-11-16 13:07:21 +05:00
Muhammad Ali
37d8d85935 clipper: delete import css rules after iteration (#3720)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-16 13:06:51 +05:00
Muhammad Ali
95db7c6329 desktop: fix minimize to system tray not working immediately after enabling (#3734)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-16 13:01:22 +05:00
Muhammad Ali
636783335f desktop: add setting to disable menu bar (#3728)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-11-16 09:51:01 +05:00
Abdullah Atta
f88431ded4 core: handle case where headline is undefined 2023-11-03 08:28:41 +05:00
Abdullah Atta
91b0de0580 web: bump version to 2.6.8 2023-11-02 13:23:00 +05:00
Abdullah Atta
a5704ad75c desktop: do not show auto update toggle for flatpak users 2023-11-02 13:20:51 +05:00
Abdullah Atta
ba8c09a7d9 desktop: update electron to 25.9.3 2023-11-02 13:07:15 +05:00
Abdullah Atta
dbe4015655 web: preserve note edited time when exporting 2023-11-02 12:59:47 +05:00
Abdullah Atta
76237ad7dd web: fix sync progress in account recovery 2023-11-02 10:35:06 +05:00
Abdullah Atta
dbfb1168fb core: fix restored notes not syncing (closes #3472) 2023-11-02 10:34:21 +05:00
Abdullah Atta
830d28fbe5 editor: fix pasting link replaces selected text
fixes #3520
2023-10-31 12:33:49 +05:00
Abdullah Atta
c6def2a8fe web: fix crash on opening editor properties
(fixes #3585)
2023-10-31 12:03:45 +05:00
Abdullah Atta
ebc95fcea7 web: make duplicate filenames in attachments unique on download (#3678)
* web: make duplicate filenames in attachments unique

* Update apps/web/src/utils/streams/attachment-stream.ts

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

* web: make attachment filename case insensitive

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
2023-10-31 11:35:01 +05:00
Abdullah Atta
f78cfe59e8 web: hide note title immediately on setting change 2023-10-31 11:12:11 +05:00
Johnny Avila
a2cbc93d34 web: add setting to allow hiding note title from window title (#3480)
Signed-off-by: Johnny Avila <avila.johnny11@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-10-31 11:08:14 +05:00
Muhammad Ali
4728f14e8c core: add year when grouping by month (#3630) 2023-10-31 10:57:52 +05:00
Abdullah Atta
e94b431d58 web: change pwa theme color according to current theme (#3621)
* web: change pwa theme color according to current theme

* web: use background color as pwa theme-color
2023-10-31 10:56:37 +05:00
Muhammad Ali
0ac5e2b995 web: hide unnecessary items from status bar in focus mode (#3577)
* web: used app-store to hide sync-status suring focus mode

* web: hide useless items from status bar in focus mode

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-10-31 10:54:57 +05:00
Muhammad Ali
365764e3e3 web: update vault setting immediately after vault creation (#3542)
* web: added onStateChange function to vault-settings.

* web: fix font style of checkbox in password dialog

---------

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

* core: add watch command
2023-10-24 15:58:26 +05:00
Ammar Ahmed
2b917c7eca mobile: fix pin input hide behind keyboard on session expiry sheet (#3624) 2023-10-24 15:57:20 +05:00
1008 changed files with 268656 additions and 118131 deletions

View File

@@ -15,6 +15,8 @@ apps/mobile/e2e/
# web
apps/web/public/an.js
apps/web/src/common/sqlite/wa-sqlite-async.js
apps/web/src/common/sqlite/wa-sqlite.js
# editor
packages/editor/styles/

View File

@@ -78,7 +78,13 @@ module.exports = {
semi: ["error", "always"],
"@typescript-eslint/no-empty-function": "off",
"react/prop-types": "off",
"header/header": ["error", "block", LICENSE, 1]
"header/header": ["error", "block", LICENSE, 1],
"@typescript-eslint/no-empty-interface": [
"error",
{
allowSingleExtends: true
}
]
},
settings: {
react: {

View File

@@ -5,9 +5,9 @@ runs:
using: "composite"
steps:
- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x
cache: "npm"
cache-dependency-path: |
package-lock.json

View File

@@ -6,6 +6,11 @@ jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
NDK_CCACHE: ccache
USE_CCACHE: 1
steps:
- name: Checkout
@@ -24,30 +29,126 @@ jobs:
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=mobile
- name: Make Gradlew Executable
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Install CCache
uses: hendrikmuhs/ccache-action@v1.2.11
with:
key: ${{ runner.os }}-ccache-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
max-size: 1500M
restore-keys: |
${{ runner.os }}-ccache-
- name: Setup CCache
run: |
ln -s $(which ccache) /usr/local/bin/gcc
ln -s $(which ccache) /usr/local/bin/g++
ln -s $(which ccache) /usr/local/bin/cc
ln -s $(which ccache) /usr/local/bin/c++
ln -s $(which ccache) /usr/local/bin/clang
ln -s $(which ccache) /usr/local/bin/clang++
ccache --set-config=base_dir=$PWD
ccache --set-config=inode_cache=true
ccache --set-config=compiler_check=content
ccache --set-config=depend_mode=true
ccache --set-config=file_clone=true
ccache --set-config=sloppiness=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
ccache -p
- name: Cache gradle
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Patch CPP Projects to use CCache
run: |
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-reanimated/android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-reanimated//android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
- name: CCache Stats Before Build
run: ccache -sv
- name: Build unsigned app bundle
run: yarn release:android:bundle
- name: CCache Stats after build
run: ccache -sv
- name: Sign app bundle for Playstore release
id: sign_app
uses: r0adkll/sign-android-release@v1
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Build apks for Github release
run: yarn release:android
- name: Sign apk files
id: sign_apk
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.PUBLIC_SIGNING_KEY }}
alias: ${{ secrets.PUBLIC_ALIAS }}
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Rename apk files
run: |
cd apps/mobile/native/android/app/build/outputs/apk/release/
mv app-arm64-v8a-release-unsigned-signed.apk notesnook-arm64-v8a.apk
mv app-armeabi-v7a-release-unsigned-signed.apk notesnook-armeabi-v7a.apk
mv app-x86-release-unsigned-signed.apk notesnook-x86.apk
mv app-x86_64-release-unsigned-signed.apk notesnook-x86_64.apk
- name: Get app version
id: package-version
uses: martinbeentjes/npm-get-version-action@master
with:
path: apps/mobile
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFile: ${{steps.sign_app.outputs.signedReleaseFile}}
track: internal
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: alpha
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Create release draft on Github
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ steps.package-version.outputs.current-version}}-beta-android
name: Notesnook Android v${{ steps.package-version.outputs.current-version}} Beta
repository: streetwriters/notesnook
token: ${{ secrets.GITHUB_TOKEN }}
files: |
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
- name: Upload signed aab to Github
uses: actions/upload-artifact@v2
with:
name: Notesnook.aab
path: ${{steps.sign_app.outputs.signedReleaseFile}}

View File

@@ -1,4 +1,4 @@
name: Publish @notesnook/mobile
name: Publish @notesnook/android
on: workflow_dispatch
@@ -6,6 +6,11 @@ jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 40
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
NDK_CCACHE: ccache
USE_CCACHE: 1
steps:
- name: Checkout
@@ -28,29 +33,65 @@ jobs:
- name: Make Gradlew Executable
run: cd apps/mobile/native/android && chmod +x ./gradlew
- name: Install CCache
uses: hendrikmuhs/ccache-action@v1.2.11
with:
key: ${{ runner.os }}-ccache-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
max-size: 1500M
restore-keys: |
${{ runner.os }}-ccache-
- name: Setup CCache
run: |
ln -s $(which ccache) /usr/local/bin/gcc
ln -s $(which ccache) /usr/local/bin/g++
ln -s $(which ccache) /usr/local/bin/cc
ln -s $(which ccache) /usr/local/bin/c++
ln -s $(which ccache) /usr/local/bin/clang
ln -s $(which ccache) /usr/local/bin/clang++
ccache --set-config=base_dir=$PWD
ccache --set-config=inode_cache=true
ccache --set-config=compiler_check=content
ccache --set-config=depend_mode=true
ccache --set-config=file_clone=true
ccache --set-config=sloppiness=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
ccache -p
- name: Cache gradle
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Patch CPP Projects to use CCache
run: |
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
sed -i -e 's/arguments/arguments "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache",/g' apps/mobile/node_modules/react-native-reanimated/android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-reanimated//android/build.gradle
sed -i -e 's/defaultConfig {/ndkVersion safeExtGet('ndkVersion', "25.2.9519653")\n defaultConfig {/g' apps/mobile/node_modules/react-native-mmkv-storage/android/build.gradle
- name: CCache Stats Before Build
run: ccache -sv
- name: Build unsigned app bundle
run: yarn release:android:bundle
- name: CCache Stats after build
run: ccache -sv
- name: Sign app bundle for Playstore release
id: sign_app
uses: r0adkll/sign-android-release@v1
uses: r0adkll/sign-android-release@master
with:
releaseDirectory: apps/mobile/native/android/app/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Build apks for Github release
run: yarn release:android
@@ -64,6 +105,8 @@ jobs:
alias: ${{ secrets.PUBLIC_ALIAS }}
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Rename apk files
run: |
@@ -79,6 +122,18 @@ jobs:
with:
path: apps/mobile
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
- name: Create release draft on Github
uses: softprops/action-gh-release@v1
with:
@@ -92,3 +147,9 @@ jobs:
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
- name: Upload signed aab to Github
uses: actions/upload-artifact@v2
with:
name: Notesnook.aab
path: ${{steps.sign_app.outputs.signedReleaseFile}}

View File

@@ -18,6 +18,21 @@ on:
required: true
default: true
description: "Publish on GitHub releases?"
build-windows:
type: boolean
required: true
default: true
description: "Build for Windows?"
build-linux:
type: boolean
required: true
default: true
description: "Build for Linux?"
build-mac:
type: boolean
required: true
default: true
description: "Build for macOS?"
jobs:
build:
@@ -43,7 +58,7 @@ jobs:
run: npx nx build:desktop @notesnook/web
- name: Archive build artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: build
path: apps/web/build/**/*
@@ -67,11 +82,13 @@ jobs:
name: Notesnook Desktop v${{ steps.app_metadata.outputs.app_version }}
tag_name: v${{ steps.app_metadata.outputs.app_version }}
files: ./notesnook_build_v${{ steps.app_metadata.outputs.app_version }}.zip
prerelease: ${{ endsWith(steps.app_metadata.outputs.app_version, '-beta') }}
build-macos:
name: Build for macOS
needs: build
runs-on: macos-12
if: inputs.build-mac
runs-on: macos-14
steps:
- name: Check out Git repository
@@ -106,7 +123,7 @@ jobs:
platform: "MAC_OS"
- name: Download build
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
@@ -137,7 +154,7 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
APPLE_API_KEY: ~/private_keys/AuthKey_${{ secrets.api_key_id }}.p8
APPLE_API_KEY_ID: ${{ secrets.api_key_id }}
APPLE_API_KEY_ISSUER: ${{ secrets.api_key_issuer_id }}
APPLE_API_ISSUER: ${{ secrets.api_key_issuer_id }}
run: |
npx nx run release --project @notesnook/desktop
if [ ${{ inputs.publish-github }} == true ]; then
@@ -163,6 +180,7 @@ jobs:
build-linux:
name: Build for Linux
needs: build
if: inputs.build-linux
runs-on: ubuntu-22.04
steps:
@@ -181,7 +199,7 @@ jobs:
if: inputs.publish-snap
- name: Download build
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
@@ -221,17 +239,27 @@ jobs:
build-windows:
name: Build for Windows
needs: build
if: inputs.build-windows
runs-on: windows-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Install AzureCodeSigning
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
shell: pwsh
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build
path: ./apps/web/build
@@ -247,6 +275,9 @@ jobs:
- name: Publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: |
if ($${{ inputs.publish-github }} -eq $true) {
yarn electron-builder --win --publish always

104
.github/workflows/ios.publish.yml vendored Normal file
View File

@@ -0,0 +1,104 @@
name: Publish @notesnook/ios
on: workflow_dispatch
jobs:
build:
runs-on: macos-13
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
- 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: Build packages
run: npx nx run @notesnook/mobile:build
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.11
with:
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/native/ios/Podfile.lock')) }}
max-size: 1500M
restore-keys: |
${{ runner.os }}-ccache-
- name: Setup ccache
run: |
ccache --set-config=base_dir=$PWD
ccache --set-config=inode_cache=true
ccache --set-config=compiler_check=content
ccache --set-config=depend_mode=true
ccache --set-config=file_clone=true
ccache --set-config=sloppiness=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros
ln -s $(which ccache) /usr/local/bin/gcc
ln -s $(which ccache) /usr/local/bin/g++
ln -s $(which ccache) /usr/local/bin/cc
ln -s $(which ccache) /usr/local/bin/c++
ln -s $(which ccache) /usr/local/bin/clang
ln -s $(which ccache) /usr/local/bin/clang++
ccache -p
- name: Cache Pods
uses: actions/cache@v2
id: pods-cache
with:
path: apps/mobile/native/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/native/ios/Podfile.lock') }}
- name: Install Pods
run: npm run prepare:ios
- name: CCache Stats Before Build
run: ccache -sv
- name: Build iOS App
uses: ammarahm-ed/ios-build-action@master
with:
bundle-identifier: org.streetwriters.notesnook
scheme: Notesnook
configuration: "Release"
export-options: apps/mobile/native/ios/ExportOptions.plist
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
update-targets: |
Notesnook
Make Note
NotesWidgetExtension
disable-targets: Notesnook-tvOS,Notesnook-tvOSTests,NotesnookTests
code-signing-identity: Apple Distribution
team-id: ${{ secrets.APPLE_TEAM_ID }}
p12-base64: ${{ secrets.APPLE_CERTIFICATE_P12 }}
certificate-password: ${{ secrets.APPLE_CERTIFICATE_P12_PASSWORD }}
app-store-connect-api-key-issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
app-store-connect-api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
app-store-connect-api-key-base64: ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE64 }}
output-path: Notesnook.ipa
mobileprovision-base64: |
${{ secrets.APPLE_MOBILE_PROVISION_APP }}
${{ secrets.APPLE_MOBILE_PROVISION_SHARE }}
${{ secrets.APPLE_MOBILE_PROVISION_WIDGET }}
- name: CCache Stats After Build
run: ccache -sv
- name: "Upload app to TestFlight"
uses: apple-actions/upload-testflight-build@v1
with:
app-path: Notesnook.ipa
issuer-id: ${{ secrets.API_KEY_ISSUER_ID }}
api-key-id: ${{ secrets.API_KEY_ID }}
api-private-key: ${{ secrets.API_KEY }}
- name: Upload Notesnook.ipa to Github
uses: actions/upload-artifact@v2
with:
name: Notesnook.ipa
path: Notesnook.ipa

View File

@@ -1,10 +1,11 @@
name: Publish @notesnook/web Beta
on:
on:
workflow_dispatch:
push:
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
@@ -22,6 +23,7 @@ on:
- synchronize
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"

View File

@@ -0,0 +1,32 @@
name: Publish @notesnook/web v3 Beta
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- 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
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Build
run: npm run build:web
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/

3
.gitignore vendored
View File

@@ -9,4 +9,5 @@ nx-cloud.env
.env.local
.nyc_output
site
node_modules.backup
node_modules.backup
.nx

View File

@@ -11,4 +11,5 @@ apps/mobile/native/
apps/web/public/an.js
# editor
packages/editor/styles/
packages/editor/styles/
/.nx/cache

View File

@@ -1,4 +1,5 @@
node_modules
build
output
dist
dist
_catalog

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.7",
"version": "3.0.7-beta",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -14,9 +14,11 @@
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.4.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.4",
"icojs": "^0.17.1",
"sodium-native": "^4.0.6",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
@@ -25,12 +27,11 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "25.8.1",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"electron": "^28.2.1",
"electron-builder": "^24.9.1",
"esbuild": "^0.20.0",
"tree-kill": "^1.2.2",
"undici": "^5.23.0"
"undici": "^6.6.1"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
@@ -40,7 +41,7 @@
"staging": "node scripts/build.mjs --run",
"release": "node scripts/build.mjs",
"build": "tsc",
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
"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"
},
@@ -54,8 +55,8 @@
"productName": "Notesnook",
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
"artifactName": "notesnook_${os}_${arch}.${ext}",
"npmRebuild": false,
"asar": false,
"generateUpdatesFilesForAllChannels": true,
"files": [
"!*.chunk.js.map",
"!*.chunk.js.LICENSE.txt",
@@ -64,9 +65,17 @@
"!build/banner.jpg",
"!build/*.ico",
"!build/*.png",
"!node_modules${/*}"
"!node_modules${/*}",
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
"node_modules/better-sqlite3-multiple-ciphers/lib",
"node_modules/better-sqlite3-multiple-ciphers/package.json",
"node_modules/file-uri-to-path",
"node_modules/bindings",
"node_modules/node-gyp-build",
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
"node_modules/sodium-native/index.js",
"node_modules/sodium-native/package.json"
],
"afterSign": "electron-builder-notarize",
"afterPack": "./scripts/removeLocales.js",
"mac": {
"bundleVersion": "240",
@@ -94,7 +103,8 @@
"entitlements": "assets/entitlements.mac.plist",
"entitlementsInherit": "assets/entitlements.mac.plist",
"gatekeeperAssess": false,
"icon": "assets/icons/app.icns"
"icon": "assets/icons/app.icns",
"notarize": true
},
"dmg": {
"contents": [
@@ -135,6 +145,10 @@
]
}
],
"signingHashAlgorithms": [
"sha256"
],
"sign": "./scripts/sign.js",
"icon": "assets/icons/app.ico"
},
"portable": {
@@ -205,7 +219,8 @@
{
"provider": "github",
"repo": "notesnook",
"owner": "streetwriters"
"owner": "streetwriters",
"channel": "beta"
}
]
}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import path from "path";
import fs from "fs/promises";
import fs, { readFile, writeFile } from "fs/promises";
import { existsSync } from "fs";
import yargs from "yargs-parser";
import os from "os";
@@ -28,19 +28,7 @@ import { fileURLToPath } from "url";
const args = yargs(process.argv);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const sodiumNativePrebuildPath = (arch) =>
path.join(
`node_modules`,
`@notesnook`,
`crypto`,
`node_modules`,
`@notesnook`,
`sodium`,
`node_modules`,
`sodium-native`,
`prebuilds`,
`${os.platform()}-${arch}`
);
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
await fs.rm("./build/", { force: true, recursive: true });
@@ -49,6 +37,15 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
await exec(`cd ${webAppPath} && npm run build:desktop`);
}
// temporary until there's support for prebuilt binaries for windows ARM & linux ARM
await patchBetterSQLite3();
if (os.platform() === "win32")
await exec(
"npx prebuildify --arch=arm64 --strip",
path.join(__dirname, "..", "node_modules", "sodium-native")
);
await fs.cp(path.join(webAppPath, "build"), "build", {
recursive: true,
force: true
@@ -62,42 +59,6 @@ if (args.variant === "mas") {
await exec(`npx tsc`);
if (existsSync(sodiumNativePrebuildPath("x64"))) {
console.log("copying sodium-native-x64");
await fs.cp(
sodiumNativePrebuildPath("x64"),
path.join("build", "prebuilds", `${process.platform}-x64`),
{
recursive: true,
force: true
}
);
}
if (existsSync(sodiumNativePrebuildPath("ia32"))) {
console.log("copying sodium-native-ia32");
await fs.cp(
sodiumNativePrebuildPath("ia32"),
path.join("build", "prebuilds", `${process.platform}-ia32`),
{
recursive: true,
force: true
}
);
}
if (existsSync(sodiumNativePrebuildPath("arm64"))) {
console.log("copying sodium-native-arm64");
await fs.cp(
sodiumNativePrebuildPath("arm64"),
path.join("build", "prebuilds", `${process.platform}-arm64`),
{
recursive: true,
force: true
}
);
}
if (args.run) {
await exec(`npx electron-builder --dir --x64`);
if (process.platform === "win32") {
@@ -109,9 +70,28 @@ if (args.run) {
}
}
async function exec(cmd) {
async function exec(cmd, cwd) {
return childProcess.execSync(cmd, {
env: process.env,
stdio: "inherit"
env: { ...process.env, NOTESNOOK_STAGING: true },
stdio: "inherit",
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 = "9.4.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));
}

View File

@@ -27,21 +27,15 @@ import crypto from "crypto";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const sodiumNativePrebuildPath = path.join(
`node_modules`,
`@notesnook`,
`crypto`,
`node_modules`,
`@notesnook`,
`sodium`,
`node_modules`,
`sodium-native`,
`prebuilds`
);
const RUNNING_PROCESSES = [];
const RESTARTABLE_PROCESSES = [];
let lastBundleHash = null;
const ENV = {
...process.env,
NO_COLOR: "true",
FORCE_COLOR: "false",
COLOR: "0"
};
await onChange(true);
console.log("Watching...");
@@ -58,6 +52,8 @@ process.on("SIGINT", async (s) => {
async function onChange(first) {
if (first) {
await fs.rm("./build/", { force: true, recursive: true });
await exec("npx electron-rebuild");
}
await exec(`npm run bundle`);
@@ -68,13 +64,6 @@ async function onChange(first) {
return;
}
if (first) {
await fs.cp(sodiumNativePrebuildPath, "build/prebuilds", {
recursive: true,
force: true
});
}
if (first) {
await spawnAndWaitUntil(
["npx", "nx", "start:desktop", "@notesnook/web"],
@@ -102,7 +91,7 @@ function spawnAndWaitUntil(cmd, cwd, predicate) {
const s = spawn(cmd[0], cmd.slice(1), {
cwd,
env: { ...process.env, NO_COLOR: "true" },
env: ENV,
shell: false
});
@@ -116,14 +105,15 @@ function spawnAndWaitUntil(cmd, cwd, predicate) {
});
}
async function exec(cmd) {
async function exec(cmd, cwd) {
try {
console.log(">", cmd);
return execSync(cmd, {
env: process.env,
env: ENV,
stdio: "inherit",
shell: false
shell: false,
cwd: cwd || process.cwd()
});
} catch {
//ignore
@@ -136,7 +126,7 @@ function execAsync(cmd, args, restartable, onExit) {
const proc = spawn(cmd, args, {
stdio: "inherit",
env: process.env,
env: ENV,
shell: false
});
@@ -179,9 +169,10 @@ async function cleanup() {
}
async function isBundleSame() {
const bundle = await fs.readFile(
path.join(__dirname, "..", "build", "electron.js")
);
const bundle = Buffer.concat([
await fs.readFile(path.join(__dirname, "..", "build", "electron.js")),
await fs.readFile(path.join(__dirname, "..", "build", "preload.js"))
]);
const hashSum = crypto.createHash("sha256");
hashSum.update(bundle);

View File

@@ -0,0 +1,59 @@
/*
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/>.
*/
const { writeFileSync, rmSync } = require("fs");
const { execSync } = require("child_process");
const { relative, join } = require("path");
module.exports = async function (configuration) {
if (process.env.NOTESNOOK_STAGING) return;
const Endpoint = "https://weu.codesigning.azure.net";
const CodeSigningAccountName = "Notesnook";
const CertificateProfileName = "Notesnook";
const FileDigest = configuration.hash.toUpperCase();
const TimestampRfc3161 = "http://timestamp.acs.microsoft.com";
const TimestampDigest = configuration.hash.toUpperCase();
const Description = "The Notesnook app";
const DescriptionUrl = "https://notesnook.com/";
const FilesCatalog = createCatalog(configuration.path);
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
console.debug("Signing", configuration.path, "using command", command);
psexec(command);
console.debug("Signed", configuration.path);
rmSync(FilesCatalog);
};
function createCatalog(path) {
const catalogPath = join(__dirname, "_catalog");
writeFileSync(catalogPath, relative(__dirname, path));
return catalogPath;
}
function psexec(cmd) {
return execSync(cmd, {
env: process.env,
stdio: "inherit",
shell: "pwsh"
});
}

View File

@@ -23,6 +23,7 @@ import { osIntegrationRouter } from "./os-integration";
import { spellCheckerRouter } from "./spell-checker";
import { updaterRouter } from "./updater";
import { bridgeRouter } from "./bridge";
import { safeStorageRouter } from "./safe-storage";
const t = initTRPC.create();
@@ -31,7 +32,8 @@ export const router = t.router({
integration: osIntegrationRouter,
spellChecker: spellCheckerRouter,
updater: updaterRouter,
bridge: bridgeRouter
bridge: bridgeRouter,
safeStorage: safeStorageRouter
});
export const api = router.createCaller({});

View File

@@ -29,6 +29,9 @@ import { dirname } from "path";
import { resolvePath } from "../utils/resolve-path";
import { observable } from "@trpc/server/observable";
import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
const t = initTRPC.create();
@@ -44,12 +47,22 @@ const NotificationOptions = z.object({
});
export const osIntegrationRouter = t.router({
isFlatpak: t.procedure.query(() => isFlatpak()),
zoomFactor: t.procedure.query(() => config.zoomFactor),
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
globalThis.window?.webContents.setZoomFactor(factor);
config.zoomFactor = factor;
}),
proxyRules: t.procedure.query(() => config.proxyRules),
setProxyRules: t.procedure
.input(z.string().optional())
.mutation(({ input: proxyRules }) => {
globalThis.window?.webContents.session.setProxy({ proxyRules });
config.proxyRules = proxyRules || "";
}),
privacyMode: t.procedure.query(() => config.privacyMode),
setPrivacyMode: t.procedure
.input(z.object({ enabled: z.boolean() }))
@@ -79,6 +92,7 @@ export const osIntegrationRouter = t.router({
AutoLaunch.disable();
}
config.desktopSettings = settings;
setupDesktopIntegration(settings);
}),
selectDirectory: t.procedure
@@ -121,10 +135,13 @@ export const osIntegrationRouter = t.router({
.input(z.object({ filePath: z.string() }))
.query(({ input }) => {
const { filePath } = input;
if (!filePath) return;
return resolvePath(filePath);
}),
deleteFile: t.procedure.input(z.string()).query(async ({ input }) => {
await rm(input);
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {

View File

@@ -0,0 +1,44 @@
/*
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 { safeStorage } from "electron";
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const safeStorageRouter = t.router({
isEncryptionAvailable: t.procedure.query(() => {
return (
!process.env.PORTABLE_EXECUTABLE_DIR &&
safeStorage.isEncryptionAvailable()
);
}),
/**
* Takes a string and returns an encrypted base64 string
*/
encryptString: t.procedure.input(z.string()).query(({ input }) => {
return safeStorage.encryptString(input).toString("base64");
}),
/**
* Takes an encrypted base64 string and returns a string
*/
decryptString: t.procedure.input(z.string()).query(({ input }) => {
return safeStorage.decryptString(Buffer.from(input, "base64"));
})
});

View File

@@ -118,5 +118,13 @@ export const spellCheckerRouter = t.router({
.mutation(({ input: { enabled } }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
config.isSpellCheckerEnabled = enabled;
})
}),
words: t.procedure.query(() =>
globalThis.window?.webContents.session.listWordsInSpellCheckerDictionary()
),
deleteWord: t.procedure.input(z.string()).mutation(({ input: word }) => {
globalThis.window?.webContents.session.removeWordFromSpellCheckerDictionary(
word
);
})
});

View File

@@ -20,3 +20,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
export * from "./constants";
export type { AppRouter } from "./api";
export { type UpdateInfo } from "builder-util-runtime";
export { type DesktopIntegration } from "./utils/config";

View File

@@ -24,7 +24,6 @@ import { configureAutoUpdater } from "./utils/autoupdater";
import { getBackgroundColor, getTheme, setTheme } from "./utils/theme";
import { setupMenu } from "./utils/menu";
import { WindowState } from "./utils/window-state";
import { AutoLaunch } from "./utils/autolaunch";
import { setupJumplist } from "./utils/jumplist";
import { setupTray } from "./utils/tray";
import { CLIOptions, parseArguments } from "./cli";
@@ -35,6 +34,7 @@ import { config } from "./utils/config";
import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -77,7 +77,7 @@ async function createWindow() {
darkTheme: getTheme() === "dark",
backgroundColor: getBackgroundColor(),
opacity: 0,
autoHideMenuBar: true,
autoHideMenuBar: false,
icon: AssetManager.appIcon({
size: 512,
format: process.platform === "win32" ? "ico" : "png"
@@ -93,6 +93,7 @@ async function createWindow() {
createIPCHandler({ router, windows: [mainWindow] });
globalThis.window = mainWindow;
mainWindow.setMenuBarVisibility(false);
mainWindowState.manage(mainWindow);
if (cliOptions.hidden && !config.desktopSettings.minimizeToSystemTray)
@@ -106,11 +107,12 @@ async function createWindow() {
}
await AssetManager.loadIcons();
setupDesktopIntegration();
setupDesktopIntegration(config.desktopSettings);
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
);
mainWindow.webContents.session.setProxy({ proxyRules: config.proxyRules });
mainWindow.once("closed", () => {
globalThis.window = null;
@@ -176,49 +178,3 @@ function createURL(options: CLIOptions, path = "/") {
return url;
}
function setupDesktopIntegration() {
const desktopIntegration = config.desktopSettings;
if (
desktopIntegration.closeToSystemTray ||
desktopIntegration.minimizeToSystemTray
) {
setupTray();
}
// when close to system tray is enabled, it becomes nigh impossible
// to "quit" the app. This is necessary in order to fix that.
if (desktopIntegration.closeToSystemTray) {
app.on("before-quit", () => app.exit(0));
}
globalThis.window?.on("close", (e) => {
if (config.desktopSettings.closeToSystemTray) {
e.preventDefault();
if (process.platform == "darwin") {
// on macOS window cannot be minimized/hidden if it is already fullscreen
// so we just close it.
if (globalThis.window?.isFullScreen()) app.exit(0);
else app.hide();
} else {
globalThis.window?.minimize();
globalThis.window?.hide();
}
}
});
globalThis.window?.on("minimize", () => {
if (config.desktopSettings.minimizeToSystemTray) {
if (process.platform == "darwin") {
app.hide();
} else {
globalThis.window?.hide();
}
}
});
if (desktopIntegration.autoStart) {
AutoLaunch.enable(!!desktopIntegration.startMinimized);
}
}

View File

@@ -23,11 +23,13 @@ import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
import type { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
import { platform } from "os";
import sqlite3, { Database } from "better-sqlite3-multiple-ciphers";
declare global {
var os: () => "mas" | ReturnType<typeof platform>;
var electronTRPC: RendererGlobalElectronTRPC;
var NativeNNCrypto: (new () => NNCrypto) | undefined;
var createSQLite3Database: (filename: string) => Database;
}
process.once("loaded", async () => {
@@ -40,11 +42,6 @@ process.once("loaded", async () => {
globalThis.electronTRPC = electronTRPC;
});
globalThis.NativeNNCrypto =
process.platform === "win32" &&
process.arch !== "x64" &&
process.arch !== "ia32"
? undefined
: require("@notesnook/crypto").NNCrypto;
globalThis.NativeNNCrypto = require("@notesnook/crypto").NNCrypto;
globalThis.createSQLite3Database = (filename) => sqlite3(filename);
globalThis.os = () => (MAC_APP_STORE ? "mas" : platform());

View File

@@ -25,7 +25,11 @@ Type=Application
Version=${app.getVersion()}
Name=${app.getName()}
Comment=${app.getName()} startup script
Exec=${process.execPath}${hidden ? " --hidden" : ""}
Exec=${
process.env.APPIMAGE
? `${process.env.APPIMAGE}${hidden ? " --hidden" : ""}`
: `${process.execPath}${hidden ? " --hidden" : ""}`
}
StartupNotify=false
Terminal=false`;
@@ -35,6 +39,8 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
"autostart"
);
const STARTUP_ARGS = ["--hidden"];
export class AutoLaunch {
static enable(hidden: boolean) {
if (process.platform === "linux") {
@@ -47,10 +53,14 @@ export class AutoLaunch {
LINUX_DESKTOP_ENTRY(hidden)
);
} else {
const loginItemSettings = app.getLoginItemSettings({
args: STARTUP_ARGS
});
if (loginItemSettings.openAtLogin) return;
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: hidden,
args: ["--hidden"]
args: STARTUP_ARGS
});
}
}

View File

@@ -20,11 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { autoUpdater } from "electron-updater";
import { config } from "./config";
const CHANNEL = autoUpdater.currentVersion.raw.endsWith("-beta")
? "beta"
: "latest";
async function configureAutoUpdater() {
autoUpdater.setFeedURL({
provider: "generic",
url: `https://notesnook.com/releases/${process.platform}/`,
useMultipleRangeRequest: false
url: `https://notesnook.com/api/v1/releases/${process.platform}/${CHANNEL}`,
useMultipleRangeRequest: false,
channel: CHANNEL
});
autoUpdater.autoDownload = config.automaticUpdates;

View File

@@ -41,7 +41,8 @@ export const config = {
isSpellCheckerEnabled: true,
zoomFactor: 1,
theme: nativeTheme.themeSource,
automaticUpdates: true
automaticUpdates: true,
proxyRules: ""
};
type ConfigKey = keyof typeof config;

View File

@@ -0,0 +1,74 @@
/*
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 { app } from "electron";
import { DesktopIntegration, config } from "./config";
import { setupTray, destroyTray } from "./tray";
import { AutoLaunch } from "./autolaunch";
export function setupDesktopIntegration(
desktopIntegration: DesktopIntegration
) {
if (
desktopIntegration.closeToSystemTray ||
desktopIntegration.minimizeToSystemTray
) {
setupTray();
} else {
destroyTray();
}
// when close to system tray is enabled, it becomes nigh impossible
// to "quit" the app. This is necessary in order to fix that.
app.on("before-quit", () =>
desktopIntegration.closeToSystemTray ? app.exit(0) : null
);
globalThis.window?.on("close", (e) => {
if (config.desktopSettings.closeToSystemTray) {
e.preventDefault();
if (process.platform == "darwin") {
// on macOS window cannot be minimized/hidden if it is already fullscreen
// so we just close it.
if (globalThis.window?.isFullScreen()) app.exit(0);
else app.hide();
} else {
try {
globalThis.window?.minimize();
globalThis.window?.hide();
} catch (error) {
console.error(error);
}
}
}
});
globalThis.window?.on("minimize", () => {
if (config.desktopSettings.minimizeToSystemTray) {
if (process.platform == "darwin") {
app.hide();
} else {
globalThis.window?.hide();
}
}
});
if (desktopIntegration.autoStart) {
AutoLaunch.enable(!!desktopIntegration.startMinimized);
}
}

View File

@@ -131,6 +131,17 @@ function setupMenu() {
);
}
if (params.mediaType === "image")
menu.append(
new MenuItem({
id: "copy-image",
label: "Copy Image",
click() {
globalThis.window?.webContents.copyImageAt(params.x, params.y);
}
})
);
if (params.isEditable)
menu.append(
new MenuItem({

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { protocol, net } from "electron";
import { protocol } from "electron";
import { isDevelopment } from "./index";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
@@ -62,8 +62,25 @@ function registerProtocol() {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
return net.fetch(request, {
bypassCustomProtocolHandlers: true
if (request.headers.has("X-Content-Length")) {
request.headers.set(
"Content-Length",
request.headers.get("X-Content-Length") || "0"
);
request.headers.delete("X-Content-Length");
}
const headers = Object.fromEntries(request.headers.entries());
return await fetch(request.url, {
signal: request.signal,
mode: request.mode,
headers,
method: request.method,
body: request.body,
credentials: request.credentials,
referrer: (request as any).referrer,
duplex: "half",
redirect: "manual"
});
}
});

View File

@@ -16,15 +16,13 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "@azure/core-asynciterator-polyfill";
import SettingsService from "./services/settings";
import "react-native-gesture-handler";
import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
} from "@notesnook/theme";
import React, { useEffect } from "react";
import { View } from "react-native";
import "react-native-gesture-handler";
import { I18nManager, View } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
import AppLockedOverlay from "./components/app-lock-overlay";
@@ -32,20 +30,26 @@ import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import { themeTrpcClient } from "./screens/settings/theme-selector";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
import { TipManager } from "./services/tip-manager";
import { useThemeStore } from "./stores/use-theme-store";
import { useUserStore } from "./stores/use-user-store";
import { themeTrpcClient } from "./screens/settings/theme-selector";
SettingsService.checkOrientation();
I18nManager.allowRTL(false);
I18nManager.forceRTL(false);
I18nManager.swapLeftAndRightInRTL(false);
const App = () => {
const init = useAppEvents();
useEffect(() => {
let { appLockMode } = SettingsService.get();
if (appLockMode && appLockMode !== "none") {
const { appLockEnabled, appLockMode } = SettingsService.get();
if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
//@ts-ignore
globalThis["IS_MAIN_APP_RUNNING"] = true;
init();
setTimeout(async () => {
@@ -97,10 +101,10 @@ const App = () => {
let currTheme =
useThemeStore.getState().colorScheme === "dark"
? SettingsService.getProperty("darkTheme")
: SettingsService.getProperty("lightTheme");
: SettingsService.getProperty("lighTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element) => {
export const withTheme = (Element: () => JSX.Element) => {
return function AppWithThemeProvider() {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
state.colorScheme,

View File

@@ -17,16 +17,39 @@ 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 Sodium from "@ammarahmed/react-native-sodium";
import { DatabaseLogger } from ".";
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 IOS_KEYCHAIN_UPGRAGE_KEY = "keychain-ios:upgraded";
const KEYCHAIN_SERVER_DBKEY = "notesnook:db";
const NOTESNOOK_APPLOCK_KEY_SALT = "3dqclWbOYllfk9kk";
const NOTESNOOK_DB_KEY_SALT = "2rcgSprDmRvZ1AAa";
const NOTESNOOK_USER_KEY_SALT = "7qO4qeoM6PbsAJ0Q";
const DB_KEY_CIPHER = "databaseKeyCipher";
const USER_KEY_CIPHER = "userKeyCipher";
const APPLOCK_CIPHER = "applockCipher";
const KEYSTORE_CONFIG = Platform.select({
ios: {
@@ -37,61 +60,232 @@ const KEYSTORE_CONFIG = Platform.select({
android: {}
});
export async function deriveCryptoKey(name, data) {
try {
let credentials = await Sodium.deriveKey(data.password, data.salt);
await Keychain.setInternetCredentials(
"notesnook",
name,
credentials.key,
KEYSTORE_CONFIG
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"
);
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
return credentials.key;
}
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) {
console.error(e);
DatabaseLogger.error(e);
console.log(e);
}
}
async function upgradeIOSKeychain(username, password) {
if (Platform.OS !== "ios") return;
if (!MMKV.getBool(IOS_KEYCHAIN_UPGRAGE_KEY)) {
await Keychain.setInternetCredentials(
"notesnook",
username,
password,
KEYSTORE_CONFIG
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 decrypted = await decrypt(
{
password: appLockPassword
},
appLockCipher
);
console.log("IOS KEYCHAIN MIGRATION COMPLETED!");
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
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_USER_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) {
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_USER_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 {
if (await Keychain.hasInternetCredentials("notesnook")) {
let credentials = await Keychain.getInternetCredentials(
"notesnook",
KEYSTORE_CONFIG
);
// upgrades ios keychain to use accessGroups
// so we have access to keychain in share extension.
await upgradeIOSKeychain(credentials.username, credentials.password);
return credentials.password;
} else {
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()
},
keyCipher
);
DatabaseLogger.info("User key decrypted: ", !!key);
return key;
} catch (e) {
console.error(e);
console.log("getCryptoKey", e);
DatabaseLogger.error(e);
}
}
export async function removeCryptoKey(_name) {
try {
let result = await Keychain.resetInternetCredentials("notesnook");
return result;
MMKV.removeItem(USER_KEY_CIPHER);
await Keychain.resetInternetCredentials("notesnook");
return true;
} catch (e) {
console.error(e);
DatabaseLogger.error(e);
}
}
@@ -109,7 +303,7 @@ export async function generateCryptoKey(password, salt) {
let credentials = await Sodium.deriveKey(password, salt || null);
return credentials;
} catch (e) {
console.log("generateCryptoKey: ", e);
DatabaseLogger.error(e);
}
}
@@ -123,6 +317,9 @@ export async function decrypt(password, data) {
return undefined;
let _data = { ...data };
_data.output = "plain";
if (!password.salt) password.salt = data.salt;
return await Sodium.decrypt(password, _data);
}
@@ -135,6 +332,11 @@ export async function decryptMulti(password, data) {
d.output = "plain";
return d;
});
if (data.length && !password.salt) {
password.salt = data[0].salt;
}
return await Sodium.decryptMulti(password, data);
}

View File

@@ -16,15 +16,18 @@ 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/dist/logger";
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 { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
import filesystem from "../filesystem";
import "./logger";
import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";
import { getDatabaseKey } from "./encryption";
database.host(
__DEV__
@@ -34,11 +37,11 @@ database.host(
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.43.108:5264",
// AUTH_HOST: "http://192.168.43.108:8264",
// SSE_HOST: "http://192.168.43.108:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.108:9264",
// ISSUES_HOST: "http://192.168.43.108:2624"
// API_HOST: "http://192.168.43.5:5264",
// AUTH_HOST: "http://192.168.43.5:8264",
// SSE_HOST: "http://192.168.43.5:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.5:9264",
// ISSUES_HOST: "http://192.168.43.5:2624"
}
: {
API_HOST: "https://api.notesnook.com",
@@ -49,15 +52,37 @@ database.host(
}
);
database.setup(
Storage,
Platform.OS === "ios" ? EventSource : AndroidEventSource,
filesystem,
{
compress: Gzip.deflate,
decompress: Gzip.inflate
}
);
export async function setupDatabase(password) {
const key = await getDatabaseKey(password);
if (!key)
throw new Error("Database setup failed, could not get database key");
console.log("Opening database with key:", !!key);
database.setup({
storage: Storage,
eventsource: Platform.OS === "ios" ? EventSource : AndroidEventSource,
fs: filesystem,
compressor: {
compress: Gzip.deflate,
decompress: Gzip.inflate
},
batchSize: 100,
sqliteOptions: {
dialect: (name) => ({
createDriver: () => {
return new RNSqliteDriver({ async: true, dbName: name });
},
createAdapter: () => new SqliteAdapter(),
createIntrospector: (db) => new SqliteIntrospector(db),
createQueryCompiler: () => new SqliteQueryCompiler()
}),
tempStore: "memory",
journalMode: Platform.OS === "ios" ? "DELETE" : "WAL",
password: key
}
});
}
export const db = database;
export const DatabaseLogger = dbLogger;

View File

@@ -17,13 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { MMKVLoader } from "react-native-mmkv-storage";
import { initalize } from "@notesnook/core/dist/logger";
import { initialize } from "@notesnook/core/dist/logger";
import { KV } from "./storage";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
initalize(new KV(LoggerStorage));
initialize(new KV(LoggerStorage));
export {};
export { LoggerStorage };

View File

@@ -0,0 +1,123 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { DatabaseConnection, Driver, QueryResult } from "kysely";
import { CompiledQuery } from "kysely";
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
type Config = { dbName: string; async: boolean; location: string };
export class RNSqliteDriver implements Driver {
private connection?: DatabaseConnection;
private connectionMutex = new ConnectionMutex();
private db: QuickSQLiteConnection;
constructor(private readonly config: Config) {
this.db = open({
name: config.dbName
});
}
async init(): Promise<void> {
this.connection = new RNSqliteConnection(this.db);
}
async acquireConnection(): Promise<DatabaseConnection> {
// SQLite only has one single connection. We use a mutex here to wait
// until the single connection has been released.
await this.connectionMutex.lock();
return this.connection!;
}
async beginTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("BEGIN TRANSACTION"));
}
async commitTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("COMMIT"));
}
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("ROLLBACK"));
}
async releaseConnection(): Promise<void> {
this.connectionMutex.unlock();
}
async destroy(): Promise<void> {
this.db.close();
}
}
class ConnectionMutex {
private promise?: Promise<void>;
private resolve?: () => void;
async lock(): Promise<void> {
while (this.promise) {
await this.promise;
}
this.promise = new Promise((resolve) => {
this.resolve = resolve;
});
}
unlock(): void {
const resolve = this.resolve;
this.promise = undefined;
this.resolve = undefined;
resolve?.();
}
}
class RNSqliteConnection implements DatabaseConnection {
constructor(private readonly db: QuickSQLiteConnection) {}
streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {
throw new Error("wasqlite driver doesn't support streaming");
}
async executeQuery<R>(
compiledQuery: CompiledQuery<unknown>
): Promise<QueryResult<R>> {
const { parameters, sql, query } = compiledQuery;
const mode =
query.kind === "SelectQueryNode"
? "query"
: query.kind === "RawNode"
? "raw"
: "exec";
const result = await this.db.executeAsync(sql, parameters as any[]);
if (mode === "query" || !result.insertId)
return {
rows: result.rows?._array || []
};
return {
insertId: BigInt(result.insertId),
numAffectedRows: BigInt(result.rowsAffected),
rows: mode === "raw" ? result.rows?._array || [] : []
};
}
}

View File

@@ -90,6 +90,11 @@ export class KV {
return this.storage.removeItem(key);
}
async removeMulti(keys) {
if (!keys) return true;
return this.storage.removeItems(keys);
}
async clear() {
return this.storage.clearStore();
}
@@ -145,6 +150,7 @@ export default {
clear: () => DefaultStorage.clear(),
getAllKeys: () => DefaultStorage.getAllKeys(),
writeMulti: (items) => DefaultStorage.writeMulti(items),
removeMulti: (keys) => DefaultStorage.removeMulti(keys),
encrypt,
decrypt,
decryptMulti,

View File

@@ -16,20 +16,61 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Dimensions, Platform } from "react-native";
import { Dimensions, Image, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "react-native-blob-util";
console.log("hello world");
/**
* Scale down & compress images to screen width
* for loading in editor.
* @returns
*/
export async function compressToBase64(path, type) {
const { width, scale } = Dimensions.get("window");
const { 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,
99999,
type,
width <= 1920 ? 100 : 90,
0,
undefined,
true,
{
mode: "contain",
onlyScaleDown: true
}
);
const base64 = await RNFetchBlob.fs.readFile(
Platform.OS === "ios"
? response.uri?.replace("file://", "")
: response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs
.unlink(response.uri.replace("file://", ""))
.catch(console.log);
resolve(base64);
});
});
}
export async function compressToFile(path, type) {
const response = await ImageResizer.createResizedImage(
path,
width * scale,
9999,
1920,
99999,
type,
80,
0,
@@ -40,11 +81,6 @@ export async function compressToBase64(path, type) {
onlyScaleDown: true
}
);
const base64 = await RNFetchBlob.fs.readFile(
Platform.OS === "ios" ? response.uri?.replace("file://", "") : response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(console.log);
return base64;
return response.uri;
}

View File

@@ -29,12 +29,13 @@ import * as ScopedStorage from "react-native-scoped-storage";
import { subscribe, zip } from "react-native-zip-archive";
import RNFetchBlob from "react-native-blob-util";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { ToastEvent, presentSheet } from "../../services/event-manager";
import { ToastManager, presentSheet } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { DatabaseLogger, db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir } from "./io";
import { createCacheDir, exists } from "./io";
import { IOS_APPGROUPID } from "../../utils/constants";
export const FileDownloadStatus = {
Success: 1,
@@ -45,15 +46,15 @@ export const FileDownloadStatus = {
* Download all user's attachments
* @returns
*/
export function downloadAllAttachments() {
const attachments = db.attachments.all;
export async function downloadAllAttachments() {
const attachments = await db.attachments.all.ids();
return downloadAttachments(attachments);
}
/**
* Downloads provided attachments to a .zip file
* on user's device.
* @param attachments
* @param {string[]} attachments
* @param onProgress
* @returns
*/
@@ -83,13 +84,15 @@ export async function downloadAttachments(
Platform.OS === "ios"
? `${outputFolder}/notesnook-attachments-${Date.now()}.zip`
: `${cacheDir}/notesnook-attachments.zip`;
if (await RNFetchBlob.fs.exists(zipSourceFolder))
if (await RNFetchBlob.fs.exists(zipSourceFolder)) {
await RNFetchBlob.fs.unlink(zipSourceFolder);
}
await RNFetchBlob.fs.mkdir(zipSourceFolder);
for (let i = 0; i < attachments.length; i++) {
let attachment = attachments[i];
const hash = attachment.metadata.hash;
let attachment = await db.attachments.attachment(attachments[i]);
const hash = attachment.hash;
try {
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
@@ -113,19 +116,20 @@ export async function downloadAttachments(
}
if (!uri) throw new Error("Failed to download file");
// Move file to the source folder we will zip eventually and rename the file to it's actual name.
const filePath = `${zipSourceFolder}/${attachment.metadata.filename}`;
const filePath = `${zipSourceFolder}/${attachment.filename}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
result.set(hash, {
filename: attachment.metadata.filename,
status: FileDownloadStatus.Success
filename: attachment.filename,
status: FileDownloadStatus.Success,
attachment: attachment
});
} catch (e) {
result.set(hash, {
filename: attachment.metadata.filename,
filename: attachment.filename,
status: FileDownloadStatus.Fail,
reason: e
});
console.log("Error downloading attachment", e);
ToastManager.error(e, "Error downloading attachment");
}
}
if (canceled.current) {
@@ -162,7 +166,7 @@ export async function downloadAttachments(
} catch (e) {
releasePermissions(outputFolder);
sub?.remove();
console.log("Error zipping attachments", e);
ToastManager.error(e, "Error zipping attachments");
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
@@ -181,14 +185,16 @@ export default async function downloadAttachment(
silent: false,
cache: false,
throwError: false,
groupId: undefined
groupId: undefined,
base64: false,
text: false
}
) {
await createCacheDir();
let attachment = db.attachments.attachment(hash);
let attachment = await db.attachments.attachment(hash);
if (!attachment) {
console.log("attachment not found");
DatabaseLogger.log("Attachment not found");
return;
}
@@ -203,39 +209,47 @@ export default async function downloadAttachment(
}
try {
console.log(
"starting download attachment",
attachment.metadata.hash,
options.groupId
);
await db.fs.downloadFile(
options.groupId || attachment.metadata.hash,
attachment.metadata.hash
);
if (
!(await RNFetchBlob.fs.exists(`${cacheDir}/${attachment.metadata.hash}`))
)
await db
.fs()
.downloadFile(options.groupId || attachment.hash, attachment.hash);
if (!(await exists(attachment.hash))) {
DatabaseLogger.log("Attachment does not exist after download.");
return;
}
if (options.base64 || options.text) {
console.log(
"starting decrypt base64 file...",
options.base64,
options.text,
attachment.hash
);
return await db.attachments.read(
attachment.hash,
options.base64 ? "base64" : "text"
);
}
let filename = getFileNameWithExtension(
attachment.metadata.filename,
attachment.metadata.type
attachment.filename,
attachment.mimeType
);
let key = await db.attachments.decryptKey(attachment.key);
let info = {
iv: attachment.iv,
salt: attachment.salt,
length: attachment.length,
length: attachment.size,
alg: attachment.alg,
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
hash: attachment.hash,
hashType: attachment.hashType,
mime: attachment.mimeType,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize
chunkSize: attachment.chunkSize,
appGroupId: IOS_APPGROUPID
};
let fileUri = await Sodium.decryptFile(
key,
info,
@@ -243,7 +257,7 @@ export default async function downloadAttachment(
);
if (!options.silent) {
ToastEvent.show({
ToastManager.show({
heading: "Download successful",
message: filename + " downloaded",
type: "success"
@@ -252,11 +266,11 @@ export default async function downloadAttachment(
if (
attachment.dateUploaded &&
!isImage(attachment.metadata?.type) &&
!isDocument(attachment.metadata?.type)
!isImage(attachment.mimeType) &&
!isDocument(attachment.mimeType)
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios" && !options.cache) {
@@ -271,7 +285,7 @@ export default async function downloadAttachment(
: "File Manager/Notesnook/downloads"
}`,
icon: "download",
context: global ? null : attachment.metadata.hash,
context: global ? null : attachment.hash,
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
});
}
@@ -281,10 +295,13 @@ export default async function downloadAttachment(
console.log("download attachment error: ", e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
}
useAttachmentStore.getState().remove(attachment.metadata.hash);
useAttachmentStore.getState().remove(attachment.hash);
if (options.throwError) {
throw e;
}

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import hosts from "@notesnook/core/dist/utils/constants";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
import { ToastEvent } from "../../services/event-manager";
import { ToastManager } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir, fileCheck } from "./utils";
@@ -29,13 +29,14 @@ import { createCacheDir, exists } from "./io";
export async function downloadFile(filename, data, cancelToken) {
if (!data) return false;
console.log("Downloading", filename);
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
console.log("Exists already", filename);
return true;
}
@@ -74,13 +75,13 @@ export async function downloadFile(filename, data, cancelToken) {
return status >= 200 && status < 300;
} catch (e) {
if (e.message !== "canceled") {
ToastEvent.show({
ToastManager.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "global"
});
ToastEvent.show({
ToastManager.show({
heading: "Error downloading file",
message: e.message,
type: "error",
@@ -96,16 +97,22 @@ export async function downloadFile(filename, data, cancelToken) {
}
export async function getUploadedFileSize(hash) {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.user.tokenManager.getAccessToken();
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
return isNaN(contentLength) ? 0 : contentLength;
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
return 0;
}
}
export async function checkAttachment(hash) {
@@ -113,7 +120,7 @@ export async function checkAttachment(hash) {
const isInternetReachable =
internetState.isConnected && internetState.isInternetReachable;
if (!isInternetReachable) return { success: true };
const attachment = db.attachments.attachment(hash);
const attachment = await db.attachments.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {

View File

@@ -17,13 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
import { IOS_APPGROUPID } from "../../utils/constants";
import { db } from "../database";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
@@ -35,9 +34,7 @@ export async function readEncrypted(filename, key, cipherData) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
const attachment = await db.attachments.attachment(filename);
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
@@ -46,20 +43,9 @@ export async function readEncrypted(filename, key, cipherData) {
hash: filename,
appGroupId: IOS_APPGROUPID
},
cipherData.outputType === "base64"
? isPng || isJpeg
? "cache"
: "base64"
: "text"
cipherData.outputType === "base64" ? "base64" : "text"
);
console.log("file decrypted...");
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
const dCachePath = `${cacheDir}/${output}`;
output = await compressToBase64(
`file://${dCachePath}`,
isPng ? "PNG" : "JPEG"
);
}
console.log("file decrypted...", attachment?.mimeType);
return output;
} catch (e) {
@@ -190,10 +176,10 @@ export async function exists(filename) {
}
if (exists || existsInAppGroup) {
const attachment = db.attachments.attachment(filename);
const totalChunks = Math.ceil(attachment.length / attachment.chunkSize);
const attachment = await db.attachments.attachment(filename);
const totalChunks = Math.ceil(attachment.size / attachment.chunkSize);
const totalAbytes = totalChunks * ABYTES;
const expectedFileSize = attachment.length + totalAbytes;
const expectedFileSize = attachment.size + totalAbytes;
const stat = await RNFetchBlob.fs.stat(
existsInAppGroup ? appGroupPath : path

View File

@@ -87,12 +87,9 @@ export async function uploadFile(filename, data, cancelToken) {
DatabaseLogger.info(
`File upload status: ${filename}, ${status}, ${text}`
);
let attachment = db.attachments.attachment(filename);
let attachment = await db.attachments.attachment(filename);
if (!attachment) return result;
if (
!isImage(attachment.metadata.type) &&
!isDocument(attachment.metadata?.type)
) {
if (!isImage(attachment.mimeType) && !isDocument(attachment.mimeType)) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
} else {

View File

@@ -17,18 +17,20 @@ 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 { FlatList, View } from "react-native";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { useMessageStore } from "../../stores/use-message-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { allowedOnPlatform, renderItem } from "./functions";
import { getContainerBorder } from "../../utils/colors";
export const Announcement = ({ color }) => {
const { colors } = useThemeColors();
const announcements = useMessageStore((state) => state.announcements);
let announcement = announcements.length > 0 ? announcements[0] : null;
const selectionMode = useSelectionStore((state) => state.selectionMode);
return !announcement || selectionMode ? null : (
<View
style={{
@@ -45,7 +47,8 @@ export const Announcement = ({ color }) => {
borderRadius: 10,
overflow: "hidden",
backgroundColor: colors.secondary.background,
paddingBottom: 12
paddingBottom: 12,
...getContainerBorder(colors.secondary.background)
}}
>
<View

View File

@@ -103,7 +103,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
key={item.title}
title={item.title}
fontSize={SIZE.sm}
type="gray"
type="plain"
onPress={() => onPress(item)}
width={null}
height={30}
@@ -149,7 +149,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
key={item.title}
title={item.title}
fontSize={SIZE.xs}
type="gray"
type="plain"
onPress={() => onPress(item)}
width={null}
height={30}

View File

@@ -34,10 +34,69 @@ import BaseDialog from "../dialog/base-dialog";
import { allowedOnPlatform, renderItem } from "./functions";
import { useCallback } from "react";
/**
* Test announcement
* {
id: "some-announcement",
type: "dialog",
body: [
{
type: "title",
text: "This is a title",
platforms: ["all"]
},
{
type: "description",
text: "Most of you are too busy to keep up to date with what's happening in Notesnook. That is unfortunate because Notesnook has come a looooong way.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "description",
text: "To solve this, we are launching the Notesnook Digest — a newsletter to help you stay updated about Notesnook development. And to keep things interesting I'll also sprinkle this newsletter with other interesting stuff like privacy tips & news, interesting books, things I am looking forward to etc.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "description",
text: "So be sure to subscribe. There won't be a proper schedule to this (yet) maybe once or twice a month. I promise no spam — only more awesomeness.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "description",
text: "— May privacy reign.",
style: {
marginBottom: 1
},
platforms: ["all"]
},
{
type: "callToActions",
actions: [
{
type: "promo",
title: "15% Off",
platforms: ["android"],
data: "com.streetwriters.notesnook.sub.yr.15"
}
],
platforms: ["all"]
}
]
}
*/
export const AnnouncementDialog = () => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [info, setInfo] = useState(null);
const [info, setInfo] = useState();
const remove = useMessageStore((state) => state.remove);
useEffect(() => {
@@ -51,7 +110,9 @@ export const AnnouncementDialog = () => {
const open = (data) => {
setInfo(data);
setVisible(true);
setImmediate(() => {
setVisible(true);
});
};
const close = useCallback(() => {

View File

@@ -54,7 +54,7 @@ export const Title = ({ text, style = {}, inline }) => {
</Heading>
<Button
type="gray"
type="plain"
icon="close"
height={null}
onPress={() => {

View File

@@ -1,215 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef } from "react";
import { Platform, View } from "react-native";
import { enabled } from "react-native-privacy-snapshot";
import { db } from "../../common/database";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeColors } from "@notesnook/theme";
import { useUserStore } from "../../stores/use-user-store";
import { NotesnookModule } from "../../utils/notesnook-module";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
const AppLockedOverlay = () => {
const { colors } = useThemeColors();
const user = useUserStore((state) => state.user);
const appLocked = useUserStore((state) => state.appLocked);
const lockApp = useUserStore((state) => state.lockApp);
const deviceMode = useSettingStore((state) => state.deviceMode);
const passwordInputRef = useRef();
const password = useRef();
const appState = useAppState();
const biometricUnlockAwaitingUserInput = useRef(false);
const onUnlockAppRequested = useCallback(async () => {
if (!(await BiometricService.isBiometryAvailable())) return;
if (Platform.OS === "android") {
const activityName = await NotesnookModule.getActivityName();
if (activityName !== "MainActivity") return;
}
useSettingStore.getState().setRequestBiometrics(true);
let unlocked = await BiometricService.validateUser(
"Unlock to access your notes",
""
);
if (unlocked) {
lockApp(false);
enabled(false);
password.current = null;
}
setTimeout(() => {
biometricUnlockAwaitingUserInput.current = false;
useSettingStore.getState().setRequestBiometrics(false);
}, 1);
}, [lockApp]);
const onSubmit = async () => {
if (!password.current) return;
try {
let unlocked = await db.user.verifyPassword(password.current);
if (unlocked) {
lockApp(false);
enabled(false);
password.current = null;
}
} catch (e) {
console.error(e);
}
};
useEffect(() => {
if (
biometricUnlockAwaitingUserInput.current ||
useUserStore.getState().disableAppLockRequests
)
return;
if (appLocked && appState === "active") {
biometricUnlockAwaitingUserInput.current = true;
onUnlockAppRequested();
}
}, [appState, onUnlockAppRequested, appLocked]);
return (
appLocked && (
<View
style={{
backgroundColor: colors.primary.background,
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999,
justifyContent: "center"
}}
>
<View
style={{
flex: 1,
justifyContent: "center",
width:
deviceMode !== "mobile"
? "50%"
: Platform.OS == "ios"
? "95%"
: "100%",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15,
alignSelf: "center"
}}
>
<IconButton
name="fingerprint"
size={100}
customStyle={{
width: 100,
height: 100,
marginBottom: 20,
marginTop: user ? 0 : 50
}}
onPress={onUnlockAppRequested}
color={colors.primary.border}
/>
<Heading
color={colors.primary.heading}
style={{
alignSelf: "center",
textAlign: "center"
}}
>
Unlock your notes
</Heading>
<Paragraph
style={{
alignSelf: "center",
textAlign: "center",
maxWidth: "90%"
}}
>
{"Please verify it's you"}
</Paragraph>
<Seperator />
<View
style={{
width: "100%",
padding: 12,
backgroundColor: colors.primary.background
}}
>
{user ? (
<>
<Input
fwdRef={passwordInputRef}
secureTextEntry
placeholder="Enter account password"
onChangeText={(v) => (password.current = v)}
onSubmit={onSubmit}
/>
</>
) : null}
<View
style={{
marginTop: user ? 25 : 25
}}
>
{user ? (
<>
<Button
title="Continue"
type="accent"
onPress={onSubmit}
width={250}
height={45}
style={{
borderRadius: 150,
marginBottom: 10
}}
fontSize={SIZE.md}
/>
</>
) : null}
<Button
title="Unlock with Biometrics"
width={250}
onPress={onUnlockAppRequested}
icon={"fingerprint"}
type="transparent"
/>
</View>
</View>
</View>
</View>
)
);
};
export default AppLockedOverlay;

View File

@@ -0,0 +1,299 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef } from "react";
import { Platform, TextInput, View } from "react-native";
//@ts-ignore
import { useThemeColors } from "@notesnook/theme";
import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger } from "../../common/database";
import {
decrypt,
encrypt,
getCryptoKey,
getDatabaseKey,
setAppLockVerificationCipher,
validateAppLockPassword
} from "../../common/database/encryption";
import { MMKV } from "../../common/database/mmkv";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { useUserStore } from "../../stores/use-user-store";
import { NotesnookModule } from "../../utils/notesnook-module";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
const getUser = () => {
const user = MMKV.getString("user");
if (user) {
return JSON.parse(user);
}
};
const verifyUserPassword = async (password: string) => {
try {
await getDatabaseKey();
const key = await getCryptoKey();
const user = getUser();
const cipher = await encrypt(
{
key: key,
salt: user.salt
},
"notesnook"
);
const plainText = await decrypt({ password }, cipher);
return plainText === "notesnook";
} catch (e) {
DatabaseLogger.error(e as Error);
return false;
}
};
const AppLockedOverlay = () => {
const { colors } = useThemeColors();
const user = getUser();
const appLocked = useUserStore((state) => state.appLocked);
const lockApp = useUserStore((state) => state.lockApp);
const deviceMode = useSettingStore((state) => state.deviceMode);
const passwordInputRef = useRef<TextInput>(null);
const password = useRef<string>();
const appState = useAppState();
const biometricUnlockAwaitingUserInput = useRef(false);
const keyboardType = useSettingStore(
(state) => state.settings.applockKeyboardType
);
const appLockHasPasswordSecurity = useSettingStore(
(state) => state.settings.appLockHasPasswordSecurity
);
const biometricsAuthEnabled = useSettingStore(
(state) =>
state.settings.biometricsAuthEnabled === true ||
(state.settings.biometricsAuthEnabled === undefined &&
!state.settings.appLockHasPasswordSecurity)
);
const onUnlockAppRequested = useCallback(async () => {
if (
!biometricsAuthEnabled ||
!(await BiometricService.isBiometryAvailable())
)
return;
biometricUnlockAwaitingUserInput.current = true;
if (Platform.OS === "android") {
const activityName = await NotesnookModule.getActivityName();
if (activityName !== "MainActivity") return;
}
useSettingStore.getState().setRequestBiometrics(true);
const unlocked = await BiometricService.validateUser(
"Unlock to access your notes",
""
);
if (unlocked) {
lockApp(false);
enabled(false);
password.current = undefined;
}
biometricUnlockAwaitingUserInput.current = false;
setTimeout(() => {
if (biometricUnlockAwaitingUserInput.current) return;
useSettingStore.getState().setRequestBiometrics(false);
}, 500);
}, [biometricsAuthEnabled, lockApp]);
const onSubmit = async () => {
if (!password.current) return;
try {
const unlocked = appLockHasPasswordSecurity
? validateAppLockPassword(password.current)
: await verifyUserPassword(password.current);
if (unlocked) {
if (!appLockHasPasswordSecurity) {
await setAppLockVerificationCipher(password.current);
SettingsService.set({
appLockHasPasswordSecurity: true,
applockKeyboardType: "default"
});
DatabaseLogger.info("App lock migrated to password security");
}
lockApp(false);
enabled(false);
password.current = undefined;
}
} catch (e) {
console.error(e);
}
};
useEffect(() => {
if (
biometricUnlockAwaitingUserInput.current ||
useUserStore.getState().disableAppLockRequests
)
return;
if (
appLocked &&
appState === "active" &&
!useSettingStore.getState().requestBiometrics
) {
biometricUnlockAwaitingUserInput.current = true;
onUnlockAppRequested();
}
}, [appState, onUnlockAppRequested, appLocked]);
return appLocked ? (
<View
style={{
backgroundColor: colors.primary.background,
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999,
justifyContent: "center"
}}
>
<View
style={{
flex: 1,
justifyContent: "center",
width:
deviceMode !== "mobile"
? "50%"
: Platform.OS == "ios"
? "95%"
: "100%",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15,
alignSelf: "center"
}}
>
<IconButton
name="fingerprint"
size={100}
style={{
width: 100,
height: 100,
marginBottom: 20,
marginTop: user ? 0 : 50
}}
onPress={onUnlockAppRequested}
color={colors.primary.border}
/>
<Heading
color={colors.primary.heading}
style={{
alignSelf: "center",
textAlign: "center"
}}
>
Unlock your notes
</Heading>
<Paragraph
style={{
alignSelf: "center",
textAlign: "center",
maxWidth: "90%"
}}
>
{"Please verify it's you"}
</Paragraph>
<Seperator />
<View
style={{
width: "100%",
padding: 12,
backgroundColor: colors.primary.background
}}
>
{user || appLockHasPasswordSecurity ? (
<>
<Input
fwdRef={passwordInputRef}
secureTextEntry
keyboardType={
appLockHasPasswordSecurity ? keyboardType : "default"
}
placeholder={`Enter ${
appLockHasPasswordSecurity
? `app lock ${
keyboardType === "numeric" ? "pin" : "password"
}`
: "account password"
}`}
onChangeText={(v) => (password.current = v)}
onSubmit={() => {
onSubmit();
}}
/>
</>
) : null}
<View
style={{
marginTop: user ? 25 : 25
}}
>
{user || appLockHasPasswordSecurity ? (
<>
<Button
title="Continue"
type="accent"
onPress={onSubmit}
width={250}
height={45}
style={{
borderRadius: 150,
marginBottom: 10
}}
fontSize={SIZE.md}
/>
</>
) : null}
{biometricsAuthEnabled ? (
<Button
title="Unlock with Biometrics"
width={250}
onPress={onUnlockAppRequested}
icon={"fingerprint"}
type="transparent"
/>
) : null}
</View>
</View>
</View>
</View>
) : null;
};
export default AppLockedOverlay;

View File

@@ -17,9 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { formatBytes } from "@notesnook/common";
import { Attachment, Note, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useState } from "react";
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { ScrollView } from "react-native-gesture-handler";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
@@ -27,14 +31,18 @@ import downloadAttachment from "../../common/filesystem/download-attachment";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import picker from "../../screens/editor/tiptap/picker";
import {
ToastManager,
eSendEvent,
presentSheet,
ToastEvent
presentSheet
} from "../../services/event-manager";
import PremiumService from "../../services/premium";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeColors } from "@notesnook/theme";
import { eCloseAttachmentDialog, eCloseSheet } from "../../utils/events";
import {
eCloseAttachmentDialog,
eCloseSheet,
eDBItemUpdate,
eOnLoadNote
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Dialog } from "../dialog";
@@ -43,31 +51,42 @@ import { openNote } from "../list-items/note/wrapper";
import { DateMeta } from "../properties/date-meta";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
import { PressableButton } from "../ui/pressable";
import { Pressable } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { formatBytes } from "@notesnook/common";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../screens/editor/tiptap/utils";
const Actions = ({ attachment, setAttachments, fwdRef }) => {
const Actions = ({
attachment,
close,
setAttachments,
fwdRef
}: {
attachment: Attachment;
setAttachments: (attachments?: VirtualizedGrouping<Attachment>) => void;
close?: () => void;
fwdRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
const contextId = attachment.metadata.hash;
const [filename, setFilename] = useState(attachment.metadata.filename);
const contextId = attachment.hash;
const [filename, setFilename] = useState(attachment.filename);
const [currentProgress] = useAttachmentProgress(attachment);
const [failed, setFailed] = useState(attachment.failed);
const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState({
name: null
});
const [failed, setFailed] = useState<string | undefined>(attachment.failed);
const [notes, setNotes] = useState<Note[]>([]);
const [loading, setLoading] = useState<{
name?: string;
}>({});
const actions = [
{
name: "Download",
onPress: async () => {
if (currentProgress) {
await db.fs.cancel(attachment.metadata.hash, "download");
useAttachmentStore.getState().remove(attachment.metadata.hash);
await db.fs().cancel(attachment.hash);
useAttachmentStore.getState().remove(attachment.hash);
}
downloadAttachment(attachment.metadata.hash, false);
downloadAttachment(attachment.hash, false);
eSendEvent(eCloseSheet, contextId);
},
icon: "download"
@@ -76,7 +95,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
name: "Reupload",
onPress: async () => {
if (!PremiumService.get()) {
ToastEvent.show({
ToastManager.show({
heading: "Upgrade to pro",
type: "error",
context: "local"
@@ -85,9 +104,9 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}
await picker.pick({
reupload: true,
hash: attachment.metadata.hash,
hash: attachment.hash,
context: contextId,
type: attachment.metadata.type
type: attachment.mimeType.startsWith("image") ? "image" : "file"
});
},
icon: "upload"
@@ -98,28 +117,29 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
setLoading({
name: "Run file check"
});
let res = await filesystem.checkAttachment(attachment.metadata.hash);
const res = await filesystem.checkAttachment(attachment.hash);
if (res.failed) {
db.attachments.markAsFailed(attachment.id, res.failed);
setFailed(res.failed);
ToastEvent.show({
ToastManager.show({
heading: "File check failed with error: " + res.failed,
type: "error",
context: "local"
});
} else {
setFailed(null);
db.attachments.markAsFailed(attachment.id, null);
ToastEvent.show({
setFailed(undefined);
db.attachments.markAsFailed(attachment.id);
eSendEvent(eDBItemUpdate, attachment.id);
ToastManager.show({
heading: "File check passed",
type: "success",
context: "local"
});
}
setAttachments([...db.attachments.all]);
setAttachments();
setLoading({
name: null
name: undefined
});
},
icon: "file-check"
@@ -128,19 +148,20 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
name: "Rename",
onPress: () => {
presentDialog({
context: contextId,
context: contextId as any,
input: true,
title: "Rename file",
paragraph: "Enter a new name for the file",
defaultValue: attachment.metadata.filename,
defaultValue: attachment.filename,
positivePress: async (value) => {
if (value && value.length > 0) {
await db.attachments.add({
hash: attachment.metadata.hash,
hash: attachment.hash,
filename: value
});
setFilename(value);
setAttachments([...db.attachments.all]);
setAttachments();
eSendEvent(eDBItemUpdate, attachment.id);
}
},
positiveText: "Rename"
@@ -151,34 +172,40 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
{
name: "Delete",
onPress: async () => {
await db.attachments.remove(attachment.metadata.hash, false);
setAttachments([...db.attachments.all]);
eSendEvent(eCloseSheet, contextId);
const relations = await db.relations.to(attachment, "note").get();
await db.attachments.remove(attachment.hash, false);
setAttachments();
eSendEvent(eDBItemUpdate, attachment.id);
relations
.map((relation) => relation.fromId)
.forEach(async (id) => {
const tab = useTabStore.getState().getTabForNote(id);
if (tab !== undefined) {
const isFocused = useTabStore.getState().currentTab === tab;
if (isFocused) {
eSendEvent(eOnLoadNote, {
item: await db.notes.note(id),
forced: true
});
} else {
editorController.current.commands.setLoading(true, tab);
}
}
});
close?.();
},
icon: "delete-outline"
}
];
const getNotes = useCallback(() => {
let allNotes = db.notes.all;
let attachmentNotes = attachment.noteIds?.map((id) => {
let index = allNotes?.findIndex((note) => id === note.id);
if (index !== -1) {
return allNotes[index];
} else {
return {
type: "notfound",
title: `Note with id ${id} does not exist.`,
id: id
};
}
});
return attachmentNotes;
}, [attachment.noteIds]);
useEffect(() => {
setNotes(getNotes());
}, [attachment, getNotes]);
db.relations
.to(attachment, "note")
.selector.items()
.then((items) => {
setNotes(items);
});
}, [attachment]);
return (
<ScrollView
@@ -221,7 +248,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
color={colors.secondary.paragraph}
>
{attachment.metadata.type}
{attachment.type}
</Paragraph>
<Paragraph
style={{
@@ -230,10 +257,10 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{formatBytes(attachment.length)}
{formatBytes(attachment.size)}
</Paragraph>
{attachment.noteIds ? (
{notes.length ? (
<Paragraph
style={{
marginRight: 10
@@ -241,14 +268,14 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{attachment.noteIds.length} note
{attachment.noteIds.length > 1 ? "s" : ""}
{notes.length} note
{notes.length > 1 ? "s" : ""}
</Paragraph>
) : null}
<Paragraph
onPress={() => {
Clipboard.setString(attachment.metadata.hash);
ToastEvent.show({
Clipboard.setString(attachment.hash);
ToastManager.show({
type: "success",
heading: "Attachment hash copied",
context: "local"
@@ -257,7 +284,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{attachment.metadata.hash}
{attachment.hash}
</Paragraph>
</View>
@@ -284,25 +311,15 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
</Heading>
{notes.map((item) => (
<PressableButton
<Pressable
onPress={async () => {
if (item.type === "notfound") {
ToastEvent.show({
heading: "Note not found",
message:
"A note with the given id was not found. Maybe you have deleted the note or moved it to trash already.",
type: "error",
context: "local"
});
return;
}
eSendEvent(eCloseSheet, contextId);
await sleep(150);
eSendEvent(eCloseAttachmentDialog);
await sleep(300);
openNote(item, item.type === "trash");
openNote(item, (item as any).type === "trash");
}}
customStyle={{
style={{
paddingVertical: 12,
alignItems: "flex-start",
@@ -311,7 +328,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
key={item.id}
>
<Paragraph size={SIZE.xs}>{item.title}</Paragraph>
</PressableButton>
</Pressable>
))}
</>
</View>
@@ -321,17 +338,16 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
<Button
key={item.name}
buttonType={{
text: item.on
? colors.primary.accent
: item.name === "Delete" || item.name === "PermDelete"
? colors.error.paragraph
: colors.primary.paragraph
text:
item.name === "Delete"
? colors.error.paragraph
: colors.primary.paragraph
}}
onPress={item.onPress}
title={item.name}
icon={item.icon}
loading={loading?.name === item.name}
type={item.on ? "shade" : "gray"}
type="plain"
fontSize={SIZE.sm}
style={{
borderRadius: 0,
@@ -359,11 +375,20 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
);
};
Actions.present = (attachment, set, context) => {
Actions.present = (
attachment: Attachment,
set: (attachments?: VirtualizedGrouping<Attachment>) => void,
context?: string
) => {
presentSheet({
context: context,
component: (ref) => (
<Actions fwdRef={ref} setAttachments={set} attachment={attachment} />
component: (ref, close) => (
<Actions
fwdRef={ref}
setAttachments={set}
close={close}
attachment={attachment}
/>
)
});
};

View File

@@ -1,171 +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 { formatBytes } from "@notesnook/common";
import React from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
export const AttachmentItem = ({
attachment,
encryption,
setAttachments,
pressable = true,
hideWhenNotDownloading,
context
}) => {
const { colors } = useThemeColors();
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
encryption
);
const onPress = () => {
if (!pressable) return;
Actions.present(attachment, setAttachments, context);
};
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
style={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12,
paddingVertical: 6,
borderRadius: 5,
backgroundColor: colors.secondary.background
}}
type="grayBg"
>
<View
style={{
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
}}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginLeft: -5
}}
>
<Icon name="file" size={SIZE.xxxl} color={colors.primary.icon} />
<Paragraph
adjustsFontSizeToFit
size={6}
color={colors.static.white}
style={{
position: "absolute"
}}
>
{getFileExtension(attachment.metadata.filename).toUpperCase()}
</Paragraph>
</View>
<View
style={{
flexShrink: 1,
marginLeft: 10
}}
>
<Paragraph
size={SIZE.sm - 1}
style={{
flexWrap: "wrap",
marginBottom: 2.5
}}
numberOfLines={1}
lineBreakMode="middle"
color={colors.primary.paragraph}
>
{attachment.metadata.filename}
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
</View>
</View>
{currentProgress ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs.cancel(attachment.metadata.hash, currentProgress.type);
setCurrentProgress(null);
}}
style={{
justifyContent: "center",
marginLeft: 5,
marginTop: 5,
marginRight: -5
}}
>
<ProgressCircleComponent
size={SIZE.xxl}
progress={currentProgress?.value ? currentProgress?.value / 100 : 0}
showsText
textStyle={{
fontSize: 10
}}
color={colors.primary.accent}
formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
</TouchableOpacity>
) : (
<>
{attachment.failed ? (
<IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.error.paragraph}
/>
) : null}
</>
)}
</TouchableOpacity>
);
};

View File

@@ -0,0 +1,191 @@
/*
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 { formatBytes } from "@notesnook/common";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useDBItem } from "../../hooks/use-db-item";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
function getFileExtension(filename: string) {
const ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
export const AttachmentItem = ({
id,
attachments,
encryption,
setAttachments,
pressable = true,
hideWhenNotDownloading,
context
}: {
id: string | number;
attachments?: VirtualizedGrouping<Attachment>;
encryption?: boolean;
setAttachments: (attachments: any) => void;
pressable?: boolean;
hideWhenNotDownloading?: boolean;
context?: string;
}) => {
const [attachment] = useDBItem(id, "attachment", attachments);
const { colors } = useThemeColors();
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
encryption
);
const onPress = () => {
if (!pressable || !attachment) return;
Actions.present(attachment, setAttachments, context);
};
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
style={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12,
paddingVertical: 6,
borderRadius: 5,
backgroundColor: colors.secondary.background,
minHeight: 45
}}
>
{!attachment ? null : (
<>
<View
style={{
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
}}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginLeft: -5
}}
>
<Icon name="file" size={SIZE.xxxl} color={colors.primary.icon} />
<Paragraph
adjustsFontSizeToFit
size={6}
color={colors.static.white}
style={{
position: "absolute"
}}
>
{getFileExtension(attachment.filename).toUpperCase()}
</Paragraph>
</View>
<View
style={{
flexShrink: 1,
marginLeft: 10
}}
>
<Paragraph
size={SIZE.sm - 1}
style={{
flexWrap: "wrap",
marginBottom: 2.5
}}
numberOfLines={1}
lineBreakMode="middle"
color={colors.primary.paragraph}
>
{attachment.filename}
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{formatBytes(attachment.size)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
</View>
</View>
{currentProgress ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs().cancel(attachment.hash);
setCurrentProgress(undefined);
}}
style={{
justifyContent: "center",
marginLeft: 5,
marginTop: 5,
marginRight: -5
}}
>
<ProgressCircleComponent
size={SIZE.xxl}
progress={
currentProgress?.value ? currentProgress?.value / 100 : 0
}
showsText
textStyle={{
fontSize: 10
}}
color={colors.primary.accent}
formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
</TouchableOpacity>
) : (
<>
{attachment.failed ? (
<IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.error.paragraph}
/>
) : null}
</>
)}
</>
)}
</TouchableOpacity>
);
};

View File

@@ -21,7 +21,10 @@ import React, { useRef, useState } from "react";
import { Platform, View } from "react-native";
import { db } from "../../common/database";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
import { presentSheet } from "../../services/event-manager";
import {
PresentSheetOptions,
presentSheet
} from "../../services/event-manager";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
@@ -29,8 +32,19 @@ import { ProgressBarComponent } from "../ui/svg/lazy";
import { useThemeColors } from "@notesnook/theme";
import { FlatList } from "react-native-actions-sheet";
import { AttachmentItem } from "./attachment-item";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
const DownloadAttachments = ({ close, attachments, isNote, update }) => {
const DownloadAttachments = ({
close,
attachments,
isNote,
update
}: {
attachments: VirtualizedGrouping<Attachment>;
close?: ((ctx?: string | undefined) => void) | undefined;
isNote?: boolean;
update?: (props: PresentSheetOptions) => void;
}) => {
const { colors } = useThemeColors();
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState({
@@ -39,63 +53,58 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
});
const [result, setResult] = useState(new Map());
const canceled = useRef(false);
const groupId = useRef();
const groupId = useRef<string>();
const onDownload = async () => {
update({
update?.({
disableClosing: true
});
} as PresentSheetOptions);
setDownloading(true);
canceled.current = false;
groupId.current = Date.now().toString();
const result = await downloadAttachments(
attachments,
(progress, statusText) => setProgress({ value: progress, statusText }),
await attachments.ids(),
(progress: number, statusText: string) =>
setProgress({ value: progress, statusText }),
canceled,
groupId.current
);
if (canceled.current) return;
setResult(result || new Map());
setDownloading(false);
update({
update?.({
disableClosing: false
});
} as PresentSheetOptions);
};
const cancel = async () => {
update({
update?.({
disableClosing: false
});
} as PresentSheetOptions);
canceled.current = true;
if (!groupId.current) return;
console.log(groupId.current, "canceling groupId downloads");
await db.fs.cancel(groupId.current);
await db.fs().cancel(groupId.current);
setDownloading(false);
groupId.current = null;
};
const successResults = () => {
const results = [];
for (let [key, value] of result.entries()) {
if (value.status === 1) results.push(db.attachments.attachment(key));
}
return results;
setResult(new Map());
groupId.current = undefined;
};
const failedResults = () => {
const results = [];
for (let [key, value] of result.entries()) {
if (value.status === 0) results.push(db.attachments.attachment(key));
for (const value of result.values()) {
if (value.status === 0) results.push(value.attachment);
}
return results;
};
function getResultText() {
const downloadedAttachmentsCount =
attachments.length - failedResults().length;
attachments?.placeholders?.length - failedResults().length;
if (downloadedAttachmentsCount === 0)
return "Failed to download all attachments";
return `Successfully downloaded ${downloadedAttachmentsCount}/${
attachments.length
attachments?.placeholders.length
} attachments as a zip file at ${
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
}`;
@@ -157,7 +166,11 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
width={null}
animated={true}
useNativeDriver
progress={progress.value ? progress.value / attachments.length : 0}
progress={
progress.value
? progress.value / attachments.placeholders?.length
: 0
}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
@@ -174,7 +187,7 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
borderRadius: 5,
marginVertical: 12
}}
data={downloading ? attachments : []}
data={downloading ? attachments.placeholders : undefined}
ListEmptyComponent={
<View
style={{
@@ -189,14 +202,15 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
</Paragraph>
</View>
}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
keyExtractor={(index) => "attachment_download" + index}
renderItem={({ index }) => {
return (
<AttachmentItem
attachment={item}
id={index}
setAttachments={() => {}}
pressable={false}
hideWhenNotDownloading={true}
attachments={attachments}
/>
);
}}
@@ -209,7 +223,9 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
borderRadius: 100,
marginTop: 20
}}
onPress={close}
onPress={() => {
close?.();
}}
type="accent"
title="Done"
/>
@@ -227,8 +243,10 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
borderRadius: 100,
marginRight: 5
}}
onPress={close}
type="grayBg"
onPress={() => {
close?.();
}}
type="secondary"
title="No"
/>
<Button
@@ -258,7 +276,11 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
);
};
DownloadAttachments.present = (context, attachments, isNote) => {
DownloadAttachments.present = (
context: string,
attachments: VirtualizedGrouping<Attachment>,
isNote?: boolean
) => {
presentSheet({
context: context,
component: (ref, close, update) => (

View File

@@ -17,16 +17,25 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import {
Attachment,
Note,
SortOptions,
VirtualizedGrouping
} from "@notesnook/core";
import { FilteredSelector } from "@notesnook/core/dist/database/sql-collection";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
import { presentSheet } from "../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { useSettingStore } from "../../stores/use-setting-store";
import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
@@ -34,79 +43,92 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import DownloadAttachments from "./download-attachments";
import { Button } from "../ui/button";
import {
isAudio,
isDocument,
isImage,
isVideo
} from "@notesnook/core/dist/utils/filename";
import { useSettingStore } from "../../stores/use-setting-store";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
export const AttachmentDialog = ({ note }) => {
const DEFAULT_SORTING: SortOptions = {
sortBy: "dateEdited",
sortDirection: "desc"
};
export const AttachmentDialog = ({ note }: { note?: Note }) => {
const { colors } = useThemeColors();
const { height } = useSettingStore((state) => state.dimensions);
const [attachments, setAttachments] = useState(
note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])]
);
const attachmentSearchValue = useRef();
const searchTimer = useRef();
const [attachments, setAttachments] =
useState<VirtualizedGrouping<Attachment>>();
const attachmentSearchValue = useRef<string>();
const searchTimer = useRef<NodeJS.Timeout>();
const [loading, setLoading] = useState(false);
const [currentFilter, setCurrentFilter] = useState("all");
const onChangeText = (text) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
const refresh = React.useCallback(() => {
if (note) {
db.attachments
.ofNote(note.id, "all")
.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.then((attachments) => {
setAttachments(attachments);
});
} else {
db.attachments.all
.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.then((attachments) => setAttachments(attachments));
}
}, [note]);
useEffect(() => {
refresh();
}, [note, refresh]);
const onChangeText = async (text: string) => {
attachmentSearchValue.current = text;
if (
!attachmentSearchValue.current ||
attachmentSearchValue.current === ""
) {
setAttachments([...attachments]);
setAttachments(await filterAttachments(currentFilter));
refresh();
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
let results = db.lookup.attachments(
attachments,
attachmentSearchValue.current
);
searchTimer.current = setTimeout(async () => {
const results = await db.lookup
.attachments(attachmentSearchValue.current as string)
.sorted();
if (results.length === 0) return;
setAttachments(results);
}, 300);
};
const renderItem = ({ item }) => (
const renderItem = ({ index }: { item: boolean; index: number }) => (
<AttachmentItem
setAttachments={setAttachments}
attachment={item}
setAttachments={async () => {
setAttachments(await filterAttachments(currentFilter));
}}
attachments={attachments}
id={index}
context="attachments-list"
/>
);
const onCheck = async () => {
if (!attachments) return;
setLoading(true);
const checkedAttachments = [];
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(attachment.metadata.hash);
for (let i = 0; i < attachments.placeholders.length; i++) {
const attachment = (await attachments.item(i))?.item;
if (!attachment) continue;
const result = await filesystem.checkAttachment(attachment.hash);
if (result.failed) {
await db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
await db.attachments.markAsFailed(attachment.hash, result.failed);
} else {
await db.attachments.markAsFailed(attachment.id, null);
await db.attachments.markAsFailed(attachment.id, undefined);
}
checkedAttachments.push(
db.attachments.attachment(attachment.metadata.hash)
);
setAttachments([...checkedAttachments]);
}
refresh();
setLoading(false);
};
@@ -133,31 +155,40 @@ export const AttachmentDialog = ({ note }) => {
}
];
const filterAttachments = (type) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
isDocument;
const filterAttachments = async (type: string) => {
let items: FilteredSelector<Attachment> = db.attachments.all;
switch (type) {
case "all":
return attachments;
items = note
? db.attachments.ofNote(note.id, "all")
: db.attachments.all;
break;
case "images":
return attachments.filter((attachment) =>
isImage(attachment.metadata.type)
);
items = note
? db.attachments.ofNote(note.id, "images")
: db.attachments.images;
break;
case "video":
return attachments.filter((attachment) =>
isVideo(attachment.metadata.type)
);
items = items = note
? db.attachments.ofNote(note.id, "videos")
: db.attachments.videos;
break;
case "audio":
return attachments.filter((attachment) =>
isAudio(attachment.metadata.type)
);
items = note
? db.attachments.ofNote(note.id, "audio")
: db.attachments.all;
break;
case "documents":
return attachments.filter((attachment) =>
isDocument(attachment.metadata.type)
);
items = note
? db.attachments.ofNote(note.id, "documents")
: db.attachments.documents;
}
return await items.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
});
};
return (
@@ -196,7 +227,7 @@ export const AttachmentDialog = ({ note }) => {
) : (
<IconButton
name="check-all"
customStyle={{
style={{
height: 40,
width: 40,
marginRight: 10
@@ -209,12 +240,13 @@ export const AttachmentDialog = ({ note }) => {
<IconButton
name="download"
customStyle={{
style={{
height: 40,
width: 40
}}
color={colors.primary.paragraph}
onPress={() => {
if (!attachments) return;
DownloadAttachments.present(
"attachments-list",
attachments,
@@ -231,7 +263,7 @@ export const AttachmentDialog = ({ note }) => {
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
onChangeText(attachmentSearchValue.current as string);
}}
/>
@@ -251,12 +283,11 @@ export const AttachmentDialog = ({ note }) => {
>
{attachmentTypes.map((item) => (
<Button
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
key={item.title}
title={
item.title +
` (${filterAttachments(item.filterBy)?.length || 0})`
type={
currentFilter === item.filterBy ? "secondaryAccented" : "plain"
}
key={item.title}
title={item.title}
style={{
borderRadius: 0,
borderBottomWidth: 1,
@@ -266,9 +297,9 @@ export const AttachmentDialog = ({ note }) => {
? "transparent"
: colors.primary.accent
}}
onPress={() => {
onPress={async () => {
setCurrentFilter(item.filterBy);
setAttachments(filterAttachments(item.filterBy));
setAttachments(await filterAttachments(item.filterBy));
}}
/>
))}
@@ -299,7 +330,7 @@ export const AttachmentDialog = ({ note }) => {
/>
}
estimatedItemSize={50}
data={attachments}
data={attachments?.placeholders}
renderItem={renderItem}
/>
@@ -322,7 +353,7 @@ export const AttachmentDialog = ({ note }) => {
);
};
AttachmentDialog.present = (note) => {
AttachmentDialog.present = (note?: Note) => {
presentSheet({
component: () => <AttachmentDialog note={note} />
});

View File

@@ -17,14 +17,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { Platform, View } from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
@@ -34,7 +36,6 @@ import { IconButton } from "../ui/icon-button";
import { hideAuth, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
export const AuthMode = {
login: 0,
@@ -68,6 +69,9 @@ const AuthModal = () => {
}
const close = () => {
useUserStore.setState({
disableAppLockRequests: false
});
actionSheetRef.current?.hide();
setCurrentAuthMode(AuthMode.login);
setVisible(false);
@@ -77,6 +81,11 @@ const AuthModal = () => {
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
onShow={() => {
useUserStore.setState({
disableAppLockRequests: true
});
}}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
@@ -86,11 +95,13 @@ const AuthModal = () => {
transparent={false}
animated={false}
centered={false}
enableSheetKeyboardHandler
>
<KeyboardAwareScrollView
style={{
width: "100%"
}}
enableAutomaticScroll={false}
keyboardShouldPersistTaps="handled"
>
{currentAuthMode !== AuthMode.login ? (
@@ -147,7 +158,7 @@ const AuthModal = () => {
hideAuth();
}}
iconSize={16}
type="gray"
type="plain"
iconPosition="right"
icon="chevron-right"
height={25}

View File

@@ -23,15 +23,18 @@ import { db } from "../../common/database";
import {
eSendEvent,
presentSheet,
ToastEvent
ToastManager
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { eCloseSheet } from "../../utils/events";
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
import DialogHeader from "../dialog/dialog-header";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { Notice } from "../ui/notice";
import Seperator from "../ui/seperator";
import { Dialog } from "../dialog";
import BackupService from "../../services/backup";
import { sleep } from "../../utils/time";
export const ChangePassword = () => {
const passwordInputRef = useRef();
@@ -45,7 +48,7 @@ export const ChangePassword = () => {
const changePassword = async () => {
if (!user?.isEmailConfirmed) {
ToastEvent.show({
ToastManager.show({
heading: "Email not confirmed",
message: "Please confirm your email to change account password",
type: "error",
@@ -54,7 +57,7 @@ export const ChangePassword = () => {
return;
}
if (error || !oldPassword.current || !password.current) {
ToastEvent.show({
ToastManager.show({
heading: "All fields required",
message: "Fill all the fields and try again.",
type: "error",
@@ -64,18 +67,23 @@ export const ChangePassword = () => {
}
setLoading(true);
try {
const result = await BackupService.run(false, "change-password-dialog");
if (!result) throw new Error("Failed to create backup");
await db.user.clearSessions();
await db.user.changePassword(oldPassword.current, password.current);
ToastEvent.show({
ToastManager.show({
heading: "Account password updated",
type: "success",
context: "global"
});
setLoading(false);
eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eOpenRecoveryKeyDialog);
} catch (e) {
setLoading(false);
ToastEvent.show({
ToastManager.show({
heading: "Failed to change password",
message: e.message,
type: "error",
@@ -92,6 +100,7 @@ export const ChangePassword = () => {
padding: 12
}}
>
<Dialog context="change-password-dialog" />
<DialogHeader
title="Change password"
paragraph="Enter your old and new passwords"
@@ -129,7 +138,14 @@ export const ChangePassword = () => {
/>
<Notice
text="Changing password is a non-undoable process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection."
text={`Changing password is an irreversible process. You will be logged out from all your devices. Please make sure you do not close the app while your password is changing and have good internet connection.`}
type="alert"
/>
<View style={{ height: 10 }} />
<Notice
text={`Once your password is changed, please make sure to save the new account recovery key.`}
type="alert"
/>

View File

@@ -22,7 +22,7 @@ import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { ToastEvent } from "../../services/event-manager";
import { ToastManager } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useThemeColors } from "@notesnook/theme";
import DialogHeader from "../dialog/dialog-header";
@@ -43,7 +43,7 @@ export const ForgotPassword = () => {
const sendRecoveryEmail = async () => {
if (!email.current || error) {
ToastEvent.show({
ToastManager.show({
heading: "Account email is required.",
type: "error",
context: "local"
@@ -63,7 +63,7 @@ export const ForgotPassword = () => {
SettingsService.set({
lastRecoveryEmailTime: Date.now()
});
ToastEvent.show({
ToastManager.show({
heading: "Check your email to reset password",
message: `Recovery email has been sent to ${email.current.toLowerCase()}`,
type: "success",
@@ -74,7 +74,7 @@ export const ForgotPassword = () => {
setSent(true);
} catch (e) {
setLoading(false);
ToastEvent.show({
ToastManager.show({
heading: "Recovery email not sent",
message: e.message,
type: "error",
@@ -113,7 +113,7 @@ export const ForgotPassword = () => {
}}
>
<IconButton
customStyle={{
style={{
width: null,
height: null
}}

View File

@@ -59,22 +59,6 @@ const Auth = ({ navigation, route }) => {
/>
)}
{/* {initialAuthMode.current === AuthMode.welcomeSignup ? null : (
<IconButton
name="arrow-left"
onPress={() => {
hideAuth();
}}
color={colors.primary.paragraph}
customStyle={{
position: 'absolute',
zIndex: 999,
left: 12,
top: Platform.OS === 'ios' ? 12 + insets.top : insets.top
}}
/>
)} */}
<Toast context="local" />
</View>
);

View File

@@ -103,7 +103,7 @@ export const Login = ({ changeMode }) => {
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.secondary.background,
borderBottomWidth: 1,
borderBottomWidth: 0.8,
marginBottom: 12,
borderBottomColor: colors.primary.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
@@ -222,7 +222,7 @@ export const Login = ({ changeMode }) => {
textDecorationLine: "underline"
}}
fontSize={SIZE.xs}
type="gray"
type="plain"
/>
</>
)}

View File

@@ -19,12 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { Modal, View } from "react-native";
import { View } from "react-native";
import { db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import BiometricService from "../../services/biometrics";
import {
ToastEvent,
ToastManager,
eSendEvent,
eSubscribeEvent
} from "../../services/event-manager";
@@ -37,6 +37,7 @@ import { eLoginSessionExpired, eUserLoggedIn } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Dialog } from "../dialog";
import BaseDialog from "../dialog/base-dialog";
import { presentDialog } from "../dialog/functions";
import SheetProvider from "../sheet-provider";
import { Toast } from "../toast";
@@ -46,7 +47,6 @@ import Input from "../ui/input";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { LoginSteps, useLogin } from "./use-login";
import BaseDialog from "../dialog/base-dialog";
function getObfuscatedEmail(email) {
if (!email) return "";
@@ -69,7 +69,11 @@ export const SessionExpired = () => {
eSendEvent(eUserLoggedIn, true);
setVisible(false);
setFocused(false);
}
useUserStore.setState({
disableAppLockRequests: false
});
},
true
);
const logout = async () => {
@@ -84,7 +88,7 @@ export const SessionExpired = () => {
clearAllStores();
setVisible(false);
} catch (e) {
ToastEvent.show({
ToastManager.show({
heading: e.message,
type: "error",
context: "local"
@@ -101,9 +105,9 @@ export const SessionExpired = () => {
const open = React.useCallback(async () => {
try {
let res = await db.user.tokenManager.getToken();
let res = await db.tokenManager.getToken();
if (!res) throw new Error("no token found");
if (db.user.tokenManager._isTokenExpired(res))
if (db.tokenManager._isTokenExpired(res))
throw new Error("token expired");
Sync.run("global", false, true, async (complete) => {
if (!complete) {
@@ -126,6 +130,9 @@ export const SessionExpired = () => {
email.current = user.email;
setFocused(false);
setVisible(true);
useUserStore.setState({
disableAppLockRequests: true
});
}
}, [email]);
@@ -138,13 +145,16 @@ export const SessionExpired = () => {
animated={false}
centered={false}
onShow={async () => {
useUserStore.setState({
disableAppLockRequests: true
});
await sleep(300);
passwordInputRef.current?.focus();
setFocused(true);
}}
enableSheetKeyboardHandler={true}
visible={true}
>
<SheetProvider context="two_factor_verify" />
<View
style={{
width: focused ? "100%" : "99.9%",
@@ -165,7 +175,7 @@ export const SessionExpired = () => {
}}
>
<IconButton
customStyle={{
style={{
width: 60,
height: 60
}}
@@ -237,6 +247,8 @@ export const SessionExpired = () => {
</View>
<Toast context="local" />
<Dialog context="session_expiry" />
<SheetProvider context="two_factor_verify" />
</BaseDialog>
)
);

View File

@@ -21,7 +21,7 @@ import React, { useRef, useState } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { ToastEvent } from "../../services/event-manager";
import { ToastManager } from "../../services/event-manager";
import { clearMessage, setEmailVerifyMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import { useThemeColors } from "@notesnook/theme";
@@ -52,7 +52,7 @@ export const Signup = ({ changeMode, trial }) => {
const { width, height } = useWindowDimensions();
const validateInfo = () => {
if (!password.current || !email.current || !confirmPassword.current) {
ToastEvent.show({
ToastManager.show({
heading: "All fields required",
message: "Fill all the fields and try again",
type: "error",
@@ -85,7 +85,7 @@ export const Signup = ({ changeMode, trial }) => {
}
} catch (e) {
setLoading(false);
ToastEvent.show({
ToastManager.show({
heading: "Signup failed",
message: e.message,
type: "error",
@@ -111,7 +111,7 @@ export const Signup = ({ changeMode, trial }) => {
paddingHorizontal: 20,
backgroundColor: colors.secondary.background,
marginBottom: 20,
borderBottomWidth: 1,
borderBottomWidth: 0.8,
borderBottomColor: colors.primary.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,

View File

@@ -24,7 +24,7 @@ import useTimer from "../../hooks/use-timer";
import {
eSendEvent,
presentSheet,
ToastEvent
ToastManager
} from "../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { eCloseSheet } from "../../utils/events";
@@ -32,7 +32,7 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import { PressableButton } from "../ui/pressable";
import { Pressable } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
@@ -138,7 +138,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
setSending(false);
} catch (e) {
setSending(false);
ToastEvent.error(e, "Error sending 2FA Code", "local");
ToastManager.error(e, "Error sending 2FA Code", "local");
}
}, [currentMethod.method, mfaInfo.token, seconds, sending, start]);
@@ -151,7 +151,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
}}
>
<IconButton
customStyle={{
style={{
width: 70,
height: 70
}}
@@ -183,7 +183,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
{currentMethod.method === "sms" || currentMethod.method === "email" ? (
<Button
onPress={onSendCode}
type={seconds ? "gray" : "transparent"}
type={seconds ? "plain" : "transparent"}
title={
sending
? ""
@@ -247,7 +247,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
<Button
title={secondaryMethodsText[currentMethod.method]}
type="gray"
type="plain"
onPress={onRequestSecondaryMethod}
height={30}
/>
@@ -255,7 +255,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
) : (
<>
{getMethods().map((item) => (
<PressableButton
<Pressable
key={item.title}
onPress={() => {
setCurrentMethod({
@@ -263,7 +263,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
isPrimary: false
});
}}
customStyle={{
style={{
paddingHorizontal: 12,
paddingVertical: 12,
marginTop: 0,
@@ -275,8 +275,8 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
}}
>
<IconButton
type="grayAccent"
customStyle={{
type="secondaryAccented"
style={{
width: 40,
height: 40,
marginRight: 10
@@ -292,7 +292,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
>
<Paragraph size={SIZE.md}>{item.title}</Paragraph>
</View>
</PressableButton>
</Pressable>
))}
</>
)}

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useRef, useState } from "react";
import { db } from "../../common/database";
import { eSendEvent, ToastEvent } from "../../services/event-manager";
import { eSendEvent, ToastManager } from "../../services/event-manager";
import { clearMessage } from "../../services/message";
import PremiumService from "../../services/premium";
import SettingsService from "../../services/settings";
@@ -33,7 +33,7 @@ export const LoginSteps = {
passwordAuth: 3
};
export const useLogin = (onFinishLogin) => {
export const useLogin = (onFinishLogin, sessionExpired = false) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
@@ -48,7 +48,7 @@ export const useLogin = (onFinishLogin) => {
(!password.current && step === LoginSteps.passwordAuth) ||
(!email.current && step === LoginSteps.emailAuth)
) {
ToastEvent.show({
ToastManager.show({
heading: "All fields required",
message: "Fill all the fields and try again",
type: "error",
@@ -69,6 +69,7 @@ export const useLogin = (onFinishLogin) => {
switch (step) {
case LoginSteps.emailAuth: {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(async (mfa, callback) => {
try {
@@ -101,7 +102,12 @@ export const useLogin = (onFinishLogin) => {
break;
}
case LoginSteps.passwordAuth: {
await db.user.authenticatePassword(email.current, password.current);
await db.user.authenticatePassword(
email.current,
password.current,
null,
sessionExpired
);
finishLogin();
break;
}
@@ -115,7 +121,7 @@ export const useLogin = (onFinishLogin) => {
const finishWithError = async (e) => {
if (e.message === "invalid_grant") setStep(LoginSteps.emailAuth);
setLoading(false);
ToastEvent.show({
ToastManager.show({
heading: "Login failed",
message: e.message,
type: "error",
@@ -129,7 +135,7 @@ export const useLogin = (onFinishLogin) => {
PremiumService.setPremiumStatus();
setUser(user);
clearMessage();
ToastEvent.show({
ToastManager.show({
heading: "Login successful",
message: `Logged in as ${user.email}`,
type: "success",

View File

@@ -34,12 +34,13 @@ import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import NativeTooltip from "../../utils/tooltip";
import { PressableButton } from "../ui/pressable";
import { Pressable } from "../ui/pressable";
interface FloatingButton {
title?: string;
interface FloatingButtonProps {
title: string;
onPress: () => void;
color?: string;
shouldShow?: boolean;
alwaysVisible?: boolean;
}
@@ -48,7 +49,7 @@ const FloatingButton = ({
onPress,
color,
alwaysVisible = false
}: FloatingButton) => {
}: FloatingButtonProps) => {
const { colors } = useThemeColors();
const deviceMode = useSettingStore((state) => state.deviceMode);
const selectionMode = useSelectionStore((state) => state.selectionMode);
@@ -115,11 +116,11 @@ const FloatingButton = ({
animatedStyle
]}
>
<PressableButton
<Pressable
testID={notesnook.buttons.add}
type="accent"
accentColor={colors.static[color as keyof typeof colors.static]}
customStyle={{
accentColor={color}
style={{
...getElevationStyle(5),
borderRadius: 100
}}
@@ -144,7 +145,7 @@ const FloatingButton = ({
size={SIZE.xxl}
/>
</View>
</PressableButton>
</Pressable>
</Animated.View>
);
};

View File

@@ -17,41 +17,42 @@ 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, { PropsWithChildren } from "react";
import { KeyboardAvoidingView, Platform } from "react-native";
import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { useSettingStore } from "../../stores/use-setting-store";
import { Header } from "../header";
import SelectionHeader from "../selection-header";
import useKeyboard from "../../hooks/use-keyboard";
export const Container = ({ children }: PropsWithChildren) => {
const floating = useIsFloatingKeyboard();
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const insets = useGlobalSafeAreaInsets();
const keyboard = useKeyboard();
const [height, setHeight] = useState(0);
const windowHeightRef = useRef(Dimensions.get("window").height);
const { height: windowHeight } = useWindowDimensions();
useEffect(() => {
if (windowHeight !== windowHeightRef.current) {
setHeight(0);
windowHeightRef.current = windowHeight;
}
}, [windowHeight]);
return (
<KeyboardAvoidingView
behavior="padding"
enabled={Platform.OS === "ios" && !floating}
<View
style={{
flex: 1,
overflow: "hidden",
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
height: height || "100%",
width: "100%"
}}
onLayout={(event) => {
if (!keyboard.keyboardShown) {
setHeight(event.nativeEvent.layout.height);
}
}}
>
{!introCompleted ? null : (
<>
<SelectionHeader />
<Header />
</>
)}
{children}
</KeyboardAvoidingView>
</View>
);
};

View File

@@ -44,7 +44,7 @@ export default function DelayLayout({
}: IDelayLayoutProps) {
const { colors } = useThemeColors();
const loading = useDelayLayout(
!props.delay || props.delay < 300 ? 300 : props.delay
!props.delay || props.delay < 300 ? 0 : props.delay
);
const Placeholder = placeholder[props.type || "default"];

View File

@@ -17,15 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { useNoteStore } from "../../stores/use-notes-store";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { AnnouncementDialog } from "../announcements";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog";
import { AddTopicDialog } from "../dialogs/add-topic";
import JumpToSectionDialog from "../dialogs/jump-to-section";
import { LoadingDialog } from "../dialogs/loading";
import PDFPreview from "../dialogs/pdf-preview";
import ResultDialog from "../dialogs/result";
import { VaultDialog } from "../dialogs/vault";
import ImagePreview from "../image-preview";
@@ -36,17 +36,16 @@ import SheetProvider from "../sheet-provider";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data";
import PDFPreview from "../dialogs/pdf-preview";
import { AppLockPassword } from "../dialogs/applock-password";
const DialogProvider = () => {
const { colors } = useThemeColors();
const loading = useNoteStore((state) => state.loading);
return (
<>
<AppLockPassword />
<LoadingDialog />
<Dialog context="global" />
<AddTopicDialog colors={colors} />
<PremiumDialog colors={colors} />
<AuthModal colors={colors} />
<MergeConflicts />
@@ -58,10 +57,11 @@ const DialogProvider = () => {
<VaultDialog colors={colors} />
<RateAppSheet />
<ImagePreview />
{loading ? null : <Expiring />}
<Expiring />
<AnnouncementDialog />
<SessionExpired />
<PDFPreview />
<JumpToSectionDialog />
</>
);
};

View File

@@ -21,11 +21,9 @@ import { eSendEvent } from "../../services/event-manager";
import {
eCloseActionSheet,
eCloseAddNotebookDialog,
eCloseAddTopicDialog,
eCloseMoveNoteDialog,
eOpenActionSheet,
eOpenAddNotebookDialog,
eOpenAddTopicDialog,
eOpenMoveNoteDialog
} from "../../utils/events";
@@ -52,9 +50,3 @@ export const AddNotebookEvent = (notebook) => {
export const HideAddNotebookEvent = (notebook) => {
eSendEvent(eCloseAddNotebookDialog, notebook);
};
export const AddTopicEvent = (topic) => {
eSendEvent(eOpenAddTopicDialog, topic);
};
export const HideAddTopicEvent = (notebook) => {
eSendEvent(eCloseAddTopicDialog, notebook);
};

View File

@@ -17,8 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
import {
ColorValue,
KeyboardAvoidingView,
Modal,
Platform,
@@ -35,6 +36,25 @@ import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { useAppState } from "../../hooks/use-app-state";
export interface BaseDialogProps extends PropsWithChildren {
animation?: "fade" | "none" | "slide" | undefined;
visible: boolean;
onRequestClose?: () => void;
onShow?: () => void;
premium?: boolean;
statusBarTranslucent?: boolean;
transparent?: boolean;
centered?: boolean;
bottom?: boolean;
background?: string | ColorValue;
animated?: boolean;
bounce?: boolean;
closeOnTouch?: boolean;
useSafeArea?: boolean;
avoidKeyboardResize?: boolean;
enableSheetKeyboardHandler?: boolean;
}
const BaseDialog = ({
visible,
onRequestClose,
@@ -46,13 +66,14 @@ const BaseDialog = ({
transparent,
centered = true,
bottom = false,
background = null,
animated = true,
bounce = true,
closeOnTouch = true,
useSafeArea = true,
avoidKeyboardResize = false
}) => {
avoidKeyboardResize = false,
enableSheetKeyboardHandler,
background
}: BaseDialogProps) => {
const floating = useIsFloatingKeyboard();
const appState = useAppState();
const lockEvents = useRef(false);
@@ -65,7 +86,9 @@ const BaseDialog = ({
}, []);
useEffect(() => {
if (SettingsService.get().appLockMode === "background") {
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.canLockAppInBackground()) {
if (appState === "background") {
setIntervalVisible(false);
if (useUserStore.getState().appLocked) {
@@ -103,7 +126,9 @@ const BaseDialog = ({
if (lockEvents.current) return;
if (onShow) {
onShow();
useSettingStore.getState().setSheetKeyboardHandler(false);
if (!enableSheetKeyboardHandler) {
useSettingStore.getState().setSheetKeyboardHandler(false);
}
}
}}
animationType={animation}
@@ -144,7 +169,7 @@ const BaseDialog = ({
]}
>
<TouchableOpacity
onPress={closeOnTouch ? onRequestClose : null}
onPress={closeOnTouch ? onRequestClose : undefined}
style={styles.overlayButton}
/>
{premium}

View File

@@ -25,6 +25,7 @@ import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
import { getColorLinearShade } from "../../utils/colors";
const DialogButtons = ({
onPressPositive,
@@ -35,7 +36,7 @@ const DialogButtons = ({
doneText,
positiveType
}) => {
const { colors } = useThemeColors();
const { colors, isDark } = useThemeColors();
return (
<View
@@ -46,7 +47,13 @@ const DialogButtons = ({
height: 60,
borderBottomRightRadius: 10,
borderBottomLeftRadius: 10,
paddingHorizontal: 12
paddingHorizontal: 12,
borderTopWidth: 0.7,
borderTopColor: getColorLinearShade(
colors.secondary.background,
0.05,
isDark
)
}
]}
>
@@ -80,7 +87,7 @@ const DialogButtons = ({
onPress={onPressNegative}
fontSize={SIZE.md}
testID={notesnook.ids.default.dialog.no}
type="gray"
type="plain"
bold
title={negativeTitle}
/>

View File

@@ -18,12 +18,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import { View, ViewProps } from "react-native";
import { DDS } from "../../services/device-detection";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../utils/elevation";
import { getContainerBorder } from "../../utils/colors";
const DialogContainer = ({ width, height, ...restProps }) => {
const DialogContainer = ({
width,
height,
style,
...restProps
}: ViewProps & {
width?: any;
height?: any;
}) => {
const { colors } = useThemeColors();
return (
@@ -35,7 +44,8 @@ const DialogContainer = ({ width, height, ...restProps }) => {
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
paddingTop: 12,
...getContainerBorder(colors.secondary.background, 0.8, 0.05)
}}
/>
);

View File

@@ -22,7 +22,7 @@ import { Text, View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButtonProps } from "../ui/pressable";
import { PressableProps } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
@@ -34,7 +34,7 @@ type DialogHeaderProps = {
onPress?: () => void;
loading?: boolean;
title?: string;
type?: PressableButtonProps["type"];
type?: PressableProps["type"];
icon?: string;
};
paragraphColor?: string;
@@ -86,7 +86,9 @@ const DialogHeader = ({
>
{title}{" "}
{titlePart ? (
<Text style={{ color: colors.primary.accent }}>{titlePart}</Text>
<Text style={{ color: colors.primary.accent }}>
{titlePart}
</Text>
) : null}
</Heading>
@@ -101,8 +103,8 @@ const DialogHeader = ({
fontSize={13}
title={button.title}
icon={button.icon}
type={button.type || "grayBg"}
height={25}
type={button.type || "secondary"}
height={30}
/>
) : null}
</View>

View File

@@ -25,12 +25,12 @@ type DialogInfo = {
paragraph?: string;
positiveText?: string;
negativeText?: string;
positivePress?: (value: unknown) => void;
positivePress?: (value: any) => void;
onClose?: () => void;
positiveType?:
| "transparent"
| "gray"
| "grayBg"
| "plain"
| "secondary"
| "accent"
| "inverted"
| "shade"
@@ -41,7 +41,9 @@ type DialogInfo = {
input: boolean;
inputPlaceholder: string;
defaultValue: string;
context: "global" | "local";
// eslint-disable-next-line @typescript-eslint/ban-types
context: "global" | "local" | (string & {});
secureTextEntry?: boolean;
};
export function presentDialog(data: Partial<DialogInfo>): void {

View File

@@ -40,7 +40,10 @@ import { Button } from "../ui/button";
export const Dialog = ({ context = "global" }) => {
const { colors } = useThemeColors();
const [visible, setVisible] = useState(false);
const [inputValue, setInputValue] = useState(null);
const [checked, setChecked] = useState(false);
const values = useRef({
inputValue: undefined
});
const inputRef = useRef();
const [dialogInfo, setDialogInfo] = useState({
title: "",
@@ -62,21 +65,12 @@ export const Dialog = ({ context = "global" }) => {
}
});
useEffect(() => {
eSubscribeEvent(eOpenSimpleDialog, show);
eSubscribeEvent(eCloseSimpleDialog, hide);
return () => {
eUnSubscribeEvent(eOpenSimpleDialog, show);
eUnSubscribeEvent(eCloseSimpleDialog, hide);
};
}, [show]);
const onPressPositive = async () => {
if (dialogInfo.positivePress) {
inputRef.current?.blur();
let result = await dialogInfo.positivePress(
inputValue || dialogInfo.defaultValue
values.current.inputValue || dialogInfo.defaultValue,
checked
);
if (result === false) {
return;
@@ -91,14 +85,26 @@ export const Dialog = ({ context = "global" }) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
setDialogInfo(data);
setChecked(false);
values.current.inputValue = data.defaultValue;
setVisible(true);
setInputValue(data.defaultValue);
},
[context]
);
useEffect(() => {
eSubscribeEvent(eOpenSimpleDialog, show);
eSubscribeEvent(eCloseSimpleDialog, hide);
return () => {
eUnSubscribeEvent(eOpenSimpleDialog, show);
eUnSubscribeEvent(eCloseSimpleDialog, hide);
};
}, [show]);
const hide = () => {
setInputValue(null);
setChecked(false);
values.current.inputValue = undefined;
setVisible(false);
};
@@ -106,7 +112,6 @@ export const Dialog = ({ context = "global" }) => {
if (dialogInfo.onClose) {
await dialogInfo.onClose();
}
hide();
};
@@ -159,7 +164,7 @@ export const Dialog = ({ context = "global" }) => {
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(value) => {
setInputValue(value);
values.current.inputValue = value;
}}
testID="input-value"
secureTextEntry={dialogInfo.secureTextEntry}
@@ -176,10 +181,10 @@ export const Dialog = ({ context = "global" }) => {
<>
<Button
onPress={() => {
setInputValue(!inputValue);
setChecked(!checked);
}}
icon={
inputValue
checked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
@@ -187,9 +192,10 @@ export const Dialog = ({ context = "global" }) => {
justifyContent: "flex-start"
}}
height={35}
iconSize={20}
width="100%"
title={dialogInfo.check.info}
type={inputValue ? dialogInfo.check.type : "gray"}
type={checked ? dialogInfo.check.type || "selected" : "plain"}
/>
</>
) : null}

View File

@@ -1,190 +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, { createRef } from "react";
import { View } from "react-native";
import { useMenuStore } from "../../../stores/use-menu-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { db } from "../../../common/database";
import {
eCloseAddTopicDialog,
eOnTopicSheetUpdate,
eOpenAddTopicDialog
} from "../../../utils/events";
import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
import DialogContainer from "../../dialog/dialog-container";
import DialogHeader from "../../dialog/dialog-header";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import { Toast } from "../../toast";
import { useRelationStore } from "../../../stores/use-relation-store";
export class AddTopicDialog extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
titleFocused: false,
loading: false
};
this.ref = createRef();
this.title;
this.titleRef = createRef();
this.notebook = null;
this.toEdit = null;
}
addNewTopic = async () => {
try {
if (!this.title || this.title?.trim() === "") {
ToastEvent.show({
heading: "Topic title is required",
type: "error",
context: "local"
});
return;
}
if (!this.toEdit) {
await db.notebooks.notebook(this.notebook.id).topics.add(this.title);
} else {
let topic = this.toEdit;
topic.title = this.title;
await db.notebooks.notebook(topic.notebookId).topics.add(topic);
}
this.close();
setTimeout(() => {
Navigation.queueRoutesForUpdate();
useMenuStore.getState().setMenuPins();
});
eSendEvent(eOnTopicSheetUpdate);
useRelationStore.getState().update();
} catch (e) {
console.error(e);
}
};
componentDidMount() {
eSubscribeEvent(eOpenAddTopicDialog, this.open);
eSubscribeEvent(eCloseAddTopicDialog, this.close);
}
componentWillUnmount() {
eUnSubscribeEvent(eOpenAddTopicDialog, this.open);
eUnSubscribeEvent(eCloseAddTopicDialog, this.close);
}
open = async ({ notebookId, toEdit }) => {
let id = notebookId;
if (id) {
this.notebook = await db.notebooks.notebook(id).data;
}
this.toEdit = toEdit;
if (this.toEdit) {
this.title = this.toEdit.title;
}
this.setState({
visible: true
});
};
close = () => {
this.title = null;
this.notebook = null;
this.toEdit = null;
this.setState({
visible: false
});
};
render() {
const { visible } = this.state;
if (!visible) return null;
return (
<BaseDialog
onShow={async () => {
if (this.toEdit) {
this.titleRef.current?.setNativeProps({
text: this.toEdit.title
});
}
this.ref.current?.animateNextTransition();
await sleep(300);
this.titleRef.current?.focus();
}}
bounce={false}
statusBarTranslucent={false}
visible={true}
onRequestClose={this.close}
>
<DialogContainer>
<DialogHeader
icon="book-outline"
title={this.toEdit ? "Edit topic" : "New topic"}
paragraph={
this.toEdit
? "Edit title of the topic"
: "Add a new topic in " + this.notebook.title
}
padding={12}
/>
<Seperator half />
<View
style={{
paddingHorizontal: 12,
zIndex: 10
}}
>
<Input
fwdRef={this.titleRef}
testID="input-title"
onChangeText={(value) => {
this.title = value;
}}
blurOnSubmit={false}
placeholder="Enter title"
onSubmit={() => this.addNewTopic()}
returnKeyLabel="Done"
returnKeyType="done"
/>
</View>
<DialogButtons
positiveTitle={this.toEdit ? "Save" : "Add"}
onPressNegative={() => this.close()}
onPressPositive={() => this.addNewTopic()}
/>
</DialogContainer>
<Toast context="local" />
</BaseDialog>
);
}
}

View File

@@ -0,0 +1,358 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import {
clearAppLockVerificationCipher,
setAppLockVerificationCipher,
validateAppLockPassword
} from "../../../common/database/encryption";
import BiometicService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
eSendEvent,
eSubscribeEvent
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
import { getElevationStyle } from "../../../utils/elevation";
import {
eCloseAppLocKPasswordDailog,
eOpenAppLockPasswordDialog
} from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
import DialogHeader from "../../dialog/dialog-header";
import { Toast } from "../../toast";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
export const AppLockPassword = () => {
const { colors } = useThemeColors();
const [mode, setMode] = useState<"create" | "change" | "remove">("create");
const [keyboardType, setKeyboardType] = useState<"pin" | "password">(
useSettingStore.getState().settings.applockKeyboardType === "default"
? "password"
: "pin"
);
const [visible, setVisible] = useState(false);
const currentPasswordInputRef = useRef<TextInput>(null);
const passwordInputRef = useRef<TextInput>(null);
const confirmPasswordInputRef = useRef<TextInput>(null);
const values = useRef<{
currentPassword?: string;
password?: string;
confirmPassword?: string;
}>({});
const [secureTextEntry, setSecureTextEntry] = useState(true);
useEffect(() => {
const subs = [
eSubscribeEvent(
eOpenAppLockPasswordDialog,
(mode: "create" | "change" | "remove") => {
setMode(mode);
setVisible(true);
}
),
eSubscribeEvent(eCloseAppLocKPasswordDailog, () => {
values.current = {};
setVisible(false);
})
];
return () => {
subs.forEach((sub) => sub?.unsubscribe());
};
}, []);
const close = () => {
values.current = {};
setVisible(false);
};
return !visible ? null : (
<BaseDialog
onShow={async () => {
await sleep(100);
if (mode !== "change") {
passwordInputRef.current?.focus();
} else {
currentPasswordInputRef.current?.focus();
}
}}
statusBarTranslucent={false}
onRequestClose={close}
visible={visible}
>
<View
style={{
...getElevationStyle(10),
width: DDS.isTab ? 350 : "85%",
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
}}
>
<DialogHeader
title={
mode === "change"
? `Change app lock ${keyboardType}`
: mode === "remove"
? `Remove app lock ${keyboardType}`
: `Set up app lock ${keyboardType}`
}
paragraph={
mode === "change"
? `Change app lock ${keyboardType}`
: mode === "remove"
? `Remove app lock ${keyboardType}`
: `Set up a custom app lock ${keyboardType} to unlock the app`
}
icon="shield"
padding={12}
/>
<Seperator half />
<View
style={{
paddingHorizontal: 12
}}
>
{mode === "change" ? (
<Input
fwdRef={currentPasswordInputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.currentPassword = value;
}}
onSubmit={() => {
passwordInputRef.current?.focus();
}}
defaultValue={values.current.currentPassword}
autoComplete="password"
returnKeyLabel="Next"
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
returnKeyType="next"
secureTextEntry={secureTextEntry}
placeholder={`Current ${keyboardType}`}
/>
) : null}
<Input
fwdRef={passwordInputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.password = value;
}}
onSubmit={() => {
confirmPasswordInputRef.current?.focus();
}}
defaultValue={values.current.password}
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
autoComplete="password"
returnKeyLabel={mode !== "remove" ? "Next" : "Remove"}
returnKeyType={mode !== "remove" ? "next" : "done"}
secureTextEntry={secureTextEntry}
buttonLeft={
<IconButton
name={keyboardType === "password" ? "numeric" : "keyboard"}
onPress={() => {
setKeyboardType(
keyboardType === "password" ? "pin" : "password"
);
setSecureTextEntry(false);
setImmediate(() => {
setSecureTextEntry(true);
});
}}
style={{
width: 25,
height: 25,
marginRight: 5
}}
size={SIZE.lg}
/>
}
placeholder={
mode === "change"
? `New ${keyboardType}`
: `${keyboardType === "pin" ? "Pin" : "Password"}`
}
/>
{mode !== "remove" ? (
<Input
fwdRef={confirmPasswordInputRef}
autoCapitalize="none"
onChangeText={(value) => {
values.current.confirmPassword = value;
}}
onSubmit={() => {
confirmPasswordInputRef.current?.focus();
}}
defaultValue={values.current.confirmPassword}
keyboardType={keyboardType === "pin" ? "number-pad" : "default"}
customValidator={() => values.current.password || ""}
validationType="confirmPassword"
autoComplete="password"
returnKeyLabel="Done"
returnKeyType="done"
secureTextEntry={secureTextEntry}
placeholder={`Confirm ${keyboardType}`}
/>
) : null}
</View>
<DialogButtons
onPressNegative={close}
onPressPositive={async () => {
if (mode === "create") {
if (!values.current.password || !values.current.confirmPassword) {
ToastManager.error(
new Error("All inputs are required"),
undefined,
"local"
);
return;
}
if (values.current.password !== values.current.confirmPassword) {
ToastManager.error(
new Error(
`${
keyboardType === "pin" ? "Pin" : "Password"
} does not match`
),
undefined,
"local"
);
return;
}
await setAppLockVerificationCipher(values.current.password);
SettingsService.setProperty("appLockHasPasswordSecurity", true);
} else if (mode === "change") {
if (
!values.current.currentPassword ||
!values.current.password ||
!values.current.confirmPassword
) {
ToastManager.error(
new Error("All inputs are required"),
undefined,
"local"
);
return;
}
if (values.current.password !== values.current.confirmPassword) {
ToastManager.error(
new Error(
`${
keyboardType === "pin" ? "Pin" : "Password"
} does not match`
),
undefined,
"local"
);
return;
}
const isCurrentPasswordCorrect = await validateAppLockPassword(
values.current.currentPassword
);
if (!isCurrentPasswordCorrect) {
ToastManager.error(
new Error(
`${keyboardType === "pin" ? "Pin" : "Password"} incorrect`
),
undefined,
"local"
);
return;
}
SettingsService.setProperty("appLockHasPasswordSecurity", true);
await setAppLockVerificationCipher(values.current.password);
} else if (mode === "remove") {
if (!values.current.password) {
ToastManager.error(
new Error("All inputs are required"),
undefined,
"local"
);
return;
}
const isCurrentPasswordCorrect = await validateAppLockPassword(
values.current.password
);
if (!isCurrentPasswordCorrect) {
ToastManager.error(
new Error(
`${keyboardType === "pin" ? "Pin" : "Password"} incorrect`
),
"local"
);
return;
}
clearAppLockVerificationCipher();
SettingsService.setProperty("appLockHasPasswordSecurity", false);
if (
!(await BiometicService.isBiometryAvailable()) ||
SettingsService.getProperty("biometricsAuthEnabled") === false
) {
SettingsService.setProperty("appLockEnabled", false);
ToastManager.show({
message: "App lock disabled",
type: "success"
});
}
}
SettingsService.setProperty(
"applockKeyboardType",
keyboardType === "password" ? "default" : "numeric"
);
close();
}}
positiveTitle="Save"
negativeTitle="Cancel"
positiveType="transparent"
loading={false}
doneText=""
/>
</View>
<Toast context="local" />
</BaseDialog>
);
};
AppLockPassword.present = (mode: "create" | "change" | "remove") => {
eSendEvent(eOpenAppLockPasswordDialog, mode);
};

View File

@@ -0,0 +1,181 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import { Image, ScrollView, TouchableOpacity, View } from "react-native";
import { ImagePickerResponse } from "react-native-image-picker";
import { useThemeColors } from "../../../../../../packages/theme/dist";
import { presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Notice } from "../../ui/notice";
import Paragraph from "../../ui/typography/paragraph";
export default function AttachImage({
response,
onAttach,
close
}: {
response: ImagePickerResponse;
onAttach: ({ compress }: { compress: boolean }) => void;
close: ((ctx?: string | undefined) => void) | undefined;
}) {
const { colors } = useThemeColors();
const [compress, setCompress] = useState(true);
return (
<View
style={{
alignItems: "center",
paddingHorizontal: 12
}}
>
<View
style={{
backgroundColor: colors.secondary.background,
marginBottom: 12,
height: 140,
width: "100%",
borderRadius: 10,
padding: 5,
paddingHorizontal: 12
}}
>
<Paragraph style={{ color: colors.primary.paragraph, marginBottom: 6 }}>
Attaching {response.assets?.length} image(s):
</Paragraph>
<ScrollView horizontal>
{response.assets?.map((item) => (
<TouchableOpacity key={item.fileName} activeOpacity={0.9}>
<Image
source={{
uri: item.uri
}}
style={{
width: 100,
height: 100,
borderRadius: 5,
backgroundColor: "black",
marginRight: 6
}}
resizeMode="cover"
/>
</TouchableOpacity>
))}
</ScrollView>
</View>
<TouchableOpacity
activeOpacity={1}
style={{
flexDirection: "row",
alignSelf: "center",
marginBottom: 12,
alignItems: "center",
width: "100%"
}}
onPress={() => {
setCompress(!compress);
}}
>
<IconButton
size={SIZE.lg}
name={compress ? "checkbox-marked" : "checkbox-blank-outline"}
color={compress ? colors.primary.accent : colors.primary.icon}
style={{
width: 25,
height: 25
}}
onPress={() => {
setCompress(!compress);
}}
/>
<Paragraph
style={{
flexShrink: 1,
marginLeft: 3
}}
size={SIZE.sm}
>
Compress (recommended)
</Paragraph>
</TouchableOpacity>
{!compress ? (
<Notice
type="alert"
text="Images uploaded without compression are slow to load and take more bandwidth. We recommend compressing images unless you need image in original quality."
size="small"
style={{
width: "100%",
marginBottom: 12
}}
/>
) : (
<Notice
type="information"
text="Compressed images are uploaded in Full HD resolution and usually are good enough for most use cases."
size="small"
style={{
width: "100%",
marginBottom: 12
}}
/>
)}
<Button
title={`${
(response.assets?.length || 0) > 1 ? "Attach Images" : "Attach Image"
}`}
type="accent"
width="100%"
onPress={() => {
onAttach({
compress
});
}}
/>
</View>
);
}
AttachImage.present = (response: ImagePickerResponse) => {
return new Promise((resolve) => {
let resolved = false;
presentSheet({
component: (ref, close, update) => (
<AttachImage
response={response}
close={close}
onAttach={(result) => {
resolved = true;
console.log("closing");
resolve(result);
close?.();
}}
/>
),
onClose: () => {
if (resolved) return;
resolve(undefined);
}
});
});
};

View File

@@ -0,0 +1,196 @@
/*
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 { Color } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native";
import ColorPickerWheel from "react-native-wheel-color-picker";
import { db } from "../../../common/database";
import { useStoredRef } from "../../../hooks/use-stored-ref";
import { ToastManager } from "../../../services/event-manager";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import BaseDialog from "../../dialog/base-dialog";
import DialogContainer from "../../dialog/dialog-container";
import { Toast } from "../../toast";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { Pressable } from "../../ui/pressable";
const HEX_COLOR_REGEX_ALPHA =
/^#(?:(?:[\da-fA-F]{3}){1,2}|(?:[\da-fA-F]{4}){1,2})$/;
const ColorPicker = ({
visible,
setVisible,
onColorAdded
}: {
visible: boolean;
setVisible: (value: boolean) => void;
onColorAdded: (color: Color) => void;
}) => {
const [selectedColor, setSelectedColor] = useState<string>();
const { colors } = useThemeColors();
const inputRef = useRef<TextInput>(null);
const title = useRef<string>();
const colorRef = useStoredRef("color-ref", "#f0f0f0");
return (
<BaseDialog
visible={visible}
onRequestClose={() => {
title.current = undefined;
setVisible(false);
useSettingStore.getState().setSheetKeyboardHandler(true);
}}
statusBarTranslucent={false}
centered
>
<Toast context="color-picker" />
<DialogContainer
style={{
paddingTop: 0,
maxHeight: 600
}}
>
<View>
<View
style={{
width: "100%",
height: 250,
backgroundColor: selectedColor,
borderRadius: 10
}}
>
<ColorPickerWheel
onColorChangeComplete={(color) => {
if (color === selectedColor) return;
colorRef.current = color;
setSelectedColor(color);
inputRef.current?.setNativeProps({
placeholder: color,
text: color
});
}}
useNativeLayout={true}
autoResetSlider={true}
color={colorRef.current}
sliderSize={30}
thumbSize={25}
shadeWheelThumb
shadeSliderThumb={true}
swatchesOnly={false}
swatches={false}
useNativeDriver={true}
/>
</View>
<View
style={{
paddingHorizontal: 12
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingTop: 10,
columnGap: 10,
marginBottom: 10
}}
>
<Input
fwdRef={inputRef}
placeholder="#f0f0f0"
marginBottom={0}
onChangeText={(value) => {
if (HEX_COLOR_REGEX_ALPHA.test(value)) {
colorRef.current = value;
setSelectedColor(value);
}
}}
defaultValue={colorRef.current}
/>
<Pressable
type="accent"
accentColor={selectedColor || colors.secondary.background}
style={{
width: 45,
height: 45,
borderRadius: 100,
justifyContent: "center",
alignItems: "center"
}}
/>
</View>
<Input
marginBottom={10}
onChangeText={(value) => {
title.current = value;
}}
defaultValue={title.current}
placeholder={title.current || "Color title"}
/>
<Button
title="Add color"
onPress={async () => {
if (!selectedColor)
return ToastManager.error(
new Error("Select a color"),
undefined,
"color-picker"
);
if (!title.current)
return ToastManager.error(
new Error("Enter a title for the color")
);
const exists = await db.colors.all.find((v) =>
v.and([v(`colorCode`, "==", selectedColor)])
);
if (exists)
return ToastManager.error(
new Error(`Color #${selectedColor} already exists`)
);
const id = await db.colors.add({
title: title.current,
colorCode: selectedColor
});
useRelationStore.getState().update();
useMenuStore.getState().setColorNotes();
setVisible(false);
const color = await db.colors.color(id);
if (color) {
onColorAdded?.(color);
}
}}
type={selectedColor ? "secondaryAccented" : "secondary"}
width="100%"
/>
</View>
</View>
</DialogContainer>
</BaseDialog>
);
};
export default ColorPicker;

View File

@@ -17,15 +17,22 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { ScrollView, View } from "react-native";
import { GroupHeader, Item, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, {
RefObject,
useCallback,
useEffect,
useRef,
useState
} from "react";
import { ActivityIndicator, FlatList, ScrollView, View } from "react-native";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useMessageStore } from "../../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../../utils/elevation";
import {
eCloseJumpToDialog,
@@ -34,29 +41,51 @@ import {
} from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import BaseDialog from "../../dialog/base-dialog";
import { PressableButton } from "../../ui/pressable";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { useCallback } from "react";
const offsets = [];
let timeout = null;
const JumpToSectionDialog = ({ scrollRef, data, type }) => {
const JumpToSectionDialog = () => {
const scrollRef = useRef<RefObject<FlatList>>();
const [data, setData] = useState<VirtualizedGrouping<Item>>();
const { colors } = useThemeColors();
const notes = data;
const [visible, setVisible] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
const currentScrollPosition = useRef(0);
const [loading, setLoading] = useState(false);
const onPress = (item) => {
let ind = notes.findIndex(
(i) => i.title === item.title && i.type === "header"
);
scrollRef.current?.scrollToIndex({
index: ind,
const [groups, setGroups] = useState<
{
index: number;
group: GroupHeader;
}[]
>();
const offsets = useRef<number[]>([]);
const onPress = (item: { index: number; group: GroupHeader }) => {
scrollRef.current?.current?.scrollToIndex({
index: item.index,
animated: true
});
close();
};
const open = useCallback(
({
data,
ref
}: {
data: VirtualizedGrouping<Item>;
ref: RefObject<FlatList>;
}) => {
setLoading(true);
setData(data);
scrollRef.current = ref;
setVisible(true);
},
[]
);
useEffect(() => {
eSubscribeEvent(eOpenJumpToDialog, open);
eSubscribeEvent(eCloseJumpToDialog, close);
@@ -69,54 +98,46 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
};
}, [open]);
const onScroll = (data) => {
let y = data.y;
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
timeout = setTimeout(() => {
let index = offsets.findIndex((o, i) => o <= y && offsets[i + 1] > y);
setCurrentIndex(index || 0);
}, 200);
const onScroll = (data: { x: number; y: number }) => {
currentScrollPosition.current = data.y;
};
const open = useCallback(
(_type) => {
if (_type !== type) return;
setVisible(true);
},
[type]
);
const close = () => {
setVisible(false);
};
useEffect(() => {
loadOffsets();
}, [loadOffsets, notes]);
const loadOffsets = useCallback(() => {
notes
.filter((i) => i.type === "header")
.map((item, index) => {
const loadGroupsAndOffsets = useCallback(() => {
notes?.groups?.().then((groups) => {
setGroups(groups);
offsets.current = [];
groups.map((item, index) => {
let offset = 35 * index;
let ind = notes.findIndex(
(i) => i.title === item.title && i.type === "header"
);
let messageState = useMessageStore.getState().message;
let msgOffset = messageState?.visible ? 60 : 10;
ind = ind + 1;
ind = ind - (index + 1);
offset = offset + ind * 100 + msgOffset;
offsets.push(offset);
let groupIndex = item.index;
const messageState = useMessageStore.getState().message;
const msgOffset = messageState?.visible ? 60 : 10;
groupIndex = groupIndex + 1;
groupIndex = groupIndex - (index + 1);
offset = offset + groupIndex * 100 + msgOffset;
offsets.current.push(offset);
});
const index = offsets.current?.findIndex((o, i) => {
return (
o <= currentScrollPosition.current + 100 &&
offsets.current[i + 1] - 100 > currentScrollPosition.current
);
});
setCurrentIndex(index < 0 ? 0 : index);
setLoading(false);
});
}, [notes]);
return !visible ? null : (
<BaseDialog
onShow={() => {
loadOffsets();
loadGroupsAndOffsets();
}}
onRequestClose={close}
visible={true}
@@ -135,35 +156,42 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
paddingTop: 30
}}
>
<ScrollView
style={{
maxHeight: "100%"
}}
>
<View
{loading ? (
<ActivityIndicator
size={SIZE.lg}
color={colors.primary.accent}
style={{
flexDirection: "row",
flexWrap: "wrap",
alignSelf: "center",
justifyContent: "center",
paddingBottom: 20
marginBottom: 20
}}
/>
) : (
<ScrollView
style={{
maxHeight: "100%"
}}
>
{notes
.filter((i) => i.type === "header")
.map((item, index) => {
return item.title ? (
<PressableButton
key={item.title}
onPress={() => onPress(item, index)}
type={currentIndex === index ? "selected" : "transparent"}
customStyle={{
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
alignSelf: "center",
justifyContent: "center",
paddingBottom: 20
}}
>
{groups?.map((item, index) => {
return (
<Pressable
key={item.group.id}
onPress={() => onPress(item)}
type={currentIndex === index ? "accent" : "transparent"}
style={{
minWidth: "20%",
width: null,
paddingHorizontal: 12,
margin: 5,
borderRadius: 100,
height: 25,
height: 30,
marginVertical: 10
}}
>
@@ -171,20 +199,21 @@ const JumpToSectionDialog = ({ scrollRef, data, type }) => {
size={SIZE.sm}
color={
currentIndex === index
? colors.selected.accent
: colors.primary.accent
? colors.static.white
: colors.primary.paragraph
}
style={{
textAlign: "center"
}}
>
{item.title}
{item.group.title}
</Paragraph>
</PressableButton>
) : null;
</Pressable>
);
})}
</View>
</ScrollView>
</View>
</ScrollView>
)}
</View>
</BaseDialog>
);

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions, TextInput, View } from "react-native";
import {
addOrientationListener,
@@ -85,7 +85,7 @@ const PDFPreview = () => {
return () => {
eUnSubscribeEvent("PDFPreview", open);
};
}, []);
}, [open]);
const onOrientationChange = (o) => {
if (o.includes("LANDSCAPE")) {
@@ -102,23 +102,26 @@ const PDFPreview = () => {
};
}, []);
const open = async (attachment) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
setAttachment(attachment);
let hash = attachment.metadata.hash;
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
snapshotValue.current = snapshot.current;
setPDFSource("file://" + path);
setLoading(false);
}, 100);
};
const open = useCallback(
async (attachment) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
setAttachment(attachment);
let hash = attachment.hash;
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
snapshotValue.current = snapshot.current;
setPDFSource("file://" + path);
setLoading(false);
}, 100);
},
[snapshot]
);
const close = () => {
setPDFSource(null);
@@ -130,7 +133,7 @@ const PDFPreview = () => {
if (error?.message === "Password required or incorrect password.") {
await sleep(300);
presentDialog({
context: attachment?.metadata?.hash,
context: attachment?.hash,
input: true,
inputPlaceholder: "Enter password",
positiveText: "Unlock",
@@ -151,8 +154,8 @@ const PDFPreview = () => {
return (
visible && (
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
<SheetProvider context={attachment?.metadata?.hash} />
<Dialog context={attachment?.metadata?.hash} />
<SheetProvider context={attachment?.hash} />
<Dialog context={attachment?.hash} />
<View
style={{
@@ -208,7 +211,7 @@ const PDFPreview = () => {
color={colors.static.white}
name="arrow-left"
onPress={close}
customStyle={{
style={{
marginRight: 12
}}
size={SIZE.xxl}
@@ -261,7 +264,7 @@ const PDFPreview = () => {
color={colors.static.white}
name="download"
onPress={() => {
downloadAttachment(attachment.metadata.hash, false);
downloadAttachment(attachment.hash, false);
}}
/>
</View>

View File

@@ -23,28 +23,26 @@ import { InteractionManager, View } from "react-native";
import Share from "react-native-share";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { editorController } from "../../../screens/editor/tiptap/utils";
import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
eUnSubscribeEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search";
import { convertNoteToText } from "../../../utils/note-to-text";
import { getElevationStyle } from "../../../utils/elevation";
import {
eClearEditor,
eCloseActionSheet,
eCloseVaultDialog,
eOnLoadNote,
eOpenVaultDialog
eOpenVaultDialog,
eUpdateNoteInEditor
} from "../../../utils/events";
import { deleteItems } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
import { convertNoteToText } from "../../../utils/note-to-text";
import { sleep } from "../../../utils/time";
import BaseDialog from "../../dialog/base-dialog";
import DialogButtons from "../../dialog/dialog-buttons";
@@ -55,10 +53,6 @@ import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
let Keychain;
const passInputRef = createRef();
const confirmPassRef = createRef();
const changePassInputRef = createRef();
export class VaultDialog extends Component {
constructor(props) {
super(props);
@@ -86,8 +80,14 @@ export class VaultDialog extends Component {
description: null,
clearVault: false,
deleteVault: false,
deleteAll: false
deleteAll: false,
noteLocked: false
};
this.passInputRef = createRef();
this.confirmPassRef = createRef();
this.changePassInputRef = createRef();
this.password = null;
this.confirmPassword = null;
this.newPassword = null;
@@ -99,7 +99,7 @@ export class VaultDialog extends Component {
? "Revoke Vault Fingerprint Unlock"
: this.state.changePassword
? "Change Vault Password"
: this.state.note.locked
: this.state.noteLocked
? this.state.deleteNote
? "Delete note"
: this.state.share
@@ -120,7 +120,7 @@ export class VaultDialog extends Component {
? "Setup a new password for the vault."
: this.state.permanant
? "Enter password to remove note from vault."
: this.state.note.locked
: this.state.noteLocked
? this.state.deleteNote
? "Unlock note to delete it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.share
@@ -148,9 +148,6 @@ export class VaultDialog extends Component {
* @param {import('../../../services/event-manager').vaultType} data
*/
open = async (data) => {
if (!Keychain) {
Keychain = require("react-native-keychain");
}
let biometry = await BiometricService.isBiometryAvailable();
let available = false;
let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
@@ -177,7 +174,8 @@ export class VaultDialog extends Component {
title: data.title,
description: data.description,
clearVault: data.clearVault,
deleteVault: data.deleteVault
deleteVault: data.deleteVault,
noteLocked: data.item && (await db.vaults.itemExists(data.item))
});
if (
@@ -199,7 +197,7 @@ export class VaultDialog extends Component {
close = () => {
if (this.state.loading) {
ToastEvent.show({
ToastManager.show({
heading: this.state.title,
message: "Please wait and do not close the app.",
type: "success",
@@ -212,7 +210,6 @@ export class VaultDialog extends Component {
this.password = null;
this.confirmPassword = null;
SearchService.updateAndSearch();
this.setState({
visible: false,
note: {},
@@ -236,7 +233,7 @@ export class VaultDialog extends Component {
if (this.state.loading) return;
if (!this.password) {
ToastEvent.show({
ToastManager.show({
heading: "Password not entered",
message: "Enter a password for the vault and try again.",
type: "error",
@@ -244,20 +241,10 @@ export class VaultDialog extends Component {
});
return;
}
if (this.password && this.password.length < 3) {
ToastEvent.show({
heading: "Password too short",
message: "Password must be longer than 3 characters.",
type: "error",
context: "local"
});
return;
}
if (!this.state.novault) {
if (this.password !== this.confirmPassword) {
ToastEvent.show({
ToastManager.show({
heading: "Passwords do not match",
type: "error",
context: "local"
@@ -283,7 +270,7 @@ export class VaultDialog extends Component {
if (this.state.biometricUnlock) {
this._enrollFingerprint(this.newPassword);
}
ToastEvent.show({
ToastManager.show({
heading: "Vault password updated successfully",
type: "success",
context: "global"
@@ -295,17 +282,19 @@ export class VaultDialog extends Component {
loading: false
});
if (e.message === db.vault.ERRORS.wrongPassword) {
ToastEvent.show({
ToastManager.show({
heading: "Incorrect password",
message: "Please enter the correct password and try again",
type: "error",
context: "local"
});
} else {
ToastManager.error(e);
}
});
} else if (this.state.locked) {
if (!this.password || this.password.trim() === 0) {
ToastEvent.show({
ToastManager.show({
heading: "Incorrect password",
message: "Please enter the correct password and try again",
type: "error",
@@ -316,7 +305,7 @@ export class VaultDialog extends Component {
});
return;
}
if (this.state.note.locked) {
if (this.state.noteLocked) {
await this._unlockNote();
} else {
db.vault
@@ -348,14 +337,33 @@ export class VaultDialog extends Component {
let verified = await db.user.verifyPassword(this.password);
if (!(await db.user.getUser())) verified = true;
if (verified) {
let noteIds = [];
if (this.state.deleteAll) {
const vault = await db.vaults.default();
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
);
});
eSendEvent("vaultUpdated");
this.setState({
loading: false
});
this.close();
} else {
ToastEvent.show({
ToastManager.show({
heading: "Account password incorrect",
message: "Please enter correct password for your account.",
type: "error",
@@ -375,14 +383,29 @@ export class VaultDialog extends Component {
loading: true
});
try {
const vault = await db.vaults.default();
const relations = await db.relations.from(vault, "note").get();
const noteIds = relations.map((item) => item.toId);
await db.vault.clear(this.password);
noteIds.forEach((id) => {
eSendEvent(
eUpdateNoteInEditor,
{
id: id,
deleted: true
},
true
);
});
this.setState({
loading: false
});
this.close();
eSendEvent("vaultUpdated");
} catch (e) {
ToastEvent.show({
ToastManager.show({
heading: "Vault password incorrect",
message: "Please enter correct password to clear vault.",
type: "error",
@@ -396,7 +419,7 @@ export class VaultDialog extends Component {
async _lockNote() {
if (!this.password || this.password.trim() === 0) {
ToastEvent.show({
ToastManager.show({
heading: "Incorrect password",
type: "error",
context: "local"
@@ -404,11 +427,12 @@ export class VaultDialog extends Component {
return;
} else {
await db.vault.add(this.state.note.id);
if (this.state.note.id === editorController.current?.note?.id) {
eSendEvent(eClearEditor);
}
console.log("update note event...");
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
ToastEvent.show({
ToastManager.show({
message: "Note locked successfully",
type: "error",
context: "local"
@@ -421,7 +445,7 @@ export class VaultDialog extends Component {
async _unlockNote() {
if (!this.password || this.password.trim() === 0) {
ToastEvent.show({
ToastManager.show({
heading: "Incorrect password",
message: "Please enter the correct password and try again",
type: "error",
@@ -459,7 +483,7 @@ export class VaultDialog extends Component {
async _deleteNote() {
try {
await db.vault.remove(this.state.note.id, this.password);
await deleteItems(this.state.note);
await deleteItems([this.state.note.id], "note");
this.close();
} catch (e) {
this._takeErrorAction(e);
@@ -479,7 +503,7 @@ export class VaultDialog extends Component {
loading: false
});
eSendEvent("vaultUpdated");
ToastEvent.show({
ToastManager.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
@@ -488,7 +512,7 @@ export class VaultDialog extends Component {
this.close();
} catch (e) {
this.close();
ToastEvent.show({
ToastManager.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
@@ -511,13 +535,11 @@ export class VaultDialog extends Component {
}
if (this.state.note?.id) {
await db.vault.add(this.state.note.id);
if (this.state.note.id === editorController.current?.note?.id) {
eSendEvent(eClearEditor);
}
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.setState({
loading: false
});
ToastEvent.show({
ToastManager.show({
heading: "Note added to vault",
type: "success",
context: "global"
@@ -525,7 +547,7 @@ export class VaultDialog extends Component {
this.close();
} else {
eSendEvent("vaultUpdated");
ToastEvent.show({
ToastManager.show({
heading: "Vault created successfully",
type: "success",
context: "global"
@@ -538,11 +560,12 @@ export class VaultDialog extends Component {
db.vault
.remove(this.state.note.id, this.password)
.then(() => {
ToastEvent.show({
ToastManager.show({
heading: "Note permanently unlocked.",
type: "success",
context: "global"
});
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
this.close();
})
.catch((e) => {
@@ -553,7 +576,9 @@ export class VaultDialog extends Component {
_openInEditor(note) {
this.close();
InteractionManager.runAfterInteractions(async () => {
eSendEvent(eOnLoadNote, note);
eSendEvent(eOnLoadNote, {
item: note
});
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
@@ -562,7 +587,7 @@ export class VaultDialog extends Component {
async _copyNote(note) {
Clipboard.setString(await convertNoteToText(note));
ToastEvent.show({
ToastManager.show({
heading: "Note copied",
type: "success",
message: "Note has been copied to clipboard!",
@@ -594,7 +619,7 @@ export class VaultDialog extends Component {
visible: true
});
setTimeout(() => {
ToastEvent.show({
ToastManager.show({
heading: "Incorrect password",
type: "error",
context: "local"
@@ -609,13 +634,13 @@ export class VaultDialog extends Component {
try {
await BiometricService.resetCredentials();
eSendEvent("vaultUpdated");
ToastEvent.show({
ToastManager.show({
heading: "Biometric unlocking disabled!",
type: "success",
context: "global"
});
} catch (e) {
ToastEvent.show({
ToastManager.show({
heading: "Failed to disable Biometric unlocking.",
message: e.message,
type: "success",
@@ -667,7 +692,8 @@ export class VaultDialog extends Component {
<BaseDialog
onShow={async () => {
await sleep(100);
passInputRef.current?.focus();
this.passInputRef.current?.focus();
}}
statusBarTranslucent={false}
onRequestClose={this.close}
@@ -702,7 +728,7 @@ export class VaultDialog extends Component {
!this.state.revokeFingerprintAccess ? (
<>
<Input
fwdRef={passInputRef}
fwdRef={this.passInputRef}
editable={!loading}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
@@ -719,7 +745,7 @@ export class VaultDialog extends Component {
}
onSubmit={() => {
changePassword
? changePassInputRef.current?.focus()
? this.confirmPassRef.current?.focus()
: this.onPress;
}}
autoComplete="password"
@@ -771,7 +797,7 @@ export class VaultDialog extends Component {
<>
<Seperator half />
<Input
ref={changePassInputRef}
ref={this.confirmPassRef}
editable={!loading}
testID={notesnook.ids.dialogs.vault.changePwd}
autoCapitalize="none"
@@ -791,7 +817,7 @@ export class VaultDialog extends Component {
{!novault ? (
<View>
<Input
fwdRef={passInputRef}
fwdRef={this.passInputRef}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
onChangeText={(value) => {
@@ -802,13 +828,13 @@ export class VaultDialog extends Component {
returnKeyType="next"
secureTextEntry
onSubmit={() => {
confirmPassRef.current?.focus();
this.confirmPassRef.current?.focus();
}}
placeholder="Password"
/>
<Input
fwdRef={confirmPassRef}
fwdRef={this.confirmPassRef}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwdAlt}
secureTextEntry
@@ -865,10 +891,10 @@ export class VaultDialog extends Component {
title="Biometric unlocking"
iconColor={
this.state.biometricUnlock
? colors.selected.icon
? colors.selected.accent
: colors.primary.icon
}
type={this.state.biometricUnlock ? "transparent" : "selected"}
type={this.state.biometricUnlock ? "transparent" : "plain"}
/>
) : null}
</View>
@@ -891,7 +917,7 @@ export class VaultDialog extends Component {
? "Revoke"
: changePassword
? "Change"
: note.locked
: this.state.noteLocked
? deleteNote
? "Delete"
: share

View File

@@ -17,42 +17,73 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Platform, StyleSheet, View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { SearchBar } from "../../screens/search/search-bar";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import useNavigationStore, {
RouteName
} from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useThemeColors } from "@notesnook/theme";
import { eScrollEvent } from "../../utils/events";
import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus";
import { Title } from "./title";
const _Header = () => {
type HeaderRightButton = {
title: string;
onPress: () => void;
};
export const Header = ({
renderedInRoute,
onLeftMenuButtonPress,
title,
titleHiddenOnRender,
headerRightButtons,
id,
accentColor,
isBeta,
canGoBack,
onPressDefaultRightButton,
hasSearch,
onSearch
}: {
onLeftMenuButtonPress?: () => void;
renderedInRoute: RouteName;
id?: string;
title: string;
headerRightButtons?: HeaderRightButton[];
titleHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
canGoBack?: boolean;
onPressDefaultRightButton?: () => void;
hasSearch?: boolean;
onSearch?: () => void;
}) => {
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [hide, setHide] = useState(true);
const [borderHidden, setBorderHidden] = useState(true);
const selectionMode = useSelectionStore((state) => state.selectionMode);
const currentScreen = useNavigationStore(
(state) => state.currentScreen?.name
);
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
const onScroll = useCallback(
(data: { x: number; y: number }) => {
(data: { x: number; y: number; id?: string; route: string }) => {
if (data.route !== renderedInRoute || data.id !== id) return;
if (data.y > 150) {
if (!hide) return;
setHide(false);
if (!borderHidden) return;
setBorderHidden(false);
} else {
if (hide) return;
setHide(true);
if (borderHidden) return;
setBorderHidden(true);
}
},
[hide]
[borderHidden, id, renderedInRoute]
);
useEffect(() => {
@@ -60,9 +91,9 @@ const _Header = () => {
return () => {
eUnSubscribeEvent(eScrollEvent, onScroll);
};
}, [hide, onScroll]);
}, [borderHidden, onScroll]);
return selectionMode ? null : (
return selectionMode && isFocused ? null : (
<>
<View
style={[
@@ -72,29 +103,42 @@ const _Header = () => {
backgroundColor: colors.primary.background,
overflow: "hidden",
borderBottomWidth: 1,
borderBottomColor: hide
borderBottomColor: borderHidden
? "transparent"
: colors.secondary.background,
justifyContent: "space-between"
}
]}
>
{currentScreen === "Search" ? (
<SearchBar />
) : (
<>
<View style={styles.leftBtnContainer}>
<LeftMenus />
<Title />
</View>
<RightMenus />
</>
)}
<>
<View style={styles.leftBtnContainer}>
<LeftMenus
canGoBack={canGoBack}
onLeftButtonPress={onLeftMenuButtonPress}
/>
<Title
isHiddenOnRender={titleHiddenOnRender}
renderedInRoute={renderedInRoute}
id={id}
accentColor={accentColor}
title={title}
isBeta={isBeta}
/>
</View>
<RightMenus
renderedInRoute={renderedInRoute}
id={id}
headerRightButtons={headerRightButtons}
onPressDefaultRightButton={onPressDefaultRightButton}
search={hasSearch}
onSearch={onSearch}
/>
</>
</View>
</>
);
};
export const Header = React.memo(_Header, () => true);
const styles = StyleSheet.create({
container: {

View File

@@ -17,23 +17,29 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { notesnook } from "../../../e2e/test.ids";
import { DDS } from "../../services/device-detection";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeColors } from "@notesnook/theme";
import { tabBarRef } from "../../utils/global-refs";
import { IconButton } from "../ui/icon-button";
export const LeftMenus = () => {
export const LeftMenus = ({
canGoBack,
onLeftButtonPress
}: {
canGoBack?: boolean;
onLeftButtonPress?: () => void;
}) => {
const { colors } = useThemeColors();
const deviceMode = useSettingStore((state) => state.deviceMode);
const canGoBack = useNavigationStore((state) => state.canGoBack);
const isTablet = deviceMode === "tablet";
const onLeftButtonPress = () => {
const _onLeftButtonPress = () => {
if (onLeftButtonPress) return onLeftButtonPress();
if (!canGoBack) {
if (tabBarRef.current?.isDrawerOpen()) {
Navigation.closeDrawer();
@@ -48,27 +54,23 @@ export const LeftMenus = () => {
return isTablet && !canGoBack ? null : (
<IconButton
testID={notesnook.ids.default.header.buttons.left}
customStyle={{
style={{
justifyContent: "center",
alignItems: "center",
height: 40,
width: 40,
borderRadius: 100,
marginLeft: -5,
marginRight: DDS.isLargeTablet() ? 10 : 7
}}
left={40}
top={40}
right={DDS.isLargeTablet() ? 10 : 10}
onPress={onLeftButtonPress}
onPress={_onLeftButtonPress}
onLongPress={() => {
Navigation.popToTop();
}}
name={canGoBack ? "arrow-left" : "menu"}
color={colors.primary.paragraph}
iconStyle={{
marginLeft: canGoBack ? -5 : 0
}}
/>
);
};

View File

@@ -20,55 +20,56 @@ 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 { notesnook } from "../../../e2e/test.ids";
import Navigation from "../../services/navigation";
import SearchService from "../../services/search";
import useNavigationStore from "../../stores/use-navigation-store";
import {
HeaderRightButton,
RouteName
} from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
export const RightMenus = () => {
export const RightMenus = ({
headerRightButtons,
renderedInRoute,
id,
onPressDefaultRightButton,
search,
onSearch
}: {
headerRightButtons?: HeaderRightButton[];
renderedInRoute: RouteName;
id?: string;
onPressDefaultRightButton?: () => void;
search?: boolean;
onSearch?: () => void;
}) => {
const { colors } = useThemeColors();
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const deviceMode = useSettingStore((state) => state.deviceMode);
const buttons = useNavigationStore((state) => state.headerRightButtons);
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
const buttonAction = useNavigationStore((state) => state.buttonAction);
const menuRef = useRef<{
show: () => void;
hide: () => void;
}>();
const menuRef = useRef<Menu>(null);
return (
<View style={styles.rightBtnContainer}>
{!currentScreen.startsWith("Settings") ? (
{search ? (
<IconButton
onPress={async () => {
SearchService.prepareSearch();
Navigation.navigate(
{
name: "Search"
},
{}
);
}}
onPress={onSearch}
testID="icon-search"
name="magnify"
color={colors.primary.paragraph}
customStyle={styles.rightBtn}
style={styles.rightBtn}
/>
) : null}
{deviceMode !== "mobile" ? (
<Button
onPress={buttonAction}
onPress={onPressDefaultRightButton}
testID={notesnook.ids.default.addBtn}
icon={currentScreen === "Trash" ? "delete" : "plus"}
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
iconSize={SIZE.xl}
type="shade"
hitSlop={{
@@ -89,7 +90,7 @@ export const RightMenus = () => {
/>
) : null}
{buttons && buttons.length > 0 ? (
{headerRightButtons && headerRightButtons.length > 0 ? (
<Menu
ref={menuRef}
animationDuration={200}
@@ -107,18 +108,18 @@ export const RightMenus = () => {
}}
name="dots-vertical"
color={colors.primary.paragraph}
customStyle={styles.rightBtn}
style={styles.rightBtn}
/>
}
>
{buttons.map((item) => (
{headerRightButtons.map((item) => (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
}}
type="gray"
type="plain"
buttonType={{
text: contextMenuColors.primary.paragraph
}}

View File

@@ -20,103 +20,76 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native";
import { db } from "../../common/database";
import Notebook from "../../screens/notebook";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import Tag from "../ui/tag";
import Heading from "../ui/typography/heading";
const titleState: { [name: string]: boolean } = {};
export const Title = () => {
export const Title = ({
title,
isHiddenOnRender,
accentColor,
isBeta,
renderedInRoute,
id
}: {
title: string;
isHiddenOnRender?: boolean;
accentColor?: string;
isBeta?: boolean;
renderedInRoute: string;
id?: string;
}) => {
const { colors } = useThemeColors();
const currentScreen = useNavigationStore((state) => state.currentScreen);
const isNotebook = currentScreen.name === "Notebook";
const isTopic = currentScreen?.name === "TopicNotes";
const [hide, setHide] = useState(
isNotebook
? typeof titleState[currentScreen.id as string] === "boolean"
? titleState[currentScreen.id as string]
: true
: false
);
const isHidden = titleState[currentScreen.id as string];
const notebook =
isTopic && currentScreen.notebookId
? db.notebooks?.notebook(currentScreen.notebookId)?.data
: null;
const title = currentScreen.title;
const isTag = currentScreen?.name === "TaggedNotes";
const [visible, setVisible] = useState(isHiddenOnRender);
const isTag = title.startsWith("#");
const onScroll = useCallback(
(data: { x: number; y: number }) => {
if (currentScreen.name !== "Notebook") {
setHide(false);
return;
}
(data: { x: number; y: number; id?: string; route: string }) => {
if (data.route !== "Notebook") return;
if (data.route !== renderedInRoute || data.id !== id) return;
if (data.y > 150) {
if (!hide) return;
titleState[currentScreen.id as string] = false;
setHide(false);
if (!visible) return;
setVisible(false);
} else {
if (hide) return;
titleState[currentScreen.id as string] = true;
setHide(true);
if (visible) return;
setVisible(true);
}
},
[currentScreen.id, currentScreen.name, hide]
[id, renderedInRoute, visible]
);
useEffect(() => {
if (currentScreen.name === "Notebook") {
const value =
typeof titleState[currentScreen.id as string] === "boolean"
? titleState[currentScreen.id as string]
: true;
setHide(value);
} else {
setHide(titleState[currentScreen.id as string]);
}
}, [currentScreen.id, currentScreen.name]);
useEffect(() => {
eSubscribeEvent(eScrollEvent, onScroll);
return () => {
eUnSubscribeEvent(eScrollEvent, onScroll);
};
}, [hide, onScroll]);
}, [visible, onScroll]);
function navigateToNotebook() {
if (!isTopic) return;
Notebook.navigate(notebook, true);
}
return (
<>
{!hide && !isHidden ? (
{!visible ? (
<Heading
onPress={navigateToNotebook}
numberOfLines={1}
size={SIZE.lg}
style={{
flexWrap: "wrap",
marginTop: Platform.OS === "ios" ? -1 : 0
}}
color={currentScreen.color || colors.primary.heading}
color={accentColor || colors.primary.heading}
>
{isTag ? (
<Heading size={SIZE.xl} color={colors.primary.accent}>
#
</Heading>
) : null}
{title}{" "}
{isTag ? title.slice(1) : title}{" "}
<Tag
visible={currentScreen.beta}
visible={isBeta}
text="BETA"
style={{
backgroundColor: "transparent"

View File

@@ -32,28 +32,27 @@ import {
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
const ImagePreview = () => {
const { colors } = useThemeColors("dialog");
const [visible, setVisible] = useState(false);
const [image, setImage] = useState("");
const [image, setImage] = useState<string>();
const [loading, setLoading] = useState(false);
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
return () => {
eUnSubscribeEvent("ImagePreview", open);
};
}, []);
const open = async (image) => {
const open = async (image: ImageAttributes) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
let hash = image.hash;
if (!hash && dataurl.toObject(image.src)) {
if (!hash && image.src && dataurl.toObject(image.src)) {
const data = dataurl.toObject(image.src);
if (!data) return;
hash = await Sodium.hashFile({
@@ -63,6 +62,7 @@ const ImagePreview = () => {
});
}
if (!hash) return;
//@ts-ignore // FIX ME
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
@@ -74,7 +74,7 @@ const ImagePreview = () => {
};
const close = () => {
setImage(null);
setImage(undefined);
setVisible(false);
};
@@ -88,7 +88,7 @@ const ImagePreview = () => {
backgroundColor: "black"
}}
>
{loading ? (
{loading || !image ? (
<View
style={{
flex: 1,

View File

@@ -36,6 +36,10 @@ import { SIZE } from "../../utils/size";
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";
const Intro = ({ navigation }) => {
const { colors } = useThemeColors();
@@ -193,9 +197,15 @@ const Intro = ({ navigation }) => {
<Button
width={250}
onPress={async () => {
navigation.navigate("AppLock", {
welcome: true
});
eSendEvent(eOpenLoginDialog, AuthMode.welcomeSignup);
setTimeout(() => {
SettingsService.set({
introCompleted: true
});
Navigation.replace("Notes", {
canGoBack: false
});
}, 1000);
}}
style={{
paddingHorizontal: 24,

View File

@@ -77,14 +77,9 @@ export const WelcomeNotice = () => {
SettingsService.set({
introCompleted: true
});
Navigation.replace(
{
name: "Notes"
},
{
canGoBack: false
}
);
Navigation.replace("Notes", {
canGoBack: false
});
}, 1000);
}}
style={{
@@ -106,19 +101,14 @@ export const WelcomeNotice = () => {
SettingsService.set({
introCompleted: true
});
Navigation.replace(
{
name: "Notes"
},
{
canGoBack: false
}
);
Navigation.replace("Notes", {
canGoBack: false
});
await sleep(1000);
eSendEvent(eCloseLoading);
}}
iconSize={20}
type="gray"
type="plain"
iconPosition="right"
icon="chevron-right"
height={25}

View File

@@ -17,57 +17,39 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { useMessageStore } from "../../../stores/use-message-store";
import { ColorValues } from "../../../utils/colors";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { Announcement } from "../../announcements/announcement";
import { Card } from "../../list/card";
import Paragraph from "../../ui/typography/paragraph";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { SIZE } from "../../../utils/size";
import { useSelectionStore } from "../../../stores/use-selection-store";
export type ListHeaderProps = {
noAnnouncement?: boolean;
color?: string;
messageCard?: boolean;
screen?: string;
shouldShow?: boolean;
};
export const Header = React.memo(
({
type,
messageCard = true,
color,
shouldShow = false,
noAnnouncement,
warning
}) => {
screen
}: ListHeaderProps) => {
const { colors } = useThemeColors();
const announcements = useMessageStore((state) => state.announcements);
const selectionMode = useSelectionStore((state) => state.selectionMode);
return selectionMode ? null : (
<>
{warning ? (
<View
style={{
padding: 12,
backgroundColor: colors.error.background,
width: "95%",
alignSelf: "center",
borderRadius: 5,
flexDirection: "row",
alignItems: "center"
}}
>
<Icon
name="sync-alert"
size={SIZE.md}
color={colors.error.icon}
f
/>
<Paragraph style={{ marginLeft: 5 }} color={colors.error.icon}>
{warning.title}
</Paragraph>
</View>
) : announcements.length !== 0 && !noAnnouncement ? (
{announcements.length !== 0 && !noAnnouncement ? (
<Announcement color={color || colors.primary.accent} />
) : type === "search" ? null : !shouldShow ? (
) : (screen as any) === "Search" ? null : !shouldShow ? (
<View
style={{
marginBottom: 5,
@@ -78,11 +60,7 @@ export const Header = React.memo(
}}
>
{messageCard ? (
<Card
color={
ColorValues[color?.toLowerCase()] || colors.primary.accent
}
/>
<Card color={color || colors.primary.accent} />
) : null}
</View>
) : null}

View File

@@ -17,27 +17,33 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useRef, useState } from "react";
import React from "react";
import { View } from "react-native";
import { Notebook } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import { useMenuStore } from "../../../stores/use-menu-store";
import { ToastEvent } from "../../../services/event-manager";
import { getTotalNotes } from "@notesnook/common";
import React, { useState } from "react";
import { View } from "react-native";
import { db } from "../../../common/database";
import { ToastManager } from "../../../services/event-manager";
import { useMenuStore } from "../../../stores/use-menu-store";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
export const NotebookHeader = ({ notebook, onEditNotebook }) => {
export const NotebookHeader = ({
notebook,
onEditNotebook,
totalNotes = 0
}: {
notebook: Notebook;
onEditNotebook: () => void;
totalNotes: number;
}) => {
const { colors } = useThemeColors();
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
db.shortcuts.exists(notebook.id)
);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const totalNotes = getTotalNotes(notebook);
const shortcutRef = useRef();
const onPinNotebook = async () => {
try {
@@ -50,7 +56,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
type: "notebook"
}
});
ToastEvent.show({
ToastManager.show({
heading: "Shortcut created",
type: "success"
});
@@ -76,7 +82,7 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
}}
>
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{new Date(notebook.dateEdited).toLocaleString()}
{getFormattedDate(notebook.dateModified, "date-time")}
</Paragraph>
<View
style={{
@@ -103,13 +109,12 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
name={isPinnedToMenu ? "link-variant-off" : "link-variant"}
onPress={onPinNotebook}
tooltipText={"Create shortcut in side menu"}
fwdRef={shortcutRef}
customStyle={{
style={{
marginRight: 15,
width: 40,
height: 40
}}
type={isPinnedToMenu ? "grayBg" : "grayBg"}
type={isPinnedToMenu ? "secondary" : "secondary"}
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
@@ -118,9 +123,9 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
onPress={onEditNotebook}
tooltipText="Edit this notebook"
name="pencil"
type="grayBg"
type="secondary"
color={colors.primary.icon}
customStyle={{
style={{
width: 40,
height: 40
}}
@@ -138,15 +143,15 @@ export const NotebookHeader = ({ notebook, onEditNotebook }) => {
style={{
marginTop: 10,
fontStyle: "italic",
fontFamily: null
fontFamily: undefined
}}
size={SIZE.xs}
color={colors.secondary.paragraph}
>
{notebook.topics.length === 1
{/* {notebook.topics.length === 1
? "1 topic"
: `${notebook.topics.length} topics`}
,{" "}
,{" "} */}
{notebook && totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1

View File

@@ -17,39 +17,52 @@ 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 { GroupHeader, GroupOptions, ItemType } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import React from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
import { ColorValues } from "../../../utils/colors";
import { RouteName } from "../../../stores/use-navigation-store";
import { ColorValues, getContainerBorder } from "../../../utils/colors";
import { GROUP } from "../../../utils/constants";
import { eOpenJumpToDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import Sort from "../../sheets/sort";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
export const SectionHeader = React.memo(
function SectionHeader({ item, index, type, color, screen, groupOptions }) {
type SectionHeaderProps = {
item: GroupHeader;
index: number;
dataType: ItemType;
color?: string;
screen?: RouteName;
groupOptions: GroupOptions;
onOpenJumpToDialog: () => void;
};
export const SectionHeader = React.memo<
React.FunctionComponent<SectionHeaderProps>
>(
function SectionHeader({
item,
index,
dataType,
color,
screen,
groupOptions,
onOpenJumpToDialog
}: SectionHeaderProps) {
const { colors } = useThemeColors();
const { fontScale } = useWindowDimensions();
let groupBy = Object.keys(GROUP).find(
(key) => GROUP[key] === groupOptions.groupBy
(key) => GROUP[key as keyof typeof GROUP] === groupOptions.groupBy
);
const jumpToRef = useRef();
const sortRef = useRef();
const compactModeRef = useRef();
const notebooksListMode = useSettingStore(
(state) => state.settings.notebooksListMode
const isCompactModeEnabled = useIsCompactModeEnabled(
dataType as "note" | "notebook"
);
const notesListMode = useSettingStore(
(state) => state.settings.notesListMode
);
const listMode = type === "notebooks" ? notebooksListMode : notesListMode;
groupBy = !groupBy
? "Default"
@@ -67,14 +80,14 @@ export const SectionHeader = React.memo(
backgroundColor: colors.secondary.background,
alignSelf: "center",
borderRadius: 5,
marginVertical: 5
marginVertical: 5,
...getContainerBorder(colors.secondary.background, 0.8)
}}
>
<TouchableOpacity
onPress={() => {
eSendEvent(eOpenJumpToDialog, type);
onOpenJumpToDialog();
}}
ref={jumpToRef}
activeOpacity={0.9}
hitSlop={{ top: 10, left: 10, right: 30, bottom: 15 }}
style={{
@@ -83,7 +96,10 @@ export const SectionHeader = React.memo(
}}
>
<Heading
color={ColorValues[color?.toLowerCase()] || colors.primary.accent}
color={
ColorValues[color?.toLowerCase() as keyof typeof ColorValues] ||
colors.primary.accent
}
size={SIZE.sm}
style={{
minWidth: 60,
@@ -101,17 +117,16 @@ export const SectionHeader = React.memo(
alignItems: "center"
}}
>
{index === 0 && (
{index === 0 ? (
<>
<Button
onPress={() => {
console.log("Opening Sort sheet", screen, dataType);
presentSheet({
component: <Sort screen={screen} type={type} />
component: <Sort screen={screen} type={dataType} />
});
}}
hidden={screen === "Reminders"}
tooltipText="Change sorting of items in list"
fwdRef={sortRef}
title={groupBy}
icon={
groupOptions.sortDirection === "asc"
@@ -124,49 +139,57 @@ export const SectionHeader = React.memo(
paddingHorizontal: 0,
backgroundColor: "transparent",
marginRight:
type === "notes" || type === "home" || type === "notebooks"
dataType === "note" ||
screen === "Notes" ||
dataType === "notebook"
? 10
: 0
}}
type="gray"
type="plain"
iconPosition="right"
/>
<IconButton
customStyle={{
style={{
width: 25,
height: 25
}}
hidden={
type !== "notes" && type !== "notebooks" && type !== "home"
dataType !== "note" &&
dataType !== "notebook" &&
screen !== "Notes"
}
testID="icon-compact-mode"
tooltipText={
listMode == "compact"
isCompactModeEnabled
? "Switch to normal mode"
: "Switch to compact mode"
}
fwdRef={compactModeRef}
color={colors.secondary.icon}
name={listMode == "compact" ? "view-list" : "view-list-outline"}
name={isCompactModeEnabled ? "view-list" : "view-list-outline"}
onPress={() => {
let settings = {};
settings[
type !== "notebooks" ? "notesListMode" : "notebooksListMode"
] = listMode === "normal" ? "compact" : "normal";
SettingsService.set(settings);
SettingsService.set({
[dataType !== "notebook"
? "notesListMode"
: "notebooksListMode"]: !isCompactModeEnabled
? "compact"
: "normal"
});
}}
size={SIZE.lg - 2}
/>
</>
)}
) : null}
</View>
</View>
);
},
(prev, next) => {
if (prev.item.title !== next.item.title) return false;
if (prev.groupOptions?.groupBy !== next.groupOptions.groupBy) return false;
if (prev.groupOptions?.sortDirection !== next.groupOptions.sortDirection)
return false;
if (prev.groupOptions?.sortBy !== next.groupOptions.sortBy) return false;
return true;
}

View File

@@ -17,21 +17,28 @@ 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 { getUpcomingReminder } from "@notesnook/core/dist/collections/reminders";
import { decode, EntityLevel } from "entities";
import {
BaseTrashItem,
Color,
Note,
Reminder,
TrashItem
} from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import { EntityLevel, decode } from "entities";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import Notebook from "../../../screens/notebook";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import NotebookScreen from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import useNavigationStore from "../../../stores/use-navigation-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useThemeColors } from "@notesnook/theme";
import { ColorValues } from "../../../utils/colors";
import { SIZE } from "../../../utils/size";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import { Properties } from "../../properties";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
@@ -39,81 +46,43 @@ import { ReminderTime } from "../../ui/reminder-time";
import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useEditorStore } from "../../../stores/use-editor-store";
function navigateToTag(item) {
const tag = db.tags.tag(item.id);
if (!tag) return;
TaggedNotes.navigate(tag, true);
}
const showActionSheet = (item) => {
Properties.present(item);
type NoteItemProps = {
item: Note | BaseTrashItem<Note>;
index: number;
tags?: TagsWithDateEdited;
notebooks?: NotebooksWithDateEdited;
color?: Color;
reminder?: Reminder;
attachmentsCount: number;
date: number;
isTrash?: boolean;
noOpen?: boolean;
locked?: boolean;
};
function getNotebook(item) {
const isTrash = item.type === "trash";
const currentId = useNavigationStore.getState().currentScreen.id;
if (isTrash) return [];
const items = [];
const notebooks = db.relations.to(item, "notebook") || [];
for (let notebook of notebooks) {
if (items.length > 1) break;
if (notebook.id === currentId) continue;
items.push(notebook);
}
if (item.notebooks) {
for (let nb of item.notebooks) {
if (items.length > 1) break;
const notebook = db.notebooks?.notebook(nb.id)?.data;
if (!notebook) continue;
for (let topicId of nb.topics) {
if (items.length > 1) break;
if (topicId === currentId) continue;
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) continue;
items.push(topic);
}
}
}
return items;
}
function getTags(item) {
const noteTags = item.tags?.slice(0, 3) || [];
const tags = [];
for (const tagName of noteTags) {
const tag = db.tags.tag(tagName);
if (!tag) continue;
tags.push(tag);
}
return tags;
}
const NoteItem = ({
item,
isTrash,
dateBy = "dateCreated",
date,
color,
notebooks,
reminder,
tags,
attachmentsCount,
locked,
noOpen = false
}) => {
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
}: NoteItemProps) => {
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(item);
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type
);
const _update = useRelationStore((state) => state.updater);
// eslint-disable-next-line react-hooks/exhaustive-deps
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
const reminders = db.relations.from(item, "reminder");
const reminder = getUpcomingReminder(reminders);
const noteColor = ColorValues[item.color?.toLowerCase()];
const tags = getTags(item);
const primaryColors =
currentEditingNote === item.id ? colors.selected : colors.primary;
const primaryColors = isEditingNote ? colors.selected : colors.primary;
return (
<>
@@ -134,44 +103,43 @@ const NoteItem = ({
flexWrap: "wrap"
}}
>
{notebooks?.map((item) => (
<Button
title={
item.title.length > 25
? item.title.slice(0, 25) + "..."
: item.title
}
tooltipText={item.title}
key={item.id}
height={25}
icon={item.type === "topic" ? "bookmark" : "book-outline"}
type="grayBg"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
textStyle={{
marginRight: 0
}}
style={{
borderRadius: 5,
marginRight: 5,
borderWidth: 0.5,
borderColor: primaryColors.border,
paddingHorizontal: 6,
marginBottom: 5
}}
onPress={() => {
if (item.type === "topic") {
TopicNotes.navigate(item, true);
} else {
Notebook.navigate(item);
{notebooks?.items
?.filter(
(item) =>
item.id !== useNavigationStore.getState().focusedRouteId
)
.map((item) => (
<Button
title={
item.title.length > 25
? item.title.slice(0, 25) + "..."
: item.title
}
}}
/>
))}
tooltipText={item.title}
key={item.id}
height={25}
icon="book-outline"
type="secondary"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
textStyle={{
marginRight: 0
}}
style={{
borderRadius: 5,
marginRight: 5,
paddingHorizontal: 6,
marginBottom: 5
}}
onPress={() => {
NotebookScreen.navigate(item, true);
}}
/>
))}
<ReminderTime
reminder={reminder}
color={noteColor}
color={color?.colorCode}
onPress={() => {
Properties.present(reminder);
}}
@@ -185,26 +153,22 @@ const NoteItem = ({
{compactMode ? (
<Paragraph
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
}
style={{
flexWrap: "wrap"
}}
color={color?.colorCode || primaryColors.heading}
size={SIZE.sm}
style={{
paddingRight: 10
}}
>
{item.title}
</Paragraph>
) : (
<Heading
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
}
style={{
flexWrap: "wrap"
}}
color={color?.colorCode || primaryColors.heading}
size={SIZE.md}
style={{
paddingRight: 10
}}
>
{item.title}
</Heading>
@@ -253,13 +217,11 @@ const NoteItem = ({
color: colors.secondary.paragraph,
marginRight: 6
}}
time={item[dateBy]}
updateFrequency={
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
}
time={date}
updateFrequency={Date.now() - date < 60000 ? 2000 : 60000}
/>
{attachmentCount > 0 ? (
{attachmentsCount > 0 ? (
<View
style={{
flexDirection: "row",
@@ -276,7 +238,7 @@ const NoteItem = ({
color={colors.secondary.paragraph}
size={SIZE.xs}
>
{attachmentCount}
{attachmentsCount}
</Paragraph>
</View>
) : null}
@@ -289,14 +251,11 @@ const NoteItem = ({
style={{
marginRight: 6
}}
color={
ColorValues[item.color?.toLowerCase()] ||
primaryColors.accent
}
color={color?.colorCode || primaryColors.accent}
/>
) : null}
{item.locked ? (
{locked ? (
<Icon
name="lock"
testID="note-locked-icon"
@@ -321,13 +280,13 @@ const NoteItem = ({
) : null}
{!isTrash && !compactMode && tags
? tags.map((item) =>
? tags.items?.map((item) =>
item.id ? (
<Button
title={"#" + item.alias}
title={"#" + item.title}
key={item.id}
height={23}
type="gray"
type="plain"
textStyle={{
textDecorationLine: "underline"
}}
@@ -338,9 +297,9 @@ const NoteItem = ({
paddingHorizontal: 6,
marginRight: 4,
zIndex: 10,
maxWidth: tags.length > 1 ? 130 : null
maxWidth: tags.items?.length > 1 ? 130 : null
}}
onPress={() => navigateToTag(item)}
onPress={() => TaggedNotes.navigate(item, true)}
/>
) : null
)
@@ -368,7 +327,8 @@ const NoteItem = ({
marginRight: 6
}}
>
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
{(item as TrashItem).itemType[0].toUpperCase() +
(item as TrashItem).itemType.slice(1)}
</Paragraph>
</>
)}
@@ -394,7 +354,7 @@ const NoteItem = ({
/>
) : null}
{item.locked ? (
{locked ? (
<Icon
name="lock"
testID="note-locked-icon"
@@ -424,8 +384,8 @@ const NoteItem = ({
color: colors.secondary.paragraph,
marginRight: 6
}}
time={item[dateBy]}
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
time={date}
updateFrequency={Date.now() - date < 60000 ? 2000 : 60000}
/>
</>
) : null}
@@ -435,8 +395,8 @@ const NoteItem = ({
color={primaryColors.paragraph}
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}
customStyle={{
onPress={() => !noOpen && Properties.present(item)}
style={{
justifyContent: "center",
height: 35,
width: 35,

View File

@@ -1,135 +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 NoteItem from ".";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import {
eSendEvent,
hideSheet,
openVault,
presentSheet
} from "../../../services/event-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { presentDialog } from "../../dialog/functions";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
const present = () =>
presentDialog({
title: "Note not synced",
negativeText: "Ok",
paragraph: "Please sync again to open this note for editing"
});
export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
let _note = item;
if (isSheet) hideSheet();
if (!isTrash) {
_note = db.notes.note(item.id).data;
if (!db.notes.note(item.id)?.synced()) {
present();
return;
}
} else {
if (!db.trash.synced(item.id)) {
present();
return;
}
}
const { selectedItemsList, selectionMode, clearSelection } =
useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode) {
setSelectedItem && setSelectedItem(_note);
return;
} else {
clearSelection();
}
if (_note.conflicted) {
eSendEvent(eShowMergeDialog, _note);
return;
}
if (_note.locked) {
openVault({
item: _note,
novault: true,
locked: true,
goToEditor: true,
title: "Open note",
description: "Unlock note to open it in editor."
});
return;
}
if (isTrash) {
const content = await db.content.get(item.contentId);
presentSheet({
component: (
<NotePreview note={item} content={{ type: "tiptap", data: content }} />
)
});
} else {
useEditorStore.getState().setReadonly(_note?.readonly);
eSendEvent(eOnLoadNote, _note);
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
}
};
export const NoteWrapper = React.memo(
function NoteWrapper({ item, index, dateBy, isSheet }) {
const isTrash = item.type === "trash";
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
return (
<SelectionWrapper
index={index}
height={100}
testID={notesnook.ids.note.get(index)}
onPress={() => openNote(item, isTrash, setSelectedItem, isSheet)}
isSheet={isSheet}
item={item}
>
<NoteItem item={item} dateBy={dateBy} isTrash={isTrash} />
</SelectionWrapper>
);
},
(prev, next) => {
if (prev.dateBy !== next.dateBy) {
return false;
}
if (prev.item?.dateEdited !== next.item?.dateEdited) {
return false;
}
if (prev.item !== next.item) {
return false;
}
return true;
}
);

View File

@@ -0,0 +1,151 @@
/*
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 { BaseTrashItem, Color, Note, Reminder } from "@notesnook/core";
import React from "react";
import NoteItem from ".";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import {
eSendEvent,
hideSheet,
presentSheet
} from "../../../services/event-manager";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
export const openNote = async (
item: Note,
isTrash?: boolean,
isSheet?: boolean
) => {
let note: Note = item;
if (isSheet) hideSheet();
if (!isTrash) {
note = (await db.notes.note(item.id)) as Note;
}
if (useSelectionStore.getState().selectionMode === item.type) {
const {
selectedItemsList,
selectionMode,
clearSelection,
setSelectedItem
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
} else {
clearSelection();
}
return;
}
if (note.conflicted) {
eSendEvent(eShowMergeDialog, note);
return;
}
if (isTrash) {
if (!note.contentId) return;
const content = await db.content.get(note.contentId as string);
presentSheet({
component: <NotePreview note={item} content={content} />
});
} else {
eSendEvent(eOnLoadNote, {
item: note
});
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
}
};
type NoteWrapperProps = {
item: Note | BaseTrashItem<Note>;
index: number;
tags?: TagsWithDateEdited;
notebooks?: NotebooksWithDateEdited;
color?: Color;
reminder?: Reminder;
attachmentsCount: number;
date: number;
isRenderedInActionSheet: boolean;
locked?: boolean;
};
export const NoteWrapper = React.memo<
React.FunctionComponent<NoteWrapperProps>
>(
function NoteWrapper({
item,
index,
isRenderedInActionSheet,
...restProps
}: NoteWrapperProps) {
const isTrash = item.type === "trash";
return (
<SelectionWrapper
testID={notesnook.ids.note.get(index)}
onPress={() => openNote(item as Note, isTrash, isRenderedInActionSheet)}
isSheet={isRenderedInActionSheet}
item={item}
color={restProps.color?.colorCode}
>
<NoteItem {...restProps} item={item} index={index} isTrash={isTrash} />
</SelectionWrapper>
);
},
(prev, next) => {
if (prev.date !== next.date) {
return false;
}
if (
prev.tags?.dateEdited !== next.tags?.dateEdited ||
prev.tags?.items?.length !== next.tags?.items?.length
)
return false;
if (
prev.notebooks?.dateEdited !== next.notebooks?.dateEdited ||
prev.notebooks?.items?.length !== next.notebooks?.items?.length
)
return false;
if (prev.color !== next.color) return false;
if (prev.reminder?.id !== next.reminder?.id) return false;
if (prev.attachmentsCount !== next.attachmentsCount) return false;
if (prev.item?.dateModified !== next.item?.dateModified) {
return false;
}
if (prev.item?.id !== next.item?.id) return false;
return true;
}
);

View File

@@ -17,41 +17,38 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFormattedDate } from "@notesnook/common";
import { BaseTrashItem, Notebook, TrashItem } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeColors } from "@notesnook/theme";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
const showActionSheet = (item) => {
Properties.present(item);
};
const navigateToTopic = (topic) => {
if (useSelectionStore.getState().selectedItemsList.length > 0) return;
TopicNotes.navigate(topic, true);
type NotebookItemProps = {
item: Notebook | BaseTrashItem<Notebook>;
totalNotes: number;
date: number;
index: number;
isTrash: boolean;
};
export const NotebookItem = ({
item,
isTopic = false,
isTrash,
dateBy,
date,
totalNotes
}) => {
}: NotebookItemProps) => {
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(item);
const topics = item.topics?.slice(0, 3) || [];
const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type
);
return (
<>
@@ -83,7 +80,7 @@ export const NotebookItem = ({
</Heading>
)}
{isTopic || !item.description || compactMode ? null : (
{!item.description || compactMode ? null : (
<Paragraph
size={SIZE.sm}
numberOfLines={2}
@@ -95,42 +92,6 @@ export const NotebookItem = ({
</Paragraph>
)}
{isTopic || compactMode ? null : (
<View
style={{
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap"
}}
>
{topics.map((topic) => (
<Button
title={topic.title}
key={topic.id}
height={null}
textStyle={{
marginRight: 0
}}
type="grayBg"
fontSize={SIZE.xs}
icon="bookmark-outline"
iconSize={SIZE.sm}
style={{
borderRadius: 5,
maxWidth: 120,
borderWidth: 0.5,
paddingVertical: 2.5,
borderColor: colors.primary.border,
paddingHorizontal: 6,
marginVertical: 5,
marginRight: 5
}}
onPress={() => navigateToTopic(topic)}
/>
))}
</View>
)}
{!compactMode ? (
<View
style={{
@@ -152,7 +113,9 @@ export const NotebookItem = ({
}}
>
{"Deleted on " +
new Date(item.dateDeleted).toISOString().slice(0, 10)}
new Date((item as TrashItem).dateDeleted)
.toISOString()
.slice(0, 10)}
</Paragraph>
<Paragraph
color={colors.primary.accent}
@@ -162,7 +125,8 @@ export const NotebookItem = ({
marginRight: 6
}}
>
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
{(item as TrashItem).itemType[0].toUpperCase() +
(item as TrashItem).itemType.slice(1)}
</Paragraph>
</>
) : (
@@ -173,7 +137,7 @@ export const NotebookItem = ({
marginRight: 6
}}
>
{getFormattedDate(item[dateBy], "date")}
{getFormattedDate(date, "date")}
</Paragraph>
)}
<Paragraph
@@ -233,8 +197,8 @@ export const NotebookItem = ({
name="dots-horizontal"
testID={notesnook.ids.notebook.menu}
size={SIZE.xl}
onPress={() => showActionSheet(item)}
customStyle={{
onPress={() => Properties.present(item)}
style={{
justifyContent: "center",
height: 35,
width: 35,

View File

@@ -17,41 +17,24 @@ 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 { BaseTrashItem, Notebook } from "@notesnook/core";
import React from "react";
import { NotebookItem } from ".";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import { ToastEvent } from "../../../services/event-manager";
import { db } from "../../../common/database";
import NotebookScreen from "../../../screens/notebook";
import { ToastManager } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { db } from "../../../common/database";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper from "../selection-wrapper";
const navigateToNotebook = (item, canGoBack) => {
if (!item) return;
Navigation.navigate(
{
title: item.title,
name: "Notebook",
id: item.id,
type: "notebook"
},
{
title: item.title,
item: item,
canGoBack
}
);
};
export const openNotebookTopic = (item) => {
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
const isTrash = item.type === "trash";
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode) {
setSelectedItem(item);
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
@@ -59,24 +42,24 @@ export const openNotebookTopic = (item) => {
if (isTrash) {
presentDialog({
title: `Restore ${item.itemType}`,
paragraph: `Restore or delete ${item.itemType} forever`,
title: `Restore notebook`,
paragraph: `Restore or delete notebook forever`,
positiveText: "Restore",
negativeText: "Delete",
positivePress: async () => {
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
useSelectionStore.getState().setSelectionMode(undefined);
ToastManager.show({
heading: "Restore successful",
type: "success"
});
},
onClose: async () => {
await db.trash.delete(item.id);
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode(undefined);
ToastManager.show({
heading: "Permanently deleted items",
type: "success",
context: "local"
@@ -85,29 +68,30 @@ export const openNotebookTopic = (item) => {
});
return;
}
if (item.type === "topic") {
TopicNotes.navigate(item, true);
} else {
navigateToNotebook(item, true);
}
NotebookScreen.navigate(item, true);
};
type NotebookWrapperProps = {
item: Notebook | BaseTrashItem<Notebook>;
totalNotes: number;
date: number;
index: number;
};
export const NotebookWrapper = React.memo(
function NotebookWrapper({ item, index, dateBy, totalNotes }) {
function NotebookWrapper({
item,
index,
date,
totalNotes
}: NotebookWrapperProps) {
const isTrash = item.type === "trash";
return (
<SelectionWrapper
pinned={item.pinned}
index={index}
onPress={() => openNotebookTopic(item)}
height={item.type === "topic" ? 80 : 110}
item={item}
>
<SelectionWrapper onPress={() => openNotebook(item)} item={item}>
<NotebookItem
isTopic={item.type === "topic"}
item={item}
dateBy={dateBy}
date={date}
index={index}
isTrash={isTrash}
totalNotes={totalNotes}
@@ -117,17 +101,9 @@ export const NotebookWrapper = React.memo(
},
(prev, next) => {
if (prev.totalNotes !== next.totalNotes) return false;
if (prev.item.title !== next.item.title) return false;
if (prev.dateBy !== next.dateBy) {
return false;
}
if (prev.item?.dateEdited !== next.item?.dateEdited) {
return false;
}
if (JSON.stringify(prev.item) !== JSON.stringify(next.item)) {
return false;
}
if (prev.date !== next.date) return false;
if (prev.item?.dateModified !== next.item?.dateModified) return false;
if (prev.item?.id !== next.item?.id) return false;
return true;
}

View File

@@ -16,20 +16,21 @@ 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 { Reminder } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import type { Reminder } from "../../../services/notifications";
import { useThemeColors } from "@notesnook/theme";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import ReminderSheet from "../../sheets/reminder";
import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper from "../selection-wrapper";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ReminderSheet from "../../sheets/reminder";
import { ReminderTime } from "../../ui/reminder-time";
const ReminderItem = React.memo(
({
@@ -43,21 +44,28 @@ const ReminderItem = React.memo(
}) => {
const { colors } = useThemeColors();
const openReminder = () => {
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
ReminderSheet.present(item, undefined, isSheet);
};
return (
<SelectionWrapper
//@ts-ignore
index={index}
height={100}
onPress={openReminder}
item={item}
isSheet={isSheet}
>
<SelectionWrapper onPress={openReminder} item={item} isSheet={isSheet}>
<View
style={{
flexShrink: 1,
opacity: item.disabled ? 0.5 : 1
opacity: item.disabled ? 0.5 : 1,
maxWidth: "80%",
flexGrow: 1
}}
>
<Heading
@@ -131,7 +139,11 @@ const ReminderItem = React.memo(
height: 30
}}
>
<Icon name="reload" size={SIZE.md} color={colors.primary.accent} />
<Icon
name="reload"
size={SIZE.md}
color={colors.primary.accent}
/>
<Paragraph
size={SIZE.xs}
color={colors.secondary.paragraph}
@@ -164,7 +176,7 @@ const ReminderItem = React.memo(
name="dots-horizontal"
size={SIZE.xl}
onPress={() => Properties.present(item, [], isSheet)}
customStyle={{
style={{
justifyContent: "center",
height: 35,
width: 35,

View File

@@ -17,21 +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 { Item } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import useIsSelected from "../../../hooks/use-selected";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
export const Filler = ({ item }) => {
export const Filler = ({ item, color }: { item: Item; color?: string }) => {
const { colors } = useThemeColors();
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
const isEditingNote = useTabStore(
(state) =>
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
);
const [selected] = useIsSelected(item);
return currentEditingNote === item.id || selected ? (
return isEditingNote || selected ? (
<View
style={{
position: "absolute",
@@ -39,12 +41,11 @@ export const Filler = ({ item }) => {
height: "150%",
backgroundColor: colors.selected.background,
borderLeftWidth: 5,
borderLeftColor:
currentEditingNote === item.id
? item.color
? colors.static[item.color]
: colors.selected.accent
: "transparent"
borderLeftColor: isEditingNote
? color
? color
: colors.selected.accent
: "transparent"
}}
collapsable={false}
/>

View File

@@ -18,38 +18,49 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import React, { PropsWithChildren, useRef } from "react";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { PressableButton } from "../../ui/pressable";
import { Pressable } from "../../ui/pressable";
import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
import { Item, TrashItem } from "@notesnook/core";
type SelectionWrapperProps = PropsWithChildren<{
item: Item;
onPress: () => void;
testID?: string;
isSheet?: boolean;
color?: string;
}>;
const SelectionWrapper = ({
children,
item,
background,
onPress,
testID,
isSheet
}) => {
isSheet,
children,
color
}: SelectionWrapperProps) => {
const itemId = useRef(item.id);
const { colors, isDark } = useThemeColors();
const compactMode = useIsCompactModeEnabled(item);
const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type
);
if (item.id !== itemId.current) {
itemId.current = item.id;
}
const onLongPress = () => {
if (!useSelectionStore.getState().selectionMode) {
useSelectionStore.getState().setSelectionMode(true);
if (useSelectionStore.getState().selectionMode !== item.type) {
useSelectionStore.getState().setSelectionMode(item.type);
}
useSelectionStore.getState().setSelectedItem(item);
useSelectionStore.getState().setSelectedItem(item.id);
};
return (
<PressableButton
<Pressable
customColor={isSheet ? colors.primary.hover : "transparent"}
testID={testID}
onLongPress={onLongPress}
@@ -57,7 +68,7 @@ const SelectionWrapper = ({
customSelectedColor={colors.primary.hover}
customAlpha={!isDark ? -0.02 : 0.02}
customOpacity={1}
customStyle={{
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
@@ -69,16 +80,10 @@ const SelectionWrapper = ({
marginBottom: isSheet ? 12 : undefined
}}
>
{item.type === "note" ? (
<Filler background={background} item={item} />
) : null}
<SelectionIcon
compactMode={compactMode}
item={item}
onLongPress={onLongPress}
/>
{item.type === "note" ? <Filler item={item} color={color} /> : null}
<SelectionIcon item={item} />
{children}
</PressableButton>
</Pressable>
);
};

View File

@@ -25,13 +25,16 @@ import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enab
import useIsSelected from "../../../hooks/use-selected";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Item, TrashItem } from "@notesnook/core";
export const SelectionIcon = ({ item }) => {
export const SelectionIcon = ({ item }: { item: Item }) => {
const { colors } = useThemeColors();
const selectionMode = useSelectionStore((state) => state.selectionMode);
const [selected] = useIsSelected(item);
const compactMode = useIsCompactModeEnabled(item);
const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type
);
return selectionMode ? (
<View

View File

@@ -17,44 +17,58 @@ 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 { Tag } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { useThemeColors } from "@notesnook/theme";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import { PressableButton } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper from "../selection-wrapper";
const TagItem = React.memo(
({ item, index }) => {
const { colors, isDark } = useThemeColors();
({
item,
index,
totalNotes
}: {
item: Tag;
index: number;
totalNotes: number;
}) => {
const { colors } = useThemeColors();
const onPress = () => {
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
TaggedNotes.navigate(item, true);
};
return (
<PressableButton
<SelectionWrapper
onPress={onPress}
selectedColor={colors.secondary.background}
item={item}
testID={notesnook.ids.tag.get(index)}
alpha={!isDark ? -0.02 : 0.02}
opacity={1}
customStyle={{
paddingHorizontal: 12,
flexDirection: "row",
paddingVertical: 12,
alignItems: "center",
width: "100%",
justifyContent: "space-between"
}}
>
<View
style={{
maxWidth: "92%"
flexGrow: 1,
flexShrink: 1
}}
>
<Heading size={SIZE.md}>
@@ -66,7 +80,7 @@ const TagItem = React.memo(
>
#
</Heading>
{item.alias}
{item.title}
</Heading>
<Paragraph
color={colors.secondary.paragraph}
@@ -75,10 +89,10 @@ const TagItem = React.memo(
marginTop: 5
}}
>
{item && item.noteIds.length && item.noteIds.length > 1
? item.noteIds.length + " notes"
: item.noteIds.length === 1
? item.noteIds.length + " note"
{totalNotes > 1
? totalNotes + " notes"
: totalNotes === 1
? totalNotes + " note"
: null}
</Paragraph>
</View>
@@ -91,7 +105,7 @@ const TagItem = React.memo(
Properties.present(item);
}}
testID={notesnook.ids.tag.menu}
customStyle={{
style={{
justifyContent: "center",
height: 35,
width: 35,
@@ -99,14 +113,11 @@ const TagItem = React.memo(
alignItems: "center"
}}
/>
</PressableButton>
</SelectionWrapper>
);
},
(prev, next) => {
if (prev.item?.dateEdited !== next.item?.dateEdited) {
return false;
}
if (JSON.stringify(prev.item) !== JSON.stringify(next.item)) {
if (prev.item?.dateModified !== next.item?.dateModified) {
return false;
}

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