Compare commits

...

289 Commits

Author SHA1 Message Date
ammarahm-ed
d0899c563e core: fix incorrect snooze time format 2023-06-16 10:21:58 +05:00
Abdullah Atta
5041e47902 core: fix require is not defined 2023-06-12 13:02:56 +05:00
Abdullah Atta
f3a03dc813 crypto: eagerly init sodium in dev mode 2023-06-12 12:57:39 +05:00
Abdullah Atta
942c9556f6 web: fix editor theme not changing with app theme 2023-06-12 12:00:47 +05:00
Abdullah Atta
5374f2b56c web: migrate to vitejs from webpack (#2715) 2023-06-12 11:49:37 +05:00
Abdullah Atta
3f128da55d docs: update readme 2023-06-10 16:03:09 +05:00
Abdullah Atta
0aa689ca03 desktop: add readme & other docs 2023-06-10 15:54:17 +05:00
Abdullah Atta
93e1ae5830 ci: set extraMetadata in package.json 2023-06-10 15:54:17 +05:00
Abdullah Atta
ef5aebdb3b ci: fix electron-builder command 2023-06-10 15:54:17 +05:00
Abdullah Atta
c152796767 ci: use yarn electron-builder on windows 2023-06-10 15:54:17 +05:00
Abdullah Atta
b75382f8b6 ci: change entrypoint before build 2023-06-10 15:54:17 +05:00
Abdullah Atta
80893a5011 ci: use electron-builder directly from ./node_modules/.bin directory 2023-06-10 15:54:17 +05:00
Abdullah Atta
0f9ac0e068 desktop: add dmg-license as optional dependency 2023-06-10 15:54:17 +05:00
Abdullah Atta
46e4ac127c ci: do not bootstrap after adding dmg-license 2023-06-10 15:54:17 +05:00
Abdullah Atta
3da4ced75b desktop: add build script & use nx to run build commands 2023-06-10 15:54:17 +05:00
Abdullah Atta
3f79ae6403 desktop: rename build script to release 2023-06-10 15:54:17 +05:00
Abdullah Atta
2d78b05e8d desktop: remove electron-builder install-app-deps from post-install 2023-06-10 15:54:17 +05:00
Abdullah Atta
c7c4ae4615 desktop: fix post-install command 2023-06-10 15:54:17 +05:00
Abdullah Atta
ee2cbea0c4 ci: add ./ before local action path 2023-06-10 15:54:17 +05:00
Abdullah Atta
782fc575a9 ci: improve node cache by using a common action 2023-06-10 15:54:17 +05:00
Abdullah Atta
151a527444 desktop: improve build scripts 2023-06-10 15:54:17 +05:00
Abdullah Atta
6ab0911970 desktop: finalize new desktop core 2023-06-10 15:54:17 +05:00
Abdullah Atta
28927e705b desktop: overhaul desktop core and migrate to typescript 2023-06-10 15:54:17 +05:00
ammarahm-ed
2a31f70b4a mobile: fix compactMode indicators show always 2023-06-10 12:25:48 +05:00
Abdullah Atta
0596bd9e84 desktop: update electron to v25.1.0 2023-06-08 18:20:47 +05:00
ammarahm-ed
3e95960b45 mobile: release v2.5.2 2023-06-08 16:21:50 +05:00
ammarahm-ed
131ecfee89 mobile: improved compactness of notebook compact mode 2023-06-08 16:21:50 +05:00
Abdullah Atta
dfccd640b0 web: bump version to 2.5.2 2023-06-08 16:03:33 +05:00
Abdullah Atta
14d27bb277 editor: remove duplicate ordered-list methods 2023-06-08 15:59:48 +05:00
Abdullah Atta
dd470297c3 editor: do not prevent all clicks 2023-06-08 15:59:48 +05:00
Abdullah Atta
1e690664f1 editor: remove selection persistence 2023-06-08 15:59:48 +05:00
ammarahm-ed
d68eaa7a41 mobile: increase compactness of compact mode 2023-06-08 15:55:06 +05:00
ammarahm-ed
791119fcbc mobile: add notes for $timestamp$ template 2023-06-08 15:22:53 +05:00
Abdullah Atta
87b82865b2 web: list $timestamp$ in supported title templates 2023-06-08 15:16:24 +05:00
Abdullah Atta
ad145c3499 core: remove YYYY-DD-MM date format 2023-06-08 15:16:24 +05:00
ammarahm-ed
5a228ce5fe mobile: allow changin primary 2fa 2023-06-08 15:06:22 +05:00
Abdullah Atta
8cb3cb6b19 web: allow changing primary 2fa method 2023-06-08 15:06:22 +05:00
Abdullah Atta
94cc3a6853 web: make 2fa recovery codes more readable 2023-06-08 12:02:14 +05:00
Abdullah Atta
163dfac5d7 core: add tests for title formatting 2023-06-08 11:57:21 +05:00
Abdullah Atta
efd0d6c2c3 core: add more date formats 2023-06-08 11:57:10 +05:00
Abdullah Atta
742dc56f3c core: add support for $timestamp$ in title 2023-06-08 11:56:59 +05:00
ammarahm-ed
19f2d8b0a7 mobile: do not resize auth modal on ios on kbd open 2023-06-07 21:00:53 +05:00
ammarahm-ed
8c9d98ab88 mobile: fix pass validation 2023-06-07 21:00:13 +05:00
ammarahm-ed
75ae5d9603 mobile: hide note headline in share ext 2023-06-06 18:20:34 +05:00
Ammar Ahmed
8a2453429d Merge pull request #2669 from streetwriters/fix/tag-name-manage-tags
mobile: fix aliased tag name not showing in manage
2023-06-06 18:20:12 +05:00
ammarahm-ed
b54ca08fc8 mobile: fix aliased tag name not showing in manage
tags sheet
2023-06-06 18:12:32 +05:00
ammarahm-ed
2be801ba3b mobile: fix oboarding ui on iphone 2023-06-06 12:35:49 +05:00
Abdullah Atta
f37cd3fe63 core: add new date formats 2023-06-06 11:58:59 +05:00
ammarahm-ed
1224e48bc8 core: fix comma added in default title 2023-06-06 10:42:26 +05:00
Abdullah Atta
780bebe141 web: fix crash on webkit (safari) 2023-06-05 20:55:41 +05:00
ammarahm-ed
f707b43efb mobile: release v2.5.1 2023-06-05 20:40:34 +05:00
Abdullah Atta
92e9cd740f web: fix crash on opening notebook context menu 2023-06-05 19:06:53 +05:00
Abdullah Atta
20bef5e70c web: fix crash when opening notebook context menu 2023-06-05 19:05:39 +05:00
ammarahm-ed
c9b45875a0 mobile: remove patch 2023-06-05 18:54:36 +05:00
Abdullah Atta
f180b79a3e web: bump version to 2.5.1 2023-06-05 18:40:56 +05:00
ammarahm-ed
438a3c2686 mobile: update deps 2023-06-05 18:33:52 +05:00
ammarahm-ed
64d23d3de0 mobile: show loading when updating marketing consent 2023-06-05 18:27:51 +05:00
ammarahm-ed
e251c6f71c mobile: add marketing consent settings 2023-06-05 18:21:07 +05:00
Abdullah Atta
40cb6ae8bf web: allow opting out of marketing emails 2023-06-05 18:21:07 +05:00
Abdullah Atta
06aaa7e2c2 web: clarify marketing consent on sign up screen 2023-06-05 18:21:07 +05:00
Abdullah Atta
ab29a01649 core: allow changing marketing emails consent settings 2023-06-05 18:21:07 +05:00
Abdullah Atta
49e7bdf202 editor: allow starting lists with arbitrary number 2023-06-05 18:04:13 +05:00
ammarahm-ed
2e0f9d8524 mobile: do not move login dialog on keyboard change 2023-06-05 16:40:33 +05:00
Ammar Ahmed
092b26be57 mobile: add support for image & file sharing (#2652)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-06-05 16:26:39 +05:00
Abdullah Atta
3525feffcf web: fix color not setting on icons 2023-06-05 16:21:40 +05:00
Abdullah Atta
6cb5a5e473 web: fix incorrect loading state of icons 2023-06-05 16:20:40 +05:00
Abdullah Atta
0992d4eeab editor: make image selectable in readonly mode 2023-06-05 16:02:50 +05:00
Abdullah Atta
828cd6be35 editor: disable selection persist in readonly mode 2023-06-05 16:02:50 +05:00
Abdullah Atta
1b1a962ec2 editor: minor refactor 2023-06-05 16:02:50 +05:00
Abdullah Atta
0a88502e59 editor: hide replace button in readonly mode 2023-06-05 16:02:50 +05:00
Muhammad Ali
50ff39845a editor: changed absolute to relative 2023-06-05 16:02:50 +05:00
Muhammad Ali
1cb34d0783 editor: added image-tools for readonly-mode 2023-06-05 16:02:50 +05:00
Abdullah Atta
bbb43bc8b4 web: replace date-time in test snapshot with xxx 2023-06-05 15:29:10 +05:00
ammarahm-ed
1521a7d120 mobile: formatted dates in editor 2023-06-05 15:29:10 +05:00
Abdullah Atta
3b6a2cb9de web: use editor date/time format from settings 2023-06-05 15:29:10 +05:00
Abdullah Atta
0deaed4ccd editor: make date-time format configurable 2023-06-05 15:29:10 +05:00
Abdullah Atta
8ebb58b70c core: fix reminder date time formatting 2023-06-05 15:29:10 +05:00
ammarahm-ed
f691d113d2 mobile: update wording 2023-06-05 15:29:10 +05:00
ammarahm-ed
a57eaf9be6 mobile: sync with core changes 2023-06-05 15:29:10 +05:00
ammarahm-ed
c5a264270c core: use only first 10 words of headline in title 2023-06-05 15:29:10 +05:00
Abdullah Atta
e75c62e6e1 core: use 2 digits for 12 hour time format 2023-06-05 15:29:10 +05:00
Abdullah Atta
6c3583c9e5 web: add support for setting default notebook/topic 2023-06-05 15:29:10 +05:00
Abdullah Atta
58ba453f57 web: use default note title from settings 2023-06-05 15:29:10 +05:00
Abdullah Atta
7f5d5fcb82 core: simplify default notebook object 2023-06-05 15:29:10 +05:00
Abdullah Atta
e218194402 web: add settings for default date, time & title formats 2023-06-05 15:29:10 +05:00
Abdullah Atta
c36f532bbb web: use date & time formats from settings 2023-06-05 15:29:10 +05:00
ammarahm-ed
b3b7bd44ef core: fix note count incorrect 2023-06-05 15:29:10 +05:00
ammarahm-ed
c85242a610 core: format time correctly 2023-06-05 15:29:10 +05:00
ammarahm-ed
58f7a206d9 mobile: add settings 2023-06-05 15:29:10 +05:00
Ammar Ahmed
25bf4fee82 mobile: redesign onboarding (#2633) 2023-06-05 15:13:19 +05:00
Abdullah Atta
c2ab5ded47 desktop: fix start as minimized on windows 2023-06-05 12:14:33 +05:00
Ammar Ahmed
ad6a0ebfab mobie: enable search in readonly mode (#2659) 2023-06-05 11:16:18 +05:00
ammarahm-ed
cb9e6578c1 mobile: downgrade react-native-bootsplash version 2023-06-01 21:16:17 +05:00
Abdullah Atta
17a1475ded editor: do not create 2 toolbar groups for static tools on mobile 2023-06-01 21:15:17 +05:00
Abdullah Atta
2cee5a4d89 editor: set preview attachment icon to eye 2023-06-01 21:15:17 +05:00
Abdullah Atta
337f88c373 editor: fix link popup font family 2023-06-01 21:15:17 +05:00
Abdullah Atta
53d5073690 editor: double click to preview image 2023-06-01 21:15:17 +05:00
Ramiro Pruis
9cc9660b48 editor: allow download & preview of attachments in readonly mode (#2621)
Signed-off-by: Ramiro Pruis<ramapruis@gmail.com>
2023-06-01 20:39:10 +05:00
ammarahm-ed
e6162b2580 mobile: fix crash on download all attachement 2023-05-31 11:31:51 +05:00
ammarahm-ed
abc14d2d1f mobile: set fdroid build to false 2023-05-30 00:17:39 +05:00
ammarahm-ed
fa11b5fabb mobile: update packages 2023-05-29 23:52:43 +05:00
ammarahm-ed
d904916863 mobile: release v2.5.0 2023-05-29 23:01:53 +05:00
Abdullah Atta
0cfc2e1609 web: bump version to 2.5.0 2023-05-29 19:14:53 +05:00
Abdullah Atta
3742e923c9 editor: fix crash due to attachment.mime being undefined 2023-05-29 13:12:28 +05:00
Abdullah Atta
792a254513 web: fix split pane sash styling 2023-05-29 12:09:50 +05:00
Abdullah Atta
17928ba182 web: disable loading announcements during tests 2023-05-27 16:50:24 +05:00
Abdullah Atta
2da0f164c6 web: fix list crash on shift+click when there's no selection 2023-05-27 16:47:54 +05:00
Abdullah Atta
4eabe79e7e web: fix attachment caching issues 2023-05-27 16:14:12 +05:00
Abdullah Atta
89dd8f5b6c web: minor ui fixes for pdf attachment preview 2023-05-27 16:14:12 +05:00
Abdullah Atta
b4d3662897 web: never delete image & pdf attachments 2023-05-27 16:14:12 +05:00
ammarahm-ed
675e1e2c6f mobile: do not cleanup documents from cache after download 2023-05-27 16:14:12 +05:00
ammarahm-ed
0f7f3bae7e mobile: save pdf snapshot 2023-05-27 16:14:12 +05:00
Abdullah Atta
c917d95a7b web: remove unnecessary css import 2023-05-27 16:14:12 +05:00
Abdullah Atta
3ae9001504 mobile: remove rn-fetch-blob patch 2023-05-27 16:14:12 +05:00
Abdullah Atta
cc97b8beed global: update package-lock.json 2023-05-27 16:14:12 +05:00
Abdullah Atta
954bdebcef web: remove @react-pdf-viewer/default-layout 2023-05-27 16:14:12 +05:00
Abdullah Atta
04b62f4e53 misc: update package-lock.json 2023-05-27 16:14:12 +05:00
Abdullah Atta
a2dcb67b36 web: add support for previewing pdf attachments 2023-05-27 16:14:12 +05:00
Abdullah Atta
cc9141c981 editor: enable preview attachment button on desktop 2023-05-27 16:14:12 +05:00
Abdullah Atta
85484b1762 fs: add support for getting actual file size 2023-05-27 16:14:12 +05:00
Abdullah Atta
1a0c94896a config: add new "fs" scope for commits 2023-05-27 16:14:12 +05:00
ammarahm-ed
068a99463d mobile: fix loading 2023-05-27 16:14:12 +05:00
ammarahm-ed
b413315777 mobile: add pdf attachment previews 2023-05-27 16:14:12 +05:00
ammarahm-ed
7227513783 editor: enable attachment previews for files 2023-05-27 16:14:12 +05:00
Abdullah Atta
083e4e1150 web: add basic support for attachment previews 2023-05-27 16:14:12 +05:00
Abdullah Atta
09098410a5 editor: enable previewing attachments on desktop/web 2023-05-27 16:14:12 +05:00
Abdullah Atta
2c9f5df530 web: fix font family in unlock note view 2023-05-25 16:15:01 +05:00
Abdullah Atta
3ad346362b web: fix notebooks in move note dialog not getting checked sometimes 2023-05-25 16:15:01 +05:00
ammarahm-ed
e176b1bf76 mobile: reset premium status 2023-05-25 15:55:14 +05:00
ammarahm-ed
b368232598 mobile: fix app hangs on adding >3 notebooks on iOS 2023-05-25 15:54:54 +05:00
Abdullah Atta
bcd27e10c1 web: add support for search inside notebook 2023-05-25 15:52:58 +05:00
Abdullah Atta
af9b9b7083 web: fix notebook item date font family 2023-05-25 15:44:09 +05:00
ammarahm-ed
ea1565048a mobile: tag multiple notes from manage tags sheet 2023-05-25 15:41:43 +05:00
Muhammad Ali
59b081704b web: add support for setting tags in bulk (#2328)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-25 14:10:32 +05:00
Muhammad Ali
2761222b36 web: add support for printing notes (#2551)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-25 12:19:21 +05:00
Muhammad Ali
571f15b556 web: close deleted note on sync (#2580)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-25 12:14:49 +05:00
Muhammad Ali
283efa6e41 editor: prevent table column size reset in readonly mode (#2586) 2023-05-25 12:11:57 +05:00
Abdullah Atta
b3b1b7470e web: do not show save file dialog if download is canceled 2023-05-25 12:02:50 +05:00
Abdullah Atta
a9e59b17dc web: use fixed size columns in attachment manager 2023-05-25 12:02:50 +05:00
Abdullah Atta
22d946b677 web: fix app lag when downloading attachment 2023-05-25 12:02:50 +05:00
ammarahm-ed
6a748c3f98 mobile: fix download all attachments progress 2023-05-25 10:18:21 +05:00
Abdullah Atta
520296106a editor: bump version to 1.5.0 2023-05-25 09:48:22 +05:00
Abdullah Atta
3b727e6f64 editor: update tiptap packages to v2.0.3 2023-05-25 09:47:20 +05:00
ammarahm-ed
b77711a53d mobile: use isImage check from core 2023-05-23 12:52:50 +05:00
ammarahm-ed
aebb2d414b mobile: allow attaching images from file manager 2023-05-23 12:52:50 +05:00
ammarahm-ed
e0e505371c mobile: update download attachments 2023-05-23 12:52:50 +05:00
ammarahm-ed
b9040a9358 mobile: show error on file check failed 2023-05-23 12:52:50 +05:00
ammarahm-ed
68f10ce308 core: add isAudio function 2023-05-23 12:52:50 +05:00
Abdullah Atta
8a475f654f web: improve attachments manager ui/ux 2023-05-23 12:52:50 +05:00
ammarahm-ed
1043b7df55 mobile: improved attachments manager 2023-05-23 12:52:50 +05:00
ammarahm-ed
8383e09243 mobile: show currently downloading file 2023-05-23 12:52:50 +05:00
ammarahm-ed
df58a6cfc4 mobile: fix recheck attachments 2023-05-23 12:52:50 +05:00
ammarahm-ed
a9df577478 mobile: update deps 2023-05-23 12:52:50 +05:00
ammarahm-ed
e021d93893 mobile: download mutliple attachments 2023-05-23 12:52:50 +05:00
Abdullah Atta
24e13f488e web: fully move to using web streams everywhere 2023-05-23 12:52:50 +05:00
Muhammad Ali
6f88b70937 web: add support for downloading all attachments as zip 2023-05-23 12:52:50 +05:00
ammarahm-ed
a90f957aa4 mobile: always check editor is ready on enter foreground 2023-05-23 08:08:10 +05:00
ammarahm-ed
748953b3eb mobile: don't lock app on showing app review 2023-05-23 08:07:27 +05:00
Muhammad Ali
874c892986 editor: make cell properties work with header cells (#2506) 2023-05-23 07:17:29 +05:00
Muhammad Ali
ab678fa3a9 core: fix duplication of notes that are directly inside a notebook (#2552)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-23 06:40:07 +05:00
Muhammad Ali
c2351ab2d3 editor: add support for link markdown syntax (#2576) 2023-05-23 06:36:34 +05:00
Muhammad Ali
3fd3e02256 web: fix wrong date font in notebook item (#2560)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-23 06:20:12 +05:00
Muhammad Ali
d2fd26abd5 editor: fix position of outline list bullet/arrow (#2555) 2023-05-20 08:03:39 +05:00
ammarahm-ed
dd84b9ca2f mobile: add fdroid changelog 2023-05-18 20:06:37 +05:00
Abdullah Atta
269f083dce clipper: bump version to 0.2.1 2023-05-18 15:14:25 +05:00
Abdullah Atta
e18867d3f1 clipper: request scripting permission 2023-05-18 15:13:44 +05:00
Abdullah Atta
e221882f8e clipper: change version to 0.2.0 2023-05-17 11:35:52 +05:00
Abdullah Atta
153ee4bce3 clipper: load content scripts on demand to reduce host permissions 2023-05-17 11:35:52 +05:00
ammarahm-ed
a2ecdab61c mobile: release 2.4.17 2023-05-17 11:24:29 +05:00
Ammar Ahmed
eb089e6211 Merge pull request #2543 from streetwriters/feat/in-app-review
mobile: add in-app-review flow
2023-05-17 11:18:22 +05:00
ammarahm-ed
1981c3a2f8 mobile: add in-app-review flow 2023-05-17 11:14:49 +05:00
Abdullah Atta
68b3ebe318 clipper: get rid of <all_urls> permission 2023-05-17 10:11:47 +05:00
ammarahm-ed
8a0b6264d2 mobile: fix pro features locked for pro users 2023-05-17 10:09:37 +05:00
Abdullah Atta
239174c3e7 web: bump version to 2.4.11 2023-05-17 09:29:16 +05:00
Abdullah Atta
db1503d828 clipper: remove contextMenus permission 2023-05-16 18:05:20 +05:00
Abdullah Atta
7b1cbba9e7 clipper: bump version to 0.2 2023-05-16 17:39:21 +05:00
Abdullah Atta
2c3c193629 clipper: default to simplified mode if user is no longer pro 2023-05-16 17:35:59 +05:00
Abdullah Atta
899e56e9cd clipper(core): escape % & ' during css stringify 2023-05-16 17:35:59 +05:00
Abdullah Atta
9c9582ae12 clipper: improve error messages 2023-05-16 17:35:59 +05:00
Abdullah Atta
b918667b63 clipper: adjust icon size in inline tag 2023-05-16 17:35:59 +05:00
Abdullah Atta
878c901e06 clipper: add support for assigning multiple notebooks 2023-05-16 17:35:59 +05:00
Muhammad Ali
fdd3897a60 web: close tag dropdown on tab press (#2508) 2023-05-16 17:32:00 +05:00
ammarahm-ed
c958657b01 mobile: release 2.4.16 2023-05-16 12:11:43 +05:00
Abdullah Atta
e7175f48a0 clipper: do not exclude localhost tabs in production mode 2023-05-13 17:51:10 +05:00
Abdullah Atta
4e1862d653 clipper: improve connection reliability
this adds support for working with multiple Notesnook tabs
even though it is not currently recommended that you
open multiple Notesnook tabs
2023-05-13 17:51:10 +05:00
Abdullah Atta
1d5ac37222 clipper: always clip tab from current active window 2023-05-13 17:50:53 +05:00
Abdullah Atta
32d027fff6 clipper(core): don't clip images in simplified mode 2023-05-13 17:50:41 +05:00
ammarahm-ed
9434993373 mobile: remove usesCleartextTraffic in release builds 2023-05-08 18:30:36 +05:00
Abdullah Atta
2bd0f1a304 web: allow users to view their billing history 2023-05-08 15:25:20 +05:00
Abdullah Atta
7f873f240e web: allow requesting refunds from settings 2023-05-08 15:25:20 +05:00
Abdullah Atta
a0229729b7 web: use sandbox checkout during development 2023-05-08 15:25:20 +05:00
Abdullah Atta
dd368a4170 web: show success on checkout completion 2023-05-08 15:25:20 +05:00
ammarahm-ed
6d3e985c9d mobile: fix failing build 2023-05-08 09:27:55 +05:00
ammarahm-ed
15858db21b mobile: sync quick notes only when online 2023-05-08 08:19:17 +05:00
Ammar Ahmed
b69a94a96f mobile: fix premium toast is not shown when using premium features on mobile (#2504) 2023-05-08 08:18:38 +05:00
Muhammad Ali
73b965eb7c web: close properties pane when note is switched (#2486) 2023-05-06 16:16:01 +05:00
Abdullah Atta
5738f48360 core: expire session if key is not found 2023-05-06 15:27:58 +05:00
Muhammad Ali
de74b8c2cd editor: position search box correctly in readonly mode (#2478) 2023-05-03 13:22:25 +05:00
Muhammad Ali
2d07d79057 web: fix issue where notes directly in a notebook could not be unlinked (#2466)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-05-02 12:49:24 +05:00
ammarahm-ed
5f6a38c8b1 mobile: release 2.4.15 2023-04-29 09:07:46 +05:00
Muhammad Ali
3496e6bfa9 editor: increased hit Area 2023-04-29 08:33:20 +05:00
ammarahm-ed
fc5b62ba61 mobile: remove editor flicker on app launch 2023-04-29 08:29:48 +05:00
ammarahm-ed
9d0a206240 mobile: fix editor not rendered when enter foreground 2023-04-29 08:29:48 +05:00
ammarahm-ed
ee6bbca18c mobile: only call verify biometrics once on focus 2023-04-29 05:47:05 +05:00
ammarahm-ed
e6d5472514 mobile: fix rerender crash 2023-04-29 05:46:06 +05:00
Abdullah Atta
1e5f13087b web: try to reconnect sync on window focus 2023-04-28 10:39:59 +05:00
Abdullah Atta
abb89ecbc3 web: bump version to 2.4.10 2023-04-27 15:15:46 +05:00
Abdullah Atta
aa168bec5e desktop: revert "desktop: remove custom protocol interception for https requests" 2023-04-27 15:14:30 +05:00
Abdullah Atta
f9116b22db web: bump version to 2.4.9 2023-04-27 13:16:01 +05:00
Abdullah Atta
5b957f5f0d web: fix Can't resolve './network.worker' error 2023-04-27 13:08:13 +05:00
Abdullah Atta
a578aae3dc web: wait for internet before running sync on device wake 2023-04-27 13:08:13 +05:00
Abdullah Atta
43e5d2fa2d editor: fix table properties tooltip text 2023-04-27 13:08:03 +05:00
Abdullah Atta
b58d034716 theme: use outline instead of boxShadow for input border 2023-04-27 12:59:26 +05:00
Abdullah Atta
3b77d79997 web: prevent pro users from resubscribing 2023-04-27 12:59:26 +05:00
Abdullah Atta
ef4c9af7dc web: rename setters from onXYZ to xyz 2023-04-27 12:59:26 +05:00
ammarahm-ed
8ba3cf172e mobile: release v2.4.14 2023-04-27 12:49:02 +05:00
ammarahm-ed
3e446ce9dd mobile: fix crash on get search items 2023-04-27 11:27:48 +05:00
ammarahm-ed
394aa98713 mobile: remove usage of lazy imports 2023-04-27 10:59:04 +05:00
Muhammad Ali
a2ac882a05 desktop: remove custom protocol interception for https requests (#2426) 2023-04-27 10:13:37 +05:00
ammarahm-ed
cae36487f9 mobile: fix notebook not updated on note 2023-04-27 09:38:44 +05:00
ammarahm-ed
fade5eddbf mobile: replace newline with paragraph in generated html 2023-04-27 08:32:09 +05:00
ammarahm-ed
66b2ffce31 mobile: fix webpack config 2023-04-27 08:32:09 +05:00
ammarahm-ed
13cd2d4daa mobile: refactor 2023-04-27 08:18:25 +05:00
ammarahm-ed
8442faeeb1 mobile: close editor on navigate to topic 2023-04-26 20:06:40 +05:00
Ammar Ahmed
6d18383b22 mobile: sync notes created from quick note/notification notes (#2428)
* mobile: sync quick notes

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

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

* mobile: ensure full db init when sync is required

---------

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

* mobile: fix db init in background

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

* mobile: improve db init in app/share extension

* mobile: cleanup

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

Signed-off-by: Nick Thomas <73144044+NickThomas20@users.noreply.github.com>
2023-04-14 06:42:57 +05:00
ammarahm-ed
9744679550 mobile: add support for setting default font-size & font-family 2023-04-08 01:53:46 +05:00
ammarahm-ed
9cdc502ce5 web: fix font family not applied when set on editor 2023-04-08 01:14:33 +05:00
Ammar Ahmed
300a104949 Merge branch 'master' into change-default-font
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-04-07 23:17:23 +05:00
Muhammad Ali
df94f31998 web: added state base to default font-family and size 2023-03-22 08:38:21 +05:00
alihamuh
aec1d601a3 web: added change default fontstyle feature in the settings 2023-01-18 13:23:39 +05:00
568 changed files with 41473 additions and 114889 deletions

View File

@@ -20,6 +20,7 @@ const SCOPES = [
"logger",
"theme",
"core",
"fs",
"clipper",
"config",
"ci",

View File

@@ -0,0 +1,27 @@
name: 'Setup node with cache action'
description: 'Setups node with cache'
runs:
using: "composite"
steps:
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
package-lock.json
apps/mobile/package-lock.json
apps/desktop/package-lock.json
apps/web/package-lock.json
extensions/web-clipper/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/sodium/package-lock.json
packages/clipper/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json

View File

@@ -11,21 +11,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@master
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2

View File

@@ -11,21 +11,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@master
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Use specific Java version for the builds
uses: joschi/setup-jdk@v2

View File

@@ -24,21 +24,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
uses: ./.github/actions/setup-node-with-cache
- name: Prepare environment
run: |

View File

@@ -28,21 +28,7 @@ jobs:
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
@@ -64,7 +50,7 @@ jobs:
- name: Collect app metadata
id: app_metadata
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop
run: |
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
@@ -91,6 +77,9 @@ jobs:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Setup notarization
run: |
mkdir -p ~/private_keys/
@@ -98,7 +87,7 @@ jobs:
- name: Collect app metadata
id: app_metadata
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop
run: |
echo ::set-output name=apple_app_id::$(cat package.json | jq -r .appAppleId)
echo ::set-output name=app_bundle_id::$(cat package.json | jq -r .build.appId)
@@ -115,26 +104,20 @@ jobs:
private-key-raw: ${{ secrets.api_key }}
platform: "MAC_OS"
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Download build
uses: actions/download-artifact@v3
with:
name: build
path: ./apps/web/desktop/build
path: ./apps/desktop/build
- name: Install packages
run: |
npm i
npm i dmg-license
working-directory: ./apps/web/desktop
npm i --ignore-scripts --prefer-offline --no-audit
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
- name: Install provisioning profile
run: echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 --decode > embedded.provisionprofile
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop
- name: Build app for Mac App Store
if: inputs.publish-apple && steps.appstore.outputs.app-version-latest != steps.app_metadata.outputs.app_version
@@ -142,9 +125,9 @@ jobs:
CSC_LINK: ${{ secrets.mac_certs }}
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
run: |
npm run build:mas
npx electron-builder --mac mas --universal -p never
working-directory: ./apps/web/desktop
npx nx run release --project @notesnook/desktop -- --variant=mas
npm run builder -- --mac mas --universal -p never
working-directory: ./apps/desktop
- name: Build zip and dmg
env:
@@ -155,13 +138,13 @@ jobs:
APPLE_API_KEY_ID: ${{ secrets.api_key_id }}
APPLE_API_KEY_ISSUER: ${{ secrets.api_key_issuer_id }}
run: |
npm run build
npx nx run release --project @notesnook/desktop
if [ ${{ inputs.publish-github }} == true ]; then
npx electron-builder --mac zip dmg --arm64 --x64 -p always
npm run builder -- --mac zip dmg --arm64 --x64 -p always
else
npx electron-builder --mac zip dmg --arm64 --x64 -p never
npm run builder -- --mac zip dmg --arm64 --x64 -p never
fi
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop
- name: Deploy to Testflight
if: inputs.publish-apple && steps.appstore.outputs.app-version-latest != steps.app_metadata.outputs.app_version
@@ -169,12 +152,12 @@ jobs:
API_KEY_ID: ${{ secrets.api_key_id }}
API_KEY_ISSUER_ID: ${{ secrets.api_key_issuer_id }}
run: |
package=$(find ./dist/mas-universal/notesnook*.pkg)
package=$(find ./output/mas-universal/notesnook*.pkg)
echo "Uploading ${package} using altool..."
xcrun altool --upload-package $package -t osx --apiKey $API_KEY_ID --apiIssuer $API_KEY_ISSUER_ID --apple-id ${{ steps.app_metadata.outputs.apple_app_id }} --bundle-id ${{ steps.app_metadata.outputs.app_bundle_id }} --bundle-short-version-string ${{ steps.app_metadata.outputs.app_version }} --bundle-version ${{ steps.app_metadata.outputs.bundle_version }}
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop
build-linux:
name: Build for Linux
@@ -185,6 +168,9 @@ jobs:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Setup Snapcraft Auth
if: inputs.publish-snap
run: echo "SNAPCRAFT_STORE_CREDENTIALS=${{ secrets.snapcraft_token }}" >> $GITHUB_ENV
@@ -192,48 +178,44 @@ jobs:
- name: Install Snapcraft
uses: samuelmeuli/action-snapcraft@v1
if: inputs.publish-snap
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Download build
uses: actions/download-artifact@v3
with:
name: build
path: ./apps/web/desktop/build
path: ./apps/desktop/build
- name: Install packages
run: npm i
working-directory: ./apps/web/desktop
run: |
npm i --ignore-scripts --prefer-offline --no-audit
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
- name: Build Electron wrapper
run: npm run build
working-directory: ./apps/web/desktop
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
- name: Build snap
if: inputs.publish-snap
run: |
npx electron-builder --linux snap:x64 -p never
working-directory: ./apps/web/desktop
npm run builder -- --linux snap:x64 -p never
working-directory: ./apps/desktop
- name: Build AppImage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
npm run builder -- --linux AppImage:x64 AppImage:arm64 -p always
else
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
npm run builder -- --linux AppImage:x64 AppImage:arm64 -p never
fi
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop
- name: Publish on Snapcraft
if: inputs.publish-snap
run: |
snapcraft upload --release=stable ./dist/notesnook_linux_amd64.snap
working-directory: ./apps/web/desktop
snapcraft upload --release=stable ./output/notesnook_linux_amd64.snap
working-directory: ./apps/desktop
build-windows:
name: Build for Windows
@@ -245,31 +227,30 @@ jobs:
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
uses: ./.github/actions/setup-node-with-cache
- name: Download build
uses: actions/download-artifact@v3
with:
name: build
path: ./apps/web/desktop/build
path: ./apps/desktop/build
- name: Install packages
run: npm i
working-directory: ./apps/web/desktop
run: |
npm i --ignore-scripts --prefer-offline --no-audit
npx lerna bootstrap --ignore=@notesnook/mobile -- --prefer-offline --no-audit
- name: Build Electron wrapper
run: npm run build
working-directory: ./apps/web/desktop
run: npx nx run release --project @notesnook/desktop
working-directory: ./apps/desktop
- name: Build app
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if ($${{ inputs.publish-github }} -eq $true) {
npx electron-builder --win --publish always
yarn electron-builder --win --publish always
} else {
npx electron-builder --win --publish never
yarn electron-builder --win --publish never
}
working-directory: ./apps/web/desktop
working-directory: ./apps/desktop

View File

@@ -25,21 +25,7 @@ jobs:
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |

View File

@@ -11,21 +11,7 @@ jobs:
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |

View File

@@ -25,21 +25,7 @@ jobs:
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
@@ -76,21 +62,7 @@ jobs:
path: ./apps/web/build
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
cache-dependency-path: |
apps/mobile/package-lock.json
apps/web/package-lock.json
packages/core/package-lock.json
packages/crypto/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json
packages/streamable-fs/package-lock.json
packages/theme/package-lock.json
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |

View File

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

View File

@@ -10,11 +10,11 @@
## Overview
Notesnook is a free (as in speech) & open source note taking app focused on user privacy & ease of use. To ensure zero knowledge principles, Notesnook encrypts everything on your device using `XChaCha20-Poly1305` & `Argon2`.
Notesnook is a free (as in speech) & open-source note-taking app focused on user privacy & ease of use. To ensure zero knowledge principles, Notesnook encrypts everything on your device using `XChaCha20-Poly1305` & `Argon2`.
Notesnook is our **proof** that privacy does _not_ (always) have to come at the cost of convenience. Our goal is to provide users peace of mind & 100% confidence that their notes are safe and secure. The decision to go fully open source is one of the most crucial steps towards that.
Notesnook is our **proof** that privacy does _not_ (always) have to come at the cost of convenience. We aim to provide users peace of mind & 100% confidence that their notes are safe and secure. The decision to go fully open source is one of the most crucial steps towards that.
This repository contains all the code required to build & use the Notesnook web, desktop & mobile clients. If you are looking for a full features list or screenshots, please check the [website](https://notesnook.com/).
This repository contains all the code required to build & use the Notesnook web, desktop & mobile clients. If you are looking for a full feature list or screenshots, please check the [website](https://notesnook.com/).
## Developer guide
@@ -25,25 +25,30 @@ Notesnook is built using the following technologies:
1. JavaScript/Typescript — this repo is in a hybrid state. A lot of the newer code is being written in Typescript & the old code is slowly being ported over.
2. React — the whole front-end across all platforms is built using React.
3. React Native — For mobile apps we are using React Native
4. NPM — listed here because we **don't** use Yarn or PNPM or XYZ across any of our projects.
5. Lerna & Nx — maintaining monorepos is hard but Nx makes it easier.
4. Electron — For desktop app
5. NPM — listed here because we **don't** use Yarn or PNPM or XYZ across any of our projects.
6. Lerna & Nx — maintaining monorepos is hard but Nx makes it easier.
> **Note: Each project in the monorepo contains its own architecture details which you can refer to.**
### Monorepo structure
| Name | Path | Description |
| -------------------------- | -------------------------------------------------- | ----------------------------------------------------------------- |
| `@notesnook/web` | [/apps/web](/apps/web) | Web/Desktop clients |
| `@notesnook/mobile` | [/apps/mobile](/apps/mobile) | Android/iOS clients |
| `@notesnook/core` | [/packages/core](/packages/core) | Shared core between all platforms |
| `@notesnook/crypto` | [/packages/crypto](/packages/crypto) | Cryptography library wrapper around libsodium |
| `@notesnook/editor` | [/packages/editor](/packages/editor) | Notesnook editor + all extensions |
| `@notesnook/editor-mobile` | [/packages/editor-mobile](/packages/editor-mobile) | A very thin wrapper around `@notesnook/editor` for mobile clients |
| `@notesnook/logger` | [/packages/logger](/packages/logger) | Simple & pluggable logger |
| `@notesnook/crypto-worker` | [/packages/crypto-worker](/packages/crypto-worker) | Helpers to use `@notesnook/crypto` from a Worker |
| `@notesnook/streamable-fs` | [/packages/streamable-fs](/packages/streamable-fs) | Streaming interface around an IndexedDB based file system |
| `@notesnook/theme` | [/packages/theme](/packages/theme) | The core theme used in web & desktop clients |
| Name | Path | Description |
| -------------------------- | -------------------------------------------------- | -------------------------------------------------------------------- |
| `@notesnook/web` | [/apps/web](/apps/web) | Web client |
| `@notesnook/desktop` | [/apps/desktop](/apps/desktop) | Desktop client |
| `@notesnook/mobile` | [/apps/mobile](/apps/mobile) | Android/iOS clients |
| `@notesnook/web-clipper` | [/extensions/web-clipper](/extensions/web-clipper) | Web clipper |
| `@notesnook/core` | [/packages/core](/packages/core) | Shared core between all platforms |
| `@notesnook/crypto` | [/packages/crypto](/packages/crypto) | Cryptography library wrapper around libsodium |
| `@notesnook/clipper` | [/packages/clipper](/packages/clipper) | Web clipper core handling everything related to actual page clipping |
| `@notesnook/editor` | [/packages/editor](/packages/editor) | Notesnook editor + all extensions |
| `@notesnook/editor-mobile` | [/packages/editor-mobile](/packages/editor-mobile) | A very thin wrapper around `@notesnook/editor` for mobile clients |
| `@notesnook/logger` | [/packages/logger](/packages/logger) | Simple & pluggable logger |
| `@notesnook/sodium` | [/packages/sodium](/packages/sodium) | Wrapper around libsodium to support Node.js & Browser |
| `@notesnook/crypto-worker` | [/packages/crypto-worker](/packages/crypto-worker) | Helpers to use `@notesnook/crypto` from a Worker |
| `@notesnook/streamable-fs` | [/packages/streamable-fs](/packages/streamable-fs) | Streaming interface around an IndexedDB based file system |
| `@notesnook/theme` | [/packages/theme](/packages/theme) | The core theme used in web & desktop clients |
### Contributing guidelines

4
apps/desktop/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
build
output
dist

58
apps/desktop/README.md Normal file
View File

@@ -0,0 +1,58 @@
<p align="center">
<img style="align:center;" src="/resources/screenshots/web.jpg" alt="Notesnook desktop screenshot" width="600" />
</p>
<h1 align="center">Notesnook Desktop</h1>
<h3 align="center">The desktop app is built using Electron & Typescript.</h3>
<p align="center">
<a href="https://notesnook.com/downloads">Downloads</a> | <a href="#developer-guide">Developer guide</a> | <a href="#build-instructions">How to build?</a>
</p>
## Getting started
## Build instructions
> **Before you start, it is recommended that you read [the contributing guidelines](/CONTRIBUTING.md).**
### Setting up the development environment
Requirements:
1. [Node.js](https://nodejs.org/en/download/)
2. [git](https://git-scm.com/downloads)
3. NPM (not yarn or pnpm)
Before you can do anything, you'll need to [install Node.js](https://nodejs.org/en/download/) v16 or later on your system.
Once you have completed the setup, the first step is to `clone` the monorepo:
```bash
git clone https://github.com/streetwriters/notesnook.git
# change directory
cd notesnook
```
Once you are inside the `./notesnook` directory, run the preparation step:
```bash
# this might take a while to complete
npm install
```
Now you can finally start the desktop app:
```bash
npm run start:desktop
```
## Developer guide
### The tech stack
We try to keep the stack as lean as possible:
1. Electron
2. tRPC: for cross-communication between the web & desktop parts
3. zod: for runtime typechecking
4. yargs: for CLI argument parsing

View File

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 520 B

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 353 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

52
apps/desktop/global.d.ts vendored Normal file
View File

@@ -0,0 +1,52 @@
/*
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/>.
*/
/* eslint-disable no-var */
import { BrowserWindow } from "electron";
import * as undici_types from "undici";
declare global {
export const {
fetch,
FormData,
Headers,
Request,
Response
}: typeof import("undici");
type FormData = undici_types.FormData;
type Headers = undici_types.Headers;
type HeadersInit = undici_types.HeadersInit;
type BodyInit = undici_types.BodyInit;
type Request = undici_types.Request;
type RequestInit = undici_types.RequestInit;
type RequestInfo = undici_types.RequestInfo;
type RequestMode = undici_types.RequestMode;
type RequestRedirect = undici_types.RequestRedirect;
type RequestCredentials = undici_types.RequestCredentials;
type RequestDestination = undici_types.RequestDestination;
type ReferrerPolicy = undici_types.ReferrerPolicy;
type Response = undici_types.Response;
type ResponseInit = undici_types.ResponseInit;
type ResponseType = undici_types.ResponseType;
var window: BrowserWindow | null;
var RELEASE: boolean;
var MAC_APP_STORE: boolean;
}

4879
apps/desktop/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,32 +2,47 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.4.7",
"version": "2.5.2",
"appAppleId": "1544027013",
"private": true,
"main": "./build/electron.js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"homepage": "https://notesnook.com/",
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "*",
"@trpc/client": "^10.29.1",
"@trpc/server": "^10.29.1",
"diary": "^0.3.1",
"electron-trpc": "^0.5.0",
"electron-updater": "^5.3.0",
"icojs": "^0.17.1",
"isomorphic-fetch": "^3.0.0",
"yargs": "^17.6.2"
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/node-fetch": "^2.6.2",
"electron": "^21.1.1",
"@electron/rebuild": "^3.2.13",
"@types/node": "^18.15.0",
"electron": "^24.4.0",
"electron-builder": "^23.6.0",
"electron-builder-notarize": "^1.5.0",
"electron-builder-notarize": "^1.5.1",
"electron-reloader": "^1.2.3",
"esbuild": "^0.15.11",
"patch-package": "^6.5.1"
"esbuild": "^0.17.19",
"undici": "^5.22.1"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
},
"scripts": {
"build": "esbuild ./electron.js ./preload.js --external:electron --external:fsevents --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
"build:mas": "esbuild ./electron.js ./preload.js --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
"pack": "rm -rf ./build && cp -r ../build ./ && npm run build && yarn electron-builder --linux AppImage:x64 AppImage:arm64",
"start": "turbowatch scripts/dev.ts",
"staging": "zx scripts/build.mjs --run",
"release": "zx 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: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",
"builder": "./node_modules/.bin/electron-builder",
"postinstall": "patch-package"
},
"author": {
@@ -44,8 +59,8 @@
"!*.chunk.js.map",
"!*.chunk.js.LICENSE.txt",
"build/",
"!build/screenshots",
"!node_modules"
"!build/screenshots${/*}",
"!node_modules${/*}"
],
"afterSign": "electron-builder-notarize",
"afterPack": "./scripts/removeLocales.js",
@@ -174,9 +189,12 @@
"extraResources": [
"./assets/**"
],
"extraMetadata": {
"main": "./build/electron.js"
},
"directories": {
"buildResources": "assets",
"output": "./dist/"
"output": "./output/"
},
"publish": [
{

View File

@@ -0,0 +1,72 @@
/*
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 path from "path";
import fs from "fs/promises";
import { $, argv, os } from "zx";
$.env = process.env;
const sodiumNativePrebuildPath = path.join(
`node_modules`,
`@notesnook`,
`crypto`,
`node_modules`,
`@notesnook`,
`sodium`,
`node_modules`,
`sodium-native`,
`prebuilds`,
`${os.platform()}-x64`
);
if (argv.rebuild) {
await fs.rm("./build/", { force: true, recursive: true });
await $`cd ../web/ && npm run build:desktop`;
await fs.cp(path.join("..", "web", "build"), "build", {
recursive: true,
force: true
});
}
if (argv.variant === "mas") {
await $`npm run bundle:mas`;
} else {
await $`npm run bundle`;
}
await $`tsc`;
await $`npm rebuild`;
await fs.cp(
sodiumNativePrebuildPath,
path.join("build", "prebuilds", `${process.platform}-x64`),
{
recursive: true,
force: true
}
);
if (argv.run) {
await $`npm run builder -- --linux AppImage:x64`;
await $`./output/notesnook_linux_x86_64.AppImage`;
}

117
apps/desktop/scripts/dev.ts Normal file
View File

@@ -0,0 +1,117 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import path from "path";
import fs from "fs/promises";
import { watch } from "turbowatch";
import { $, within } from "zx";
const sodiumNativePrebuildPath = path.join(
`node_modules`,
`@notesnook`,
`crypto`,
`node_modules`,
`@notesnook`,
`sodium`,
`node_modules`,
`sodium-native`,
`prebuilds`
);
let isServerRunning = false;
async function main() {
const { shutdown } = await watch({
project: path.join(__dirname, ".."),
cwd: path.join(__dirname, ".."),
triggers: [
{
expression: [
"allof",
["not", ["dirname", "node_modules"]],
["match", "*.ts", "basename"]
],
name: "dev",
// retry: { retries: 0 },
onTeardown: async () => {
await fs.rm("./build/", { force: true, recursive: true });
},
onChange: async ({ spawn: $, first, log }) => {
if (first) {
await fs.rm("./build/", { force: true, recursive: true });
}
await $`npm run build:electron`;
await $`tsc`;
if (first) {
await fs.cp(sodiumNativePrebuildPath, "build/prebuilds", {
recursive: true,
force: true
});
}
if (!isServerRunning) {
await spawnAndWaitUntil(
path.join(__dirname, "..", "..", "web"),
"npm run start:desktop",
(data) => data.includes("Compiled successfully!")
);
isServerRunning = true;
}
log("Starting desktop app!");
await $`npx electron ${path.join("build", "electron.js")}`;
}
}
]
});
// SIGINT is the signal sent when we press Ctrl+C
process.once("SIGINT", () => {
void shutdown();
});
}
main();
function spawnAndWaitUntil(
cwd: string,
cmd: string,
predicate: (data: string) => boolean
) {
return new Promise((resolve) => {
within(async () => {
$.env = process.env;
$.quote = (c) => c;
try {
const s = $`cd ${cwd} && ${cmd}`;
s.stdout.on("data", (data) => {
if (predicate(data)) resolve(undefined); //
});
await s;
} catch (e) {
//ignore
} finally {
isServerRunning = false;
}
});
});
}

View File

@@ -0,0 +1,58 @@
/*
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 { initTRPC } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { EventEmitter } from "events";
import TypedEventEmitter from "typed-emitter";
export type AppEvents = {
onCreateItem(name: "note" | "notebook" | "reminder"): void;
};
const emitter = new EventEmitter();
const typedEmitter = emitter as TypedEventEmitter<AppEvents>;
const t = initTRPC.create();
export const bridgeRouter = t.router({
onCreateItem: createSubscription("onCreateItem")
});
export const bridge: AppEvents = new Proxy({} as AppEvents, {
get(_t, name) {
if (typeof name === "symbol") return;
return (...args: unknown[]) => {
emitter.emit(name, ...args);
};
}
});
function createSubscription<TName extends keyof AppEvents>(eventName: TName) {
return t.procedure.subscription(() => {
return observable<Parameters<AppEvents[TName]>[0]>((emit) => {
const listener: AppEvents[TName] = (...args: any[]) => {
emit.next(args[0]);
};
typedEmitter.addListener(eventName, listener);
return () => {
typedEmitter.removeListener(eventName, listener);
};
});
});
}

View File

@@ -18,16 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import zlib from "node:zlib";
import utils from "node:util";
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
const gzipAsync = utils.promisify(zlib.gzip);
const gunzipAsync = utils.promisify(zlib.gunzip);
export async function gzip(args) {
const { data, level } = args;
return (await gzipAsync(data, { level })).toString("base64");
}
export async function gunzip(args) {
const { data } = args;
return (await gunzipAsync(Buffer.from(data, "base64"))).toString("utf-8");
}
export const compressionRouter = t.router({
gzip: t.procedure
.input(z.object({ data: z.string(), level: z.number() }))
.query(async ({ input }) => {
const { data, level } = input;
return (await gzipAsync(data, { level })).toString("base64");
}),
gunzip: t.procedure.input(z.string()).query(async ({ input }) => {
return (await gunzipAsync(Buffer.from(input, "base64"))).toString("utf-8");
})
});

View File

@@ -17,25 +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 { ipcMain } from "electron";
import { logger } from "../logger";
import { getAction } from "./actions";
import { getCall } from "./calls";
import { initTRPC } from "@trpc/server";
import { compressionRouter } from "./compression";
import { osIntegrationRouter } from "./os-integration";
import { spellCheckerRouter } from "./spell-checker";
import { updaterRouter } from "./updater";
import { bridgeRouter } from "./bridge";
ipcMain.on("fromRenderer", async (event, args) => {
logger.info("Action requested by renderer", args);
const t = initTRPC.create();
const { type } = args;
const action = getAction(type);
if (!action) return;
await action(args);
export const router = t.router({
compress: compressionRouter,
integration: osIntegrationRouter,
spellChecker: spellCheckerRouter,
updater: updaterRouter,
bridge: bridgeRouter
});
ipcMain.handle("fromRenderer", async (event, args) => {
const { type } = args;
logger.info("Call requested by renderer", type);
const call = getCall(type);
if (!call) return;
export const api = router.createCaller({});
return await call(args, globalThis.window);
});
// Export type router type signature,
// NOT the router itself.
export type AppRouter = typeof router;

View File

@@ -0,0 +1,160 @@
/*
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 { initTRPC } from "@trpc/server";
import { z } from "zod";
import { dialog, nativeTheme, Notification, shell } from "electron";
import { AutoLaunch } from "../utils/autolaunch";
import { config, DesktopIntegration } from "../utils/config";
import { bringToFront } from "../utils/bring-to-front";
import { getTheme, setTheme, Theme } from "../utils/theme";
import { mkdirSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { platform } from "os";
import { resolvePath } from "../utils/resolve-path";
import { observable } from "@trpc/server/observable";
const t = initTRPC.create();
const NotificationOptions = z.object({
title: z.string().optional(),
body: z.string().optional(),
silent: z.boolean().optional(),
timeoutType: z.union([z.literal("default"), z.literal("never")]).optional(),
urgency: z
.union([z.literal("normal"), z.literal("critical"), z.literal("low")])
.optional(),
tag: z.string()
});
export const osIntegrationRouter = t.router({
zoomFactor: t.procedure.query(() => config.zoomFactor),
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
globalThis.window?.webContents.setZoomFactor(factor);
config.zoomFactor = factor;
}),
privacyMode: t.procedure.query(() => config.privacyMode),
setPrivacyMode: t.procedure
.input(z.boolean())
.mutation(({ input: privacyMode }) => {
if (!globalThis.window || !["win32", "darwin"].includes(process.platform))
return;
globalThis.window.setContentProtection(privacyMode);
if (process.platform === "win32") {
globalThis.window.setThumbnailClip(
privacyMode
? { x: 0, y: 0, width: 1, height: 1 }
: { x: 0, y: 0, width: 0, height: 0 }
);
}
config.privacyMode = privacyMode;
}),
desktopIntegration: t.procedure.query(() => config.desktopSettings),
setDesktopIntegration: t.procedure
.input(DesktopIntegration)
.mutation(({ input: settings }) => {
if (settings.autoStart) {
AutoLaunch.enable(settings.startMinimized);
} else {
AutoLaunch.disable();
}
config.desktopSettings = settings;
}),
selectDirectory: t.procedure
.input(
z.object({
title: z.string().optional(),
buttonLabel: z.string().optional(),
defaultPath: z.string().optional()
})
)
.query(async ({ input }) => {
if (!globalThis.window) return undefined;
const { title, buttonLabel, defaultPath } = input;
const result = await dialog.showOpenDialog(globalThis.window, {
title,
buttonLabel,
properties: ["openDirectory"],
defaultPath: defaultPath && resolvePath(defaultPath)
});
if (result.canceled) return undefined;
return result.filePaths[0];
}),
saveFile: t.procedure
.input(z.object({ data: z.string(), filePath: z.string() }))
.query(({ input }) => {
const { data, filePath } = input;
if (!data || !filePath) return;
const resolvedPath = resolvePath(filePath);
mkdirSync(dirname(resolvedPath), { recursive: true });
writeFileSync(resolvedPath, data);
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {
const notification = new Notification({
...input,
icon: join(
__dirname,
platform() === "win32" ? "app.ico" : "favicon-72x72.png"
)
});
notification.show();
if (input.urgency === "critical") {
shell.beep();
}
return new Promise((resolve) => {
notification.once("close", () => resolve(undefined));
notification.once("click", () => resolve(input.tag));
});
}),
openPath: t.procedure
.input(z.object({ type: z.literal("path"), link: z.string() }))
.query(({ input }) => {
const { type, link } = input;
if (type === "path") return shell.openPath(resolvePath(link));
}),
bringToFront: t.procedure.query(() => bringToFront()),
changeTheme: t.procedure
.input(Theme)
.mutation(({ input }) => setTheme(input)),
onThemeChanged: t.procedure.subscription(() =>
observable<"dark" | "light">((emit) => {
nativeTheme.on("updated", () => {
if (getTheme() === "system") {
emit.next(nativeTheme.shouldUseDarkColors ? "dark" : "light");
}
});
})
)
});

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 { getIsSpellCheckerEnabled } from "../../config/spellChecker";
import { initTRPC } from "@trpc/server";
import { z } from "zod";
import { config } from "../utils/config";
const LANGUAGES = {
const t = initTRPC.create();
const LANGUAGES: Record<string, string> = {
af: "Afrikaans",
bg: "Bulgarian",
ca: "Catalan",
@@ -78,28 +82,39 @@ const LANGUAGES = {
vi: "Vietnamese"
};
/**
*
* @param {*} args
* @param {import("electron").BrowserWindow} win
* @returns
*/
export default function (_args, win) {
if (!win) return;
type Language = { code: string; name: string };
return {
enabled: getIsSpellCheckerEnabled(),
languages: win.webContents.session.availableSpellCheckerLanguages.map(
(code) => ({
code,
name: LANGUAGES[code]
})
export const spellCheckerRouter = t.router({
isEnabled: t.procedure.query(() => config.isSpellCheckerEnabled),
languages: t.procedure.query(
() =>
<Language[]>(
globalThis.window?.webContents.session.availableSpellCheckerLanguages.map(
(code) => ({
code,
name: LANGUAGES[code]
})
)
)
),
enabledLanguages: t.procedure.query(
() =>
<Language[]>(
globalThis.window?.webContents.session
.getSpellCheckerLanguages()
.map((code) => ({
code,
name: LANGUAGES[code]
}))
)
),
setLanguages: t.procedure
.input(z.array(z.string()))
.mutation(({ input: languages }) =>
globalThis.window?.webContents.session.setSpellCheckerLanguages(languages)
),
enabledLanguages: win.webContents.session
.getSpellCheckerLanguages()
.map((code) => ({
code,
name: LANGUAGES[code]
}))
};
}
toggle: t.procedure.input(z.boolean()).mutation(({ input: enabled }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
config.isSpellCheckerEnabled = enabled;
})
});

View File

@@ -0,0 +1,71 @@
/*
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 { initTRPC } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { CancellationToken, autoUpdater } from "electron-updater";
import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
type UpdateInfo = { version: string };
type Progress = { percent: number };
const t = initTRPC.create();
export const updaterRouter = t.router({
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
download: t.procedure.query(async () => {
const cancellationToken = new CancellationToken();
await autoUpdater.downloadUpdate(cancellationToken);
}),
check: t.procedure.query(async () => {
await autoUpdater.checkForUpdates();
}),
onChecking: createSubscription("checking-for-update"),
onDownloaded: createSubscription<"update-downloaded", UpdateInfo>(
"update-downloaded"
),
onDownloadProgress: createSubscription<"download-progress", Progress>(
"download-progress"
),
onNotAvailable: createSubscription<"update-not-available", UpdateInfo>(
"update-not-available"
),
onAvailable: createSubscription<"update-available", UpdateInfo>(
"update-available"
),
onError: createSubscription("error")
});
function createSubscription<
TName extends keyof AppUpdaterEvents,
TReturnType = Parameters<AppUpdaterEvents[TName]>[0]
>(eventName: TName) {
return t.procedure.subscription(() => {
return observable<TReturnType>((emit) => {
const listener: AppUpdaterEvents[TName] = (...args: any[]) => {
emit.next(args[0]);
};
autoUpdater.addListener(eventName, listener);
return () => {
autoUpdater.removeListener(eventName, listener);
};
});
});
}

View File

@@ -21,18 +21,21 @@ import yargs from "yargs";
import { hideBin } from "yargs/helpers";
export type CLIOptions = {
note?: boolean | string;
notebook?: boolean | string;
reminder?: boolean | string;
note: boolean | string;
notebook: boolean | string;
reminder: boolean | string;
hidden: boolean;
};
export async function parseArguments(): Promise<CLIOptions> {
const result: CLIOptions = {
note: false,
notebook: false,
reminder: false
reminder: false,
hidden: false
};
await yargs(hideBin(process.argv))
const { hidden } = await yargs(hideBin(process.argv))
.boolean("hidden")
.command("new", "Create a new item", (yargs) => {
return yargs
.command("note", "Create a new note", {}, () => {
@@ -52,7 +55,7 @@ export async function parseArguments(): Promise<CLIOptions> {
"Open a note",
{ id: { string: true, description: "Id of the note" } },
(args) => {
result.note = args.id;
result.note = args.id || false;
}
)
.command(
@@ -60,7 +63,7 @@ export async function parseArguments(): Promise<CLIOptions> {
"Open a notebook",
{ id: { string: true, description: "Id of the notebook" } },
(args) => {
result.notebook = args.id;
result.notebook = args.id || false;
}
)
.command(
@@ -76,5 +79,6 @@ export async function parseArguments(): Promise<CLIOptions> {
);
})
.parse();
result.hidden = hidden || false;
return result;
}

View File

@@ -17,8 +17,6 @@ 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 { getPrivacyMode } from "../../config/privacyMode";
export default function () {
return getPrivacyMode();
}
export * from "./constants";
export type { AppRouter } from "./api";
export { type UpdateInfo } from "builder-util-runtime";

View File

@@ -18,28 +18,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* global MAC_APP_STORE, RELEASE */
import "isomorphic-fetch";
import { app, BrowserWindow, nativeTheme, shell } from "electron";
import { isDevelopment } from "./utils";
import { registerProtocol, PROTOCOL_URL } from "./protocol";
import { configureAutoUpdater } from "./autoupdate";
import { getBackgroundColor, getTheme, setTheme } from "./config/theme";
import getZoomFactor from "./ipc/calls/getZoomFactor";
import { logger } from "./logger";
import { setupMenu } from "./menu";
import { WindowState } from "./config/window-state";
import { sendMessageToRenderer } from "./ipc/utils";
import { EVENTS } from "./events";
import "./ipc/index.js";
import getPrivacyMode from "./ipc/calls/getPrivacyMode";
import setPrivacyMode from "./ipc/actions/setPrivacyMode";
import { getIsSpellCheckerEnabled } from "./config/spellChecker";
import { getDesktopIntegration } from "./config/desktopIntegration";
import { AutoLaunch } from "./autolaunch";
import { setupJumplist } from "./jumplist";
import { setupTray } from "./tray";
import { parseArguments } from "./cli";
import { AssetManager } from "./asset-manager";
import { registerProtocol, PROTOCOL_URL } from "./utils/protocol";
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";
import { AssetManager } from "./utils/asset-manager";
import { createIPCHandler } from "electron-trpc/main";
import { router, api } from "./api";
import { config } from "./utils/config";
if (!RELEASE) {
require("electron-reloader")(module);
@@ -55,15 +48,15 @@ if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
var mainWindowState;
async function createWindow() {
await AssetManager.loadIcons();
const cliOptions = await parseArguments();
mainWindowState = new WindowState({});
const mainWindowState = new WindowState({});
setTheme(getTheme());
const mainWindow = new BrowserWindow({
show: false,
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
@@ -77,21 +70,27 @@ async function createWindow() {
format: process.platform === "win32" ? "ico" : "png"
}),
webPreferences: {
zoomFactor: getZoomFactor(),
zoomFactor: config.zoomFactor,
devTools: true, // isDev,
nodeIntegration: false, //true,
contextIsolation: true,
nativeWindowOpen: true,
spellcheck: getIsSpellCheckerEnabled(),
nodeIntegration: true, //true,
contextIsolation: false,
spellcheck: config.isSpellCheckerEnabled,
preload: __dirname + "/preload.js"
}
});
mainWindow.on("show", async () => {
if (config.privacyMode) {
await api.integration.setPrivacyMode(config.privacyMode);
}
});
mainWindowState.manage(mainWindow);
globalThis.window = mainWindow;
setupMenu();
setupJumplist();
setupDesktopIntegration();
setupDesktopIntegration(cliOptions.hidden);
createIPCHandler({ router, windows: [mainWindow] });
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
@@ -100,14 +99,10 @@ async function createWindow() {
if (isDevelopment())
mainWindow.webContents.openDevTools({ mode: "right", activate: true });
if (getPrivacyMode()) {
setPrivacyMode({ privacyMode: getPrivacyMode() });
}
try {
await mainWindow.loadURL(`${createURL(cliOptions)}`);
} catch (e) {
logger.error(e);
if (e instanceof Error) console.error(e);
}
mainWindow.on("closed", () => {
@@ -122,20 +117,14 @@ async function createWindow() {
nativeTheme.on("updated", () => {
setupTray();
setupJumplist();
if (getTheme() === "system") {
sendMessageToRenderer(EVENTS.themeChanged, {
theme: nativeTheme.shouldUseDarkColors ? "dark" : "light"
});
}
});
}
app.commandLine.appendSwitch("lang", "en-US");
app.on("ready", async () => {
logger.info("App ready. Opening window.");
console.info("App ready. Opening window.");
registerProtocol();
if (!isDevelopment()) registerProtocol();
await createWindow();
configureAutoUpdater();
});
@@ -152,10 +141,7 @@ app.on("activate", () => {
}
});
/**
* @param {import("./cli").CLIOptions} options
*/
function createURL(options) {
function createURL(options: CLIOptions) {
const url = new URL(isDevelopment() ? "http://localhost:3000" : PROTOCOL_URL);
if (options.note === true) url.hash = "/notes/create/1";
@@ -169,8 +155,8 @@ function createURL(options) {
return url;
}
function setupDesktopIntegration() {
const desktopIntegration = getDesktopIntegration();
function setupDesktopIntegration(hidden: boolean) {
const desktopIntegration = config.desktopSettings;
if (
desktopIntegration.closeToSystemTray ||
@@ -179,42 +165,49 @@ function setupDesktopIntegration() {
setupTray();
}
globalThis.window?.on("ready-to-show", () => {
if (!hidden) {
globalThis.window?.show();
}
if (hidden && !desktopIntegration.minimizeToSystemTray) {
globalThis.window?.show();
globalThis.window?.minimize();
}
});
// 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 (getDesktopIntegration().closeToSystemTray) {
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);
if (globalThis.window?.isFullScreen()) app.exit(0);
else app.hide();
} else {
globalThis.window.minimize();
globalThis.window.hide();
globalThis.window?.minimize();
globalThis.window?.hide();
}
}
});
globalThis.window.on("minimize", () => {
if (getDesktopIntegration().minimizeToSystemTray) {
globalThis.window?.on("minimize", () => {
if (config.desktopSettings.minimizeToSystemTray) {
if (process.platform == "darwin") {
app.hide();
} else {
globalThis.window.hide();
globalThis.window?.hide();
}
}
});
if (desktopIntegration.autoStart) {
AutoLaunch.enable(desktopIntegration.startMinimized);
if (process.platform === "win32" && desktopIntegration.startMinimized) {
globalThis.window.minimize();
}
}
}

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/>.
*/
/* eslint-disable no-var */
import { ELECTRON_TRPC_CHANNEL } from "electron-trpc/main";
import { type RendererGlobalElectronTRPC } from "electron-trpc/src/types";
import { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
declare global {
var os: string;
var electronTRPC: RendererGlobalElectronTRPC;
var NativeNNCrypto: new () => NNCrypto;
}
console.log("HELLO", process);
process.once("loaded", async () => {
console.log("HELLO!");
const electronTRPC: RendererGlobalElectronTRPC = {
sendMessage: (operation) =>
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),
onMessage: (callback) =>
ipcRenderer.on(ELECTRON_TRPC_CHANNEL, (_event, args) => callback(args))
};
globalThis.electronTRPC = electronTRPC;
});
globalThis.NativeNNCrypto = NNCrypto;
globalThis.os = MAC_APP_STORE ? "mas" : process.platform;

View File

@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeImage, nativeImage } from "electron";
import path from "path";
import { isDevelopment } from "./utils";
import { isDevelopment } from "./index";
import { parse, ParsedImage } from "icojs";
import { readFileSync } from "fs";
import { getSystemTheme } from "./config/theme";
import { getSystemTheme } from "./theme";
type Formats = "ico" | "png" | "icns";
type IconOptions<TFormat extends Formats> = {
@@ -30,8 +30,8 @@ type IconOptions<TFormat extends Formats> = {
format?: TFormat;
};
type IconNames = typeof icons[number];
type Prefixes = typeof prefixes[number];
type IconNames = (typeof icons)[number];
type Prefixes = (typeof prefixes)[number];
type FlexibleIcon<TFormat extends Formats> = TFormat extends "ico"
? string

View File

@@ -47,7 +47,11 @@ export class AutoLaunch {
LINUX_DESKTOP_ENTRY(hidden)
);
} else {
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: hidden });
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: hidden,
args: ["--hidden"]
});
}
}

View File

@@ -19,6 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { autoUpdater } from "electron-updater";
export default () => {
autoUpdater.checkForUpdates();
};
async function configureAutoUpdater() {
autoUpdater.setFeedURL({
provider: "generic",
url: `https://notesnook.com/releases/${process.platform}/`,
useMultipleRangeRequest: false
});
autoUpdater.autoDownload = false;
autoUpdater.allowDowngrade = false;
autoUpdater.allowPrerelease = false;
autoUpdater.autoInstallOnAppQuit = true;
}
export { configureAutoUpdater };

View File

@@ -17,9 +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 { WindowState } from "../../config/window-state";
import { WindowState } from "./window-state";
export default () => {
export function bringToFront() {
if (!globalThis.window) return;
if (globalThis.window.isMinimized()) {
@@ -31,4 +31,4 @@ export default () => {
globalThis.window.focus();
globalThis.window.moveTop();
globalThis.window.webContents.focus();
};
}

View File

@@ -0,0 +1,55 @@
/*
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 { nativeTheme } from "electron";
import { JSONStorage } from "./json-storage";
import { z } from "zod";
export const DesktopIntegration = z.object({
autoStart: z.boolean(),
startMinimized: z.boolean(),
minimizeToSystemTray: z.boolean(),
closeToSystemTray: z.boolean()
});
export type DesktopIntegration = z.infer<typeof DesktopIntegration>;
export const config = {
desktopSettings: <DesktopIntegration>{
autoStart: false,
startMinimized: false,
minimizeToSystemTray: false,
closeToSystemTray: false
},
privacyMode: false,
isSpellCheckerEnabled: false,
zoomFactor: 0,
theme: nativeTheme.themeSource
};
type ConfigKey = keyof typeof config;
for (const key in config) {
const defaultValue = config[<ConfigKey>key];
if (Object.hasOwn(config, key)) {
Object.defineProperty(config, key, {
get: () => JSONStorage.get(key, defaultValue),
set: (value) => JSONStorage.set(key, value)
});
}
}

View File

@@ -17,15 +17,21 @@ 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 { JSONStorage } from "../jsonstorage";
import { app } from "electron";
import { join } from "path";
import { existsSync } from "fs";
function getIsSpellCheckerEnabled() {
let mode = JSONStorage.get("isSpellCheckerEnabled");
return mode === undefined ? true : mode;
export const APP_ICON_PATH = join(
__dirname,
process.platform === "win32" ? "app.ico" : "favicon-72x72.png"
);
export function isDevelopment() {
return process.env.ELECTRON_IS_DEV
? Number.parseInt(process.env.ELECTRON_IS_DEV, 10) === 1
: !app.isPackaged;
}
function setIsSpellCheckerEnabled(mode) {
return JSONStorage.set("isSpellCheckerEnabled", mode);
export function isFlatpak() {
return existsSync("/.flatpak-info");
}
export { getIsSpellCheckerEnabled, setIsSpellCheckerEnabled };

View File

@@ -19,9 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { app, Menu } from "electron";
import { AssetManager } from "./asset-manager";
import { EVENTS } from "./events";
import bringToFront from "./ipc/actions/bringToFront";
import { sendMessageToRenderer } from "./ipc/utils";
import { bringToFront } from "./bring-to-front";
import { bridge } from "../api/bridge";
export function setupJumplist() {
if (process.platform === "win32") {
@@ -76,7 +75,7 @@ function setDockMenuOnMacOs() {
type: "normal",
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "note" });
bridge.onCreateItem("note");
}
},
{
@@ -84,7 +83,7 @@ function setDockMenuOnMacOs() {
type: "normal",
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "notebook" });
bridge.onCreateItem("notebook");
}
},
{
@@ -92,7 +91,7 @@ function setDockMenuOnMacOs() {
type: "normal",
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "reminder" });
bridge.onCreateItem("reminder");
}
}
]);

View File

@@ -20,6 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Menu, MenuItem, clipboard, shell } from "electron";
function setupMenu() {
if (!globalThis.window) return;
globalThis.window.webContents.on("context-menu", (_event, params) => {
const menu = new Menu();
@@ -29,7 +31,7 @@ function setupMenu() {
new MenuItem({
label: suggestion,
click: () =>
globalThis.window.webContents.replaceMisspelling(suggestion)
globalThis.window?.webContents.replaceMisspelling(suggestion)
})
);
}
@@ -40,7 +42,7 @@ function setupMenu() {
new MenuItem({
label: "Add to dictionary",
click: () =>
globalThis.window.webContents.session.addWordToSpellCheckerDictionary(
globalThis.window?.webContents.session.addWordToSpellCheckerDictionary(
params.misspelledWord
)
})

View File

@@ -17,19 +17,18 @@ 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 } from "electron";
import { isDevelopment, getPath } from "./utils";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
import { logger } from "./logger";
import { Blob } from "buffer";
import { protocol, ProtocolRequest } from "electron";
import { isDevelopment } from "./index";
import { createReadStream, statSync } from "fs";
import { extname, join, normalize } from "path";
import { URL } from "url";
import { Readable } from "stream";
const FILE_NOT_FOUND = -6;
const BASE_PATH = isDevelopment() ? "../public" : "";
const HOSTNAME = `app.notesnook.com`;
const PROTOCOL = "https";
const extensionToMimeType = {
const FILE_NOT_FOUND = -6;
const SCHEME = "https";
const extensionToMimeType: Record<string, string> = {
html: "text/html",
json: "application/json",
js: "application/javascript",
@@ -40,11 +39,11 @@ const extensionToMimeType = {
function registerProtocol() {
const protocolInterceptionResult = protocol.interceptStreamProtocol(
PROTOCOL,
SCHEME,
async (request, callback) => {
const url = new URL(request.url);
if (shouldInterceptRequest(url)) {
logger.info("Intercepting request:", request);
console.info("Intercepting request:", request.url);
const loadIndex = !extname(url.pathname);
const absoluteFilePath = normalize(
@@ -56,7 +55,7 @@ function registerProtocol() {
);
const filePath = getPath(absoluteFilePath);
if (!filePath) {
logger.error("Local asset file not found at", filePath);
console.error("Local asset file not found at", filePath);
callback({ error: FILE_NOT_FOUND });
return;
}
@@ -68,64 +67,91 @@ function registerProtocol() {
mimeType: extensionToMimeType[fileExtension]
});
} else {
var response;
let response: Response;
try {
const body = await getBody(request);
response = await fetch(request.url, {
...request,
body,
headers: {
...request.headers,
origin: `${PROTOCOL}://${HOSTNAME}/`
...request.headers
// origin: `${PROTOCOL}://${HOSTNAME}/`
},
referrer: request.referrer,
redirect: "manual"
});
} catch (e) {
console.error(e);
logger.error(`Error sending request to `, request.url, "Error: ", e);
console.error(`Error sending request to `, request.url, "Error: ", e);
callback({ statusCode: 400 });
return;
}
callback({
statusCode: response.status,
data: response.body,
data: response.body ? Readable.fromWeb(response.body) : undefined,
headers: Object.fromEntries(response.headers.entries()),
mimeType: response.headers.get("Content-Type")
mimeType: response.headers.get("Content-Type") || undefined
});
}
}
);
logger.info(
`${PROTOCOL} protocol inteception ${
console.info(
`${SCHEME} protocol inteception ${
protocolInterceptionResult ? "successful" : "failed"
}.`
);
// protocol.handle(SCHEME, (request) => {
// const url = new URL(request.url);
// if (shouldInterceptRequest(url)) {
// console.info("Intercepting request:", request.url);
// const loadIndex = !extname(url.pathname);
// const absoluteFilePath = normalize(
// `${__dirname}${
// loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
// }`
// );
// const filePath = getPath(absoluteFilePath);
// if (!filePath) {
// console.error("Local asset file not found at", filePath);
// return new Response(undefined, {
// status: 404,
// statusText: "FILE_NOT_FOUND"
// });
// }
// const fileExtension = extname(filePath).replace(".", "");
// const data = createReadStream(filePath);
// return new Response(data, {
// headers: { "Content-Type": extensionToMimeType[fileExtension] }
// });
// } else {
// return net.fetch(request);
// }
// });
// console.info(`${SCHEME} protocol inteception "successful"`);
}
const bypassedRoutes = ["/notes/index_v14.json", "/notes/welcome-web"];
function shouldInterceptRequest(url) {
let shouldIntercept = url.hostname === HOSTNAME;
const bypassedRoutes: string[] = [];
function shouldInterceptRequest(url: URL) {
const shouldIntercept = url.hostname === HOSTNAME;
return shouldIntercept && !bypassedRoutes.includes(url.pathname);
}
/**
*
* @param {Electron.ProtocolRequest} request
*/
async function getBody(request) {
/**
* @type {Electron.Session}
*/
const session = globalThis.window.webContents.session;
const PROTOCOL_URL = `${SCHEME}://${HOSTNAME}/`;
export { registerProtocol, PROTOCOL_URL };
async function getBody(request: ProtocolRequest) {
const session = globalThis?.window?.webContents?.session;
const blobParts = [];
if (!request.uploadData || !request.uploadData.length) return null;
for (let data of request.uploadData) {
if (data.type === "rawData") {
for (const data of request.uploadData) {
if (data.bytes) {
blobParts.push(new Uint8Array(data.bytes));
} else if (data.type === "blob") {
} else if (session && data.blobUUID) {
const buffer = await session.getBlobData(data.blobUUID);
if (!buffer) continue;
blobParts.push(new Uint8Array(buffer));
}
}
@@ -133,5 +159,18 @@ async function getBody(request) {
return await blob.arrayBuffer();
}
const PROTOCOL_URL = `${PROTOCOL}://${HOSTNAME}/`;
export { registerProtocol, PROTOCOL_URL };
function getPath(filePath: string): string | undefined {
try {
const result = statSync(filePath);
if (result.isFile()) {
return filePath;
}
if (result.isDirectory()) {
return getPath(join(filePath, "index.html"));
}
} catch (_) {
// ignore
}
}

View File

@@ -17,25 +17,17 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { logger } from "../logger";
import { app } from "electron";
import { isAbsolute, join } from "path";
function sendMessageToRenderer(type, payload = {}) {
const message = { type, ...payload };
logger.info("Sending message to renderer", message);
if (globalThis.window)
globalThis.window.webContents.send("fromMain", message);
}
function resolvePath(_path) {
export function resolvePath(_path: string) {
if (isAbsolute(_path)) return _path;
return join(
..._path.split("/").map((segment) => {
let resolved = segment;
try {
resolved = app.getPath(resolved);
resolved = app.getPath(resolved as any);
} catch (e) {
// ignore
}
@@ -43,5 +35,3 @@ function resolvePath(_path) {
})
);
}
export { resolvePath, sendMessageToRenderer };

View File

@@ -17,19 +17,27 @@ 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 { JSONStorage } from "../jsonstorage";
import { nativeTheme } from "electron";
import { config } from "./config";
import { z } from "zod";
type Theme = typeof nativeTheme.themeSource;
function getTheme(): Theme {
return JSONStorage.get("theme", nativeTheme.themeSource);
export const Theme = z.union([
z.literal("system"),
z.literal("light"),
z.literal("dark")
]);
export type Theme = z.infer<typeof Theme>;
function getTheme() {
return config.theme;
}
function setTheme(theme: Theme) {
changeTheme(theme);
if (globalThis.window)
globalThis.window.setBackgroundColor(getBackgroundColor());
return JSONStorage.set("theme", theme);
config.theme = theme;
}
function getBackgroundColor() {

View File

@@ -19,12 +19,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { app, Menu, Tray } from "electron";
import { AssetManager } from "./asset-manager";
import { EVENTS } from "./events";
import bringToFront from "./ipc/actions/bringToFront";
import { sendMessageToRenderer } from "./ipc/utils";
import { isFlatpak } from "./utils";
import { isFlatpak } from "./index";
import { bringToFront } from "./bring-to-front";
import { bridge } from "../api/bridge";
let tray: Tray | undefined = undefined;
export function destroyTray() {
if (tray) tray.destroy();
}
export function setupTray() {
if (tray) tray.destroy();
@@ -55,7 +58,7 @@ export function setupTray() {
: AssetManager.icon("note-add", { size: trayIconSize }),
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "note" });
bridge.onCreateItem("note");
}
},
{
@@ -66,7 +69,7 @@ export function setupTray() {
: AssetManager.icon("notebook-add", { size: trayIconSize }),
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "notebook" });
bridge.onCreateItem("notebook");
}
},
{ type: "separator" },

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 { JSONStorage } from "../jsonstorage";
import { JSONStorage } from "./json-storage";
import { screen as _screen, BrowserWindow } from "electron";
const screen = _screen;

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./dist/",
"lib": ["ESNext"]
},
"include": ["src", "global.d.ts"]
}

View File

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

View File

@@ -31,14 +31,6 @@ import { TipManager } from "./services/tip-manager";
import { useUserStore } from "./stores/use-user-store";
import { View } from "react-native";
import { useState } from "react";
import NetInfo from "@react-native-community/netinfo";
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
reachabilityTest: (response) => {
if (!response) return false;
return response?.status >= 200 && response?.status < 300;
}
});
SettingsService.init();
SettingsService.checkOrientation();

View File

@@ -22,10 +22,17 @@ import "react-native-get-random-values";
import * as Keychain from "react-native-keychain";
import { generateSecureRandom } from "react-native-securerandom";
import Sodium from "@ammarahmed/react-native-sodium";
import { MMKV } from "./mmkv";
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 KEYSTORE_CONFIG = Platform.select({
ios: {
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
accessGroup: IOS_KEYCHAIN_ACCESS_GROUP,
service: IOS_KEYCHAIN_SERVICE_NAME
},
android: {}
});
@@ -39,12 +46,27 @@ export async function deriveCryptoKey(name, data) {
credentials.key,
KEYSTORE_CONFIG
);
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
return credentials.key;
} catch (e) {
console.error(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
);
console.log("IOS KEYCHAIN MIGRATION COMPLETED!");
MMKV.setBool(IOS_KEYCHAIN_UPGRAGE_KEY, true);
}
}
export async function getCryptoKey(_name) {
try {
if (await Keychain.hasInternetCredentials("notesnook")) {
@@ -52,6 +74,9 @@ export async function getCryptoKey(_name) {
"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 {
return null;

View File

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

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import {
decrypt,
deriveCryptoKey,

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Dimensions, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
/**
* Scale down & compress images to screen width
* for loading in editor.

View File

@@ -17,25 +17,169 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import {
getFileNameWithExtension,
isImage,
isDocument
} from "@notesnook/core/utils/filename";
import React from "react";
import { Platform } from "react-native";
import * as ScopedStorage from "react-native-scoped-storage";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { subscribe, zip } from "react-native-zip-archive";
import RNFetchBlob from "react-native-blob-util";
import { ShareComponent } from "../../components/sheets/export-notes/share";
import { presentSheet, ToastEvent } from "../../services/event-manager";
import { ToastEvent, presentSheet } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir } from "./utils";
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
export const FileDownloadStatus = {
Success: 1,
Fail: 0
};
/**
* Download all user's attachments
* @returns
*/
export function downloadAllAttachments() {
const attachments = db.attachments.all;
return downloadAttachments(attachments);
}
/**
* Downloads provided attachments to a .zip file
* on user's device.
* @param attachments
* @param onProgress
* @returns
*/
export async function downloadAttachments(
attachments,
onProgress,
canceled,
groupId
) {
if (!attachments || !attachments.length) return;
const result = new Map();
let outputFolder;
if (Platform.OS === "android") {
// Ask the user to select a directory to store the file
let file = await ScopedStorage.openDocumentTree(true);
outputFolder = file.uri;
if (!outputFolder) return;
} else {
outputFolder = await Storage.checkAndCreateDir("/downloads/");
}
// Create the folder to zip;
const zipSourceFolder = `${cacheDir}/notesnook-attachments`;
const zipOutputFile =
Platform.OS === "ios"
? `${outputFolder}/notesnook-attachments-${Date.now()}.zip`
: `${cacheDir}/notesnook-attachments.zip`;
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;
try {
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
onProgress?.(
i + 1 / attachments.length,
`Downloading attachments (${i + 1}/${
attachments.length
})... Please wait`
);
// Download to cache
let uri = await downloadAttachment(hash, false, {
silent: true,
cache: true,
groupId: groupId
});
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
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}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
result.set(hash, {
filename: attachment.metadata.filename,
status: FileDownloadStatus.Success
});
} catch (e) {
result.set(hash, {
filename: attachment.metadata.filename,
status: FileDownloadStatus.Fail,
reason: e
});
console.log("Error downloading attachment", e);
}
}
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
return;
}
if (result?.size) {
let sub;
try {
onProgress?.(0, `Zipping... Please wait`);
// If all goes well, zip the notesnook-attachments folder in cache.
sub = subscribe(({ progress }) => {
onProgress(
progress,
`Saving zip file (${(progress * 100).toFixed(1)}%)... Please wait`
);
});
await zip(zipSourceFolder, zipOutputFile);
sub?.remove();
onProgress(1, `Saving zip file... Please wait`);
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
outputFolder,
`notesnook-attachments-${Date.now()}.zip`,
"application/zip"
);
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
}
onProgress?.(1, `Done`);
releasePermissions(outputFolder);
} catch (e) {
releasePermissions(outputFolder);
sub?.remove();
console.log("Error zipping attachments", e);
}
// Remove source & zip file from cache.
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
if (Platform.OS === "android") {
RNFetchBlob.fs.unlink(zipOutputFile).catch(console.log);
}
}
return result;
}
export default async function downloadAttachment(
hash,
global = true,
options = {
silent: false,
cache: false
cache: false,
throwError: false,
groupId: undefined
}
) {
let attachment = db.attachments.attachment(hash);
@@ -55,8 +199,13 @@ export default async function downloadAttachment(
}
try {
await db.fs.downloadFile(
console.log(
"starting download attachment",
attachment.metadata.hash,
options.groupId
);
await db.fs.downloadFile(
options.groupId || attachment.metadata.hash,
attachment.metadata.hash
);
if (
@@ -99,7 +248,8 @@ export default async function downloadAttachment(
if (
attachment.dateUploaded &&
!attachment.metadata?.type?.startsWith("image")
!isImage(attachment.metadata?.type) &&
!isDocument(attachment.metadata?.type)
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
@@ -131,5 +281,8 @@ export default async function downloadAttachment(
.catch(console.log);
}
useAttachmentStore.getState().remove(attachment.metadata.hash);
if (options.throwError) {
throw e;
}
}
}

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import hosts from "@notesnook/core/utils/constants";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { ToastEvent } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";

View File

@@ -19,18 +19,29 @@ 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 RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { cacheDir, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function readEncrypted(filename, key, cipherData) {
let path = `${cacheDir}/${filename}`;
try {
let exists = await RNFetchBlob.fs.exists(path);
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists =
(await RNFetchBlob.fs.exists(path)) ||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
if (!exists) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
@@ -39,7 +50,8 @@ export async function readEncrypted(filename, key, cipherData) {
key,
{
...cipherData,
hash: filename
hash: filename,
appGroupId: IOS_APPGROUPID
},
cipherData.outputType === "base64"
? isPng || isJpeg

View File

@@ -17,10 +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 RNFetchBlob from "rn-fetch-blob";
import RNFetchBlob from "react-native-blob-util";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
@@ -35,9 +38,19 @@ export async function uploadFile(filename, data, cancelToken) {
if (!res.ok) throw new Error(`${res.status}: Unable to resolve upload url`);
const uploadUrl = await res.text();
if (!uploadUrl) throw new Error("Unable to resolve upload url");
let uploadFilePath = `${cacheDir}/${filename}`;
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists = await RNFetchBlob.fs.exists(uploadFilePath);
if (!exists && Platform.OS === "ios") {
uploadFilePath = appGroupPath;
}
let request = RNFetchBlob.config({
IOSBackgroundTask: true
IOSBackgroundTask: !globalThis["IS_SHARE_EXTENSION"]
})
.fetch(
"PUT",
@@ -45,7 +58,7 @@ export async function uploadFile(filename, data, cancelToken) {
{
"content-type": ""
},
RNFetchBlob.wrap(`${cacheDir}/${filename}`)
RNFetchBlob.wrap(uploadFilePath)
)
.uploadProgress((sent, total) => {
useAttachmentStore
@@ -63,7 +76,10 @@ export async function uploadFile(filename, data, cancelToken) {
if (result) {
let attachment = db.attachments.attachment(filename);
if (!attachment) return result;
if (!attachment.metadata.type.startsWith("image/")) {
if (
!isImage(attachment.metadata.type) &&
!isDocument(attachment.metadata?.type)
) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
}

View File

@@ -17,7 +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 RNFetchBlob from "rn-fetch-blob";
import * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
@@ -62,3 +64,22 @@ export function cancelable(operation) {
};
};
}
export function copyFileAsync(source, dest) {
return new Promise((resolve) => {
ScopedStorage.copyFile(source, dest, (e, r) => {
console.log(e, r);
resolve();
});
});
}
export async function releasePermissions(path) {
if (Platform.OS === "ios") return;
const uris = await ScopedStorage.getPersistedUriPermissions();
for (let uri of uris) {
if (path.startsWith(uri)) {
await ScopedStorage.releasePersistableUriPermission(uri);
}
}
}

View File

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

View File

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

View File

@@ -102,15 +102,21 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
if (res.failed) {
db.attachments.markAsFailed(attachment.id, res.failed);
setFailed(res.failed);
ToastEvent.show({
heading: "File check failed with error: " + res.failed,
type: "error",
context: "local"
});
} else {
setFailed(null);
db.attachments.markAsFailed(attachment.id, null);
ToastEvent.show({
heading: "File check passed",
type: "success",
context: "local"
});
}
ToastEvent.show({
heading: "File check passed",
type: "success",
context: "local"
});
setAttachments([...db.attachments.all]);
setLoading({
name: null
@@ -209,7 +215,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
>
<Paragraph
size={SIZE.xs + 1}
size={SIZE.xs}
style={{
marginRight: 10
}}
@@ -221,7 +227,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
style={{
marginRight: 10
}}
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
>
{formatBytes(attachment.length)}
@@ -232,7 +238,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
style={{
marginRight: 10
}}
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
>
{attachment.noteIds.length} note
@@ -248,7 +254,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
context: "local"
});
}}
size={SIZE.xs + 1}
size={SIZE.xs}
color={colors.icon}
>
{attachment.metadata.hash}
@@ -304,7 +310,7 @@ const Actions = ({ attachment, setAttachments, fwdRef }) => {
}}
key={item.id}
>
<Paragraph size={SIZE.xs + 1}>{item.title}</Paragraph>
<Paragraph size={SIZE.xs}>{item.title}</Paragraph>
</PressableButton>
))}
</>

View File

@@ -22,7 +22,6 @@ 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 { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { formatBytes } from "../../utils";
import { SIZE } from "../../utils/size";
@@ -37,19 +36,26 @@ function getFileExtension(filename) {
return ext == null ? "" : ext[1];
}
export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
export const AttachmentItem = ({
attachment,
encryption,
setAttachments,
pressable = true,
hideWhenNotDownloading
}) => {
const colors = useThemeStore((state) => state.colors);
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
attachment,
encryption
);
const encryptionProgress = useAttachmentStore(
(state) => state.encryptionProgress
);
const onPress = () => {
if (!pressable) return;
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
return (
return hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value) ? null : (
<TouchableOpacity
activeOpacity={0.9}
onPress={onPress}
@@ -112,22 +118,22 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
{attachment.metadata.filename}
</Paragraph>
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
{!hideWhenNotDownloading ? (
<Paragraph color={colors.icon} size={SIZE.xs}>
{formatBytes(attachment.length)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}
</View>
</View>
{currentProgress ||
(encryptionProgress && encryptionProgress !== "0.00") ||
encryption ? (
{currentProgress ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption) return;
if (encryption || !pressable) return;
db.fs.cancel(attachment.metadata.hash);
setCurrentProgress(null);
}}
@@ -140,13 +146,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
>
<ProgressCircleComponent
size={SIZE.xxl}
progress={
encryptionProgress
? encryptionProgress
: currentProgress?.value
? currentProgress?.value / 100
: 0
}
progress={currentProgress?.value ? currentProgress?.value / 100 : 0}
showsText
textStyle={{
fontSize: 10

View File

@@ -0,0 +1,273 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { Platform, View } from "react-native";
import { db } from "../../common/database";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
import { presentSheet } from "../../services/event-manager";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { ProgressBarComponent } from "../ui/svg/lazy";
import { useThemeStore } from "../../stores/use-theme-store";
import { FlatList } from "react-native-actions-sheet";
import { AttachmentItem } from "./attachment-item";
const DownloadAttachments = ({ close, attachments, isNote, update }) => {
const colors = useThemeStore((state) => state.colors);
const [downloading, setDownloading] = useState(false);
const [progress, setProgress] = useState({
value: 0,
statusText: "Download started... Please wait"
});
const [result, setResult] = useState(new Map());
const canceled = useRef(false);
const groupId = useRef();
const onDownload = async () => {
update({
disableClosing: true
});
setDownloading(true);
canceled.current = false;
groupId.current = Date.now().toString();
const result = await downloadAttachments(
attachments,
(progress, statusText) => setProgress({ value: progress, statusText }),
canceled,
groupId.current
);
if (canceled.current) return;
setResult(result || new Map());
setDownloading(false);
update({
disableClosing: false
});
};
const cancel = async () => {
update({
disableClosing: false
});
canceled.current = true;
console.log(groupId.current, "canceling groupId downloads");
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;
};
const failedResults = () => {
const results = [];
for (let [key, value] of result.entries()) {
if (value.status === 0) results.push(db.attachments.attachment(key));
}
return results;
};
function getResultText() {
const downloadedAttachmentsCount =
attachments.length - failedResults().length;
if (downloadedAttachmentsCount === 0)
return "Failed to download all attachments";
return `Successfully downloaded ${downloadedAttachmentsCount}/${
attachments.length
} attachments as a zip file at ${
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
}`;
}
return (
<View
style={{
alignItems: "center",
width: "100%",
paddingVertical: 12,
paddingHorizontal: 12
}}
>
<Heading>
{downloading
? "Downloading attachments"
: result?.size
? "Downloaded attachments"
: "Download attachments"}
</Heading>
{downloading ? (
<Paragraph
style={{
textAlign: "center"
}}
>
{progress.statusText}
</Paragraph>
) : result?.size ? (
<Paragraph
style={{
textAlign: "center"
}}
>
{getResultText()}
</Paragraph>
) : (
<Paragraph
style={{
textAlign: "center"
}}
>
Are you sure you want to download all attachments
{isNote ? " of this note?" : "?"}
</Paragraph>
)}
{downloading ? (
<View
style={{
width: 200,
marginTop: 10
}}
>
<ProgressBarComponent
height={5}
width={null}
animated={true}
useNativeDriver
progress={progress.value ? progress.value / attachments.length : 0}
unfilledColor={colors.nav}
color={colors.accent}
borderWidth={0}
/>
</View>
) : null}
<FlatList
style={{
maxHeight: 300,
width: "100%",
minHeight: 60,
backgroundColor: colors.nav,
borderRadius: 5,
marginVertical: 12
}}
data={downloading ? attachments : []}
ListEmptyComponent={
<View
style={{
width: "100%",
justifyContent: "center",
alignItems: "center",
height: 60
}}
>
<Paragraph color={colors.icon}>No downloads in progress.</Paragraph>
</View>
}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<AttachmentItem
attachment={item}
setAttachments={() => {}}
pressable={false}
hideWhenNotDownloading={true}
/>
);
}}
/>
{result?.size ? (
<Button
style={{
width: 250,
borderRadius: 100,
marginTop: 20
}}
onPress={close}
type="accent"
title="Done"
/>
) : !downloading ? (
<View
style={{
flexDirection: "row",
width: "100%",
marginTop: 20
}}
>
<Button
style={{
flex: 1,
borderRadius: 100,
marginRight: 5
}}
onPress={close}
type="grayBg"
title="No"
/>
<Button
style={{
flex: 1,
borderRadius: 100,
marginLeft: 5
}}
onPress={onDownload}
type="accent"
title="Yes"
/>
</View>
) : (
<Button
style={{
width: 250,
borderRadius: 100,
marginTop: 20
}}
onPress={cancel}
type="error"
title="Cancel"
/>
)}
</View>
);
};
DownloadAttachments.present = (context, attachments, isNote) => {
presentSheet({
context: context,
component: (ref, close, update) => (
<DownloadAttachments
close={close}
attachments={attachments}
isNote={isNote}
update={update}
/>
)
});
};
export default DownloadAttachments;

View File

@@ -18,44 +18,60 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { ActivityIndicator, ScrollView, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
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 { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
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";
import { AttachmentItem } from "./attachment-item";
import { FlatList } from "react-native-actions-sheet";
import DownloadAttachments from "./download-attachments";
import { Button } from "../ui/button";
import {
isAudio,
isDocument,
isImage,
isVideo
} from "@notesnook/core/utils/filename";
export const AttachmentDialog = ({ data }) => {
export const AttachmentDialog = ({ note }) => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(data);
const [attachments, setAttachments] = useState(
data
? db.attachments.ofNote(data.id, "all")
note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])]
);
const attachmentSearchValue = useRef();
const searchTimer = useRef();
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 || [])];
attachmentSearchValue.current = text;
if (
!attachmentSearchValue.current ||
attachmentSearchValue.current === ""
) {
setAttachments([...db.attachments.all]);
setAttachments([...attachments]);
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
let results = db.lookup.attachments(
db.attachments.all,
attachments,
attachmentSearchValue.current
);
if (results.length === 0) return;
@@ -67,6 +83,77 @@ export const AttachmentDialog = ({ data }) => {
<AttachmentItem setAttachments={setAttachments} attachment={item} />
);
const onCheck = async () => {
setLoading(true);
const checkedAttachments = [];
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(attachment.metadata.hash);
if (result.failed) {
await db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
await db.attachments.markAsFailed(attachment.id, null);
}
checkedAttachments.push(
db.attachments.attachment(attachment.metadata.hash)
);
setAttachments([...checkedAttachments]);
}
setLoading(false);
};
const attachmentTypes = [
{
title: "All",
filterBy: "all"
},
{
title: "Images",
filterBy: "images"
},
{
title: "Documents",
filterBy: "documents"
},
{
title: "Video",
filterBy: "video"
},
{
title: "Audio",
filterBy: "audio"
}
];
const filterAttachments = (type) => {
const attachments = note
? db.attachments.ofNote(note.id, "all")
: [...(db.attachments.all || [])];
isDocument;
switch (type) {
case "all":
return attachments;
case "images":
return attachments.filter((attachment) =>
isImage(attachment.metadata.type)
);
case "video":
return attachments.filter((attachment) =>
isVideo(attachment.metadata.type)
);
case "audio":
return attachments.filter((attachment) =>
isAudio(attachment.metadata.type)
);
case "documents":
return attachments.filter((attachment) =>
isDocument(attachment.metadata.type)
);
}
};
return (
<View
style={{
@@ -75,43 +162,71 @@ export const AttachmentDialog = ({ data }) => {
paddingHorizontal: 12
}}
>
<DialogHeader
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
<SheetProvider context="attachments-list" />
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading>Attachments</Heading>
<View
style={{
flexDirection: "row"
}}
>
{loading ? (
<ActivityIndicator
style={{
height: 40,
width: 40,
marginRight: 10
}}
size={SIZE.lg}
/>
) : (
<IconButton
name="check-all"
customStyle={{
height: 40,
width: 40,
marginRight: 10
}}
color={colors.pri}
size={SIZE.lg}
onPress={onCheck}
/>
)}
<IconButton
name="download"
customStyle={{
height: 40,
width: 40
}}
color={colors.pri}
onPress={() => {
DownloadAttachments.present(
"attachments-list",
attachments,
!!note
);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
}
setLoading(false);
}
}}
size={SIZE.lg}
/>
</View>
</View>
<Seperator />
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
) : null}
<FlatList
keyboardDismissMode="none"
@@ -119,6 +234,45 @@ export const AttachmentDialog = ({ data }) => {
maxToRenderPerBatch={10}
initialNumToRender={10}
windowSize={5}
stickyHeaderIndices={[0]}
ListHeaderComponent={
<ScrollView
style={{
width: "100%",
height: 50,
flexDirection: "row",
backgroundColor: colors.bg
}}
contentContainerStyle={{
minWidth: "100%"
}}
horizontal
>
{attachmentTypes.map((item) => (
<Button
type={currentFilter === item.filterBy ? "grayAccent" : "gray"}
key={item.title}
title={
item.title +
` (${filterAttachments(item.filterBy)?.length || 0})`
}
style={{
borderRadius: 0,
borderBottomWidth: 1,
flexGrow: 1,
borderBottomColor:
currentFilter !== item.filterBy
? "transparent"
: colors.accent
}}
onPress={() => {
setCurrentFilter(item.filterBy);
setAttachments(filterAttachments(item.filterBy));
}}
/>
))}
</ScrollView>
}
ListEmptyComponent={
<View
style={{
@@ -162,6 +316,6 @@ export const AttachmentDialog = ({ data }) => {
AttachmentDialog.present = (note) => {
presentSheet({
component: () => <AttachmentDialog data={note} />
component: () => <AttachmentDialog note={note} />
});
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { Platform, View } from "react-native";
import { View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import {
eSubscribeEvent,
@@ -75,7 +75,7 @@ const AuthModal = () => {
return !visible ? null : (
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
statusBarTranslucent={true}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
@@ -84,6 +84,7 @@ const AuthModal = () => {
background={colors.bg}
transparent={false}
animated={false}
avoidKeyboardResize
>
{currentAuthMode !== AuthMode.login ? (
<Signup
@@ -101,7 +102,7 @@ const AuthModal = () => {
<View
style={{
position: "absolute",
top: Platform.OS === "ios" ? insets.top : 0,
top: insets.top,
zIndex: 999,
flexDirection: "row",
alignItems: "center",
@@ -126,11 +127,11 @@ const AuthModal = () => {
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
<Button
title="Skip for now"
title="Skip"
onPress={() => {
hideAuth();
}}
iconSize={20}
iconSize={16}
type="gray"
iconPosition="right"
icon="chevron-right"

View File

@@ -18,9 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { TouchableOpacity, View } from "react-native";
import { SheetManager } from "react-native-actions-sheet";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
@@ -30,13 +29,12 @@ import SheetProvider from "../sheet-provider";
import { Progress } from "../sheets/progress";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { SVG } from "./background";
import { hideAuth } from "./common";
import { ForgotPassword } from "./forgot-password";
import { useLogin } from "./use-login";
import { useSettingStore } from "../../stores/use-setting-store";
const LoginSteps = {
emailAuth: 1,
@@ -64,6 +62,7 @@ export const Login = ({ changeMode }) => {
await sleep(500);
Progress.present();
});
const deviceMode = useSettingStore((state) => state.deviceMode);
useEffect(() => {
async () => {
@@ -82,62 +81,70 @@ export const Login = ({ changeMode }) => {
<>
<ForgotPassword />
<SheetProvider context="two_factor_verify" />
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: "100%",
alignSelf: "center",
height: "100%",
minHeight: "100%"
}}
>
<View
style={{
height: 250,
overflow: "hidden"
}}
>
<SvgView
src={SVG(colors.night ? colors.icon : "black")}
height={700}
/>
</View>
<View
style={{
width: "100%",
justifyContent: "center",
alignSelf: "center",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: 15
flexGrow: 0.8,
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.nav,
borderBottomWidth: 1,
marginBottom: 12,
borderBottomColor: colors.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,
borderColor: deviceMode !== "mobile" ? colors.border : null,
borderRadius: deviceMode !== "mobile" ? 20 : null,
marginTop: deviceMode !== "mobile" ? 50 : null,
width: deviceMode === "mobile" ? null : "50%"
}}
>
<View
style={{
flexDirection: "row"
}}
>
<View
style={{
width: 100,
height: 5,
backgroundColor: colors.accent,
borderRadius: 2,
marginRight: 7
}}
/>
<View
style={{
width: 20,
height: 5,
backgroundColor: colors.nav,
borderRadius: 2
}}
/>
</View>
<Heading
style={{
textAlign: "center"
marginBottom: 25,
marginTop: 10
}}
size={30}
color={colors.heading}
extraBold
size={SIZE.xxl}
>
Welcome back!
Login to your {"\n"}account
</Heading>
<Paragraph
style={{
textDecorationLine: "underline",
textAlign: "center",
marginTop: 5
}}
onPress={() => {
if (loading) return;
changeMode(1);
}}
size={SIZE.md}
>
{"Don't have an account? Sign up"}
</Paragraph>
</View>
<View
style={{
width: DDS.isTab
@@ -147,10 +154,10 @@ export const Login = ({ changeMode }) => {
: focused
? "100%"
: "99.9%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1,
alignSelf: "center"
alignSelf: "center",
paddingHorizontal: 20,
flexGrow: 1
}}
>
<Input
@@ -217,28 +224,22 @@ export const Login = ({ changeMode }) => {
<View
style={{
marginTop: 25,
alignSelf: "center"
marginTop: 25
}}
>
<Button
style={{
width: 250,
borderRadius: 100
}}
loading={loading}
onPress={() => {
if (loading) return;
login();
}}
style={{
width: 250,
borderRadius: 100
}}
fontSize={SIZE.md}
type="accent"
title={
loading
? null
: step === LoginSteps.emailAuth
? "Login"
: "Continue"
}
title={!loading ? "Continue" : null}
/>
{step === LoginSteps.passwordAuth && (
@@ -261,9 +262,34 @@ export const Login = ({ changeMode }) => {
type="errorShade"
/>
)}
{!loading ? (
<TouchableOpacity
onPress={() => {
if (loading) return;
changeMode(1);
}}
activeOpacity={0.8}
style={{
alignSelf: "center",
marginTop: 12,
paddingVertical: 12
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
Don't have an account?{" "}
<Paragraph
size={SIZE.xs + 1}
style={{ color: colors.accent }}
>
Sign up
</Paragraph>
</Paragraph>
</TouchableOpacity>
) : null}
</View>
</View>
</Animated.View>
</View>
</>
);
};

View File

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

View File

@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { Dimensions, View } from "react-native";
import Animated, { FadeInDown, FadeOutUp } from "react-native-reanimated";
import { TouchableOpacity, View } from "react-native";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { ToastEvent } from "../../services/event-manager";
@@ -32,11 +31,10 @@ import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Button } from "../ui/button";
import Input from "../ui/input";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { SVG } from "./background";
import { hideAuth } from "./common";
import { useSettingStore } from "../../stores/use-setting-store";
export const Signup = ({ changeMode, trial }) => {
const colors = useThemeStore((state) => state.colors);
@@ -50,6 +48,7 @@ export const Signup = ({ changeMode, trial }) => {
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
const setLastSynced = useUserStore((state) => state.setLastSynced);
const deviceMode = useSettingStore((state) => state.deviceMode);
const validateInfo = () => {
if (!password.current || !email.current || !confirmPassword.current) {
@@ -96,69 +95,77 @@ export const Signup = ({ changeMode, trial }) => {
return (
<>
<Animated.View
entering={FadeInDown}
exiting={FadeOutUp}
<View
style={{
borderRadius: DDS.isTab ? 5 : 0,
backgroundColor: colors.bg,
zIndex: 10,
width: "100%",
alignSelf: "center",
height: "100%",
minHeight: "100%"
}}
>
<View
style={{
height: 250,
overflow: "hidden"
flexGrow: 1,
justifyContent: "flex-end",
paddingHorizontal: 20,
backgroundColor: colors.nav,
marginBottom: 20,
borderBottomWidth: 1,
borderBottomColor: colors.border,
alignSelf: deviceMode !== "mobile" ? "center" : undefined,
borderWidth: deviceMode !== "mobile" ? 1 : null,
borderColor: deviceMode !== "mobile" ? colors.border : null,
borderRadius: deviceMode !== "mobile" ? 20 : null,
marginTop: deviceMode !== "mobile" ? 50 : null,
width: deviceMode === "mobile" ? null : "50%"
}}
>
<SvgView
src={SVG(colors.night ? colors.icon : "black")}
height={700}
/>
<View
style={{
flexDirection: "row"
}}
>
<View
style={{
width: 100,
height: 5,
backgroundColor: colors.accent,
borderRadius: 2,
marginRight: 7
}}
/>
<View
style={{
width: 20,
height: 5,
backgroundColor: colors.nav,
borderRadius: 2
}}
/>
</View>
<Heading
extraBold
style={{
marginBottom: 25,
marginTop: 10
}}
size={SIZE.xxl}
>
Create your {"\n"}account
</Heading>
</View>
<View
style={{
width: "100%",
justifyContent: "center",
alignSelf: "center",
paddingHorizontal: 12,
marginBottom: 30,
marginTop: Dimensions.get("window").height < 700 ? -75 : 15
}}
>
<Heading
style={{
textAlign: "center"
}}
size={30}
color={colors.heading}
>
Create your account
</Heading>
<Paragraph
style={{
textDecorationLine: "underline",
textAlign: "center"
}}
onPress={() => {
if (loading) return;
changeMode(0);
}}
size={SIZE.md}
>
Already have an account? Log in
</Paragraph>
</View>
<View
style={{
width: DDS.isTab ? "50%" : "100%",
padding: 12,
paddingHorizontal: 20,
backgroundColor: colors.bg,
flexGrow: 1,
alignSelf: "center"
alignSelf: "center",
flexGrow: 0.5
}}
>
<Input
@@ -217,37 +224,13 @@ export const Signup = ({ changeMode, trial }) => {
validationType="confirmPassword"
customValidator={() => password.current}
placeholder="Confirm password"
marginBottom={5}
marginBottom={12}
onSubmit={signup}
/>
<View
style={{
marginTop: 25,
alignSelf: "center"
}}
>
<Button
style={{
width: 250,
borderRadius: 100
}}
loading={loading}
onPress={() => {
if (loading) return;
signup();
}}
type="accent"
title={loading ? null : "Agree and continue"}
/>
</View>
<Paragraph
style={{
textAlign: "center",
position: "absolute",
bottom: 0,
alignSelf: "center",
marginBottom: 20
marginBottom: 25
}}
size={SIZE.xs}
color={colors.icon}
@@ -263,7 +246,7 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.accent}
>
terms of service{" "}
Terms of Service{" "}
</Paragraph>
and{" "}
<Paragraph
@@ -276,11 +259,46 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.accent}
>
privacy policy.
</Paragraph>
Privacy Policy.
</Paragraph>{" "}
You also agree to recieve marketing emails from us which you can
opt-out of from app settings.
</Paragraph>
<Button
title={!loading ? "Continue" : null}
type="accent"
loading={loading}
onPress={signup}
fontSize={SIZE.md}
style={{
marginRight: 12,
width: 250,
borderRadius: 100
}}
/>
<TouchableOpacity
onPress={() => {
if (loading) return;
changeMode(0);
}}
activeOpacity={0.8}
style={{
alignSelf: "center",
marginTop: 12,
paddingVertical: 12
}}
>
<Paragraph size={SIZE.xs + 1} color={colors.icon}>
Already have an account?{" "}
<Paragraph size={SIZE.xs + 1} style={{ color: colors.accent }}>
Login
</Paragraph>
</Paragraph>
</TouchableOpacity>
</View>
</Animated.View>
</View>
</>
);
};

View File

@@ -36,6 +36,7 @@ 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";
const DialogProvider = () => {
const colors = useThemeStore((state) => state.colors);
@@ -60,6 +61,7 @@ const DialogProvider = () => {
{loading ? null : <Expiring />}
<AnnouncementDialog />
<SessionExpired />
<PDFPreview />
</>
);
};

View File

@@ -46,7 +46,8 @@ const BaseDialog = ({
animated = true,
bounce = true,
closeOnTouch = true,
useSafeArea = true
useSafeArea = true,
avoidKeyboardResize = false
}) => {
const floating = useIsFloatingKeyboard();
@@ -94,7 +95,7 @@ const BaseDialog = ({
}}
>
<KeyboardAvoidingView
enabled={!floating && Platform.OS === "ios"}
enabled={!floating && Platform.OS === "ios" && !avoidKeyboardResize}
behavior="padding"
>
<BouncingView

View File

@@ -0,0 +1,324 @@
/*
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, { useEffect, useRef, useState } from "react";
import { Dimensions, TextInput, View } from "react-native";
import {
addOrientationListener,
removeOrientationListener
} from "react-native-orientation";
import Pdf from "react-native-pdf";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import { cacheDir } from "../../../common/filesystem/utils";
import { useAttachmentProgress } from "../../../hooks/use-attachment-progress";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import BaseDialog from "../../dialog/base-dialog";
import { presentDialog } from "../../dialog/functions";
import SheetProvider from "../../sheet-provider";
import { IconButton } from "../../ui/icon-button";
import { ProgressBarComponent } from "../../ui/svg/lazy";
import Paragraph from "../../ui/typography/paragraph";
import { sleep } from "../../../utils/time";
import { MMKV } from "../../../common/database/mmkv";
const WIN_WIDTH = Dimensions.get("window").width;
const WIN_HEIGHT = Dimensions.get("window").height;
const attachmentSnapshotsKey = "___attachmentsnapshots";
const usePDFSnapshot = (attachment) => {
const snapshots = useRef(MMKV.getMap(attachmentSnapshotsKey) || {});
const snapshot = useRef(snapshots[attachment?.id]);
function saveSnapshot(ss) {
if (!attachment) return;
snapshots.current[attachment.id] = ss;
MMKV.setMap(attachmentSnapshotsKey, snapshots.current);
snapshot.current = snapshots.current[attachment.id];
}
return [snapshot, saveSnapshot];
};
const PDFPreview = () => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [pdfSource, setPDFSource] = useState();
const [loading, setLoading] = useState(false);
const [width, setWidth] = useState(WIN_WIDTH);
const insets = useGlobalSafeAreaInsets();
const [numPages, setNumPages] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const inputRef = useRef();
const pdfRef = useRef();
const [attachment, setAttachment] = useState(null);
const [password, setPassword] = useState("");
const [progress] = useAttachmentProgress(attachment);
const [snapshot, saveSnapshot] = usePDFSnapshot(attachment);
const snapshotValue = useRef(snapshot.current);
useEffect(() => {
eSubscribeEvent("PDFPreview", open);
return () => {
eUnSubscribeEvent("PDFPreview", open);
};
}, []);
const onOrientationChange = (o) => {
if (o.includes("LANDSCAPE")) {
setWidth(WIN_HEIGHT);
} else {
setWidth(WIN_WIDTH);
}
};
useEffect(() => {
addOrientationListener(onOrientationChange);
return () => {
removeOrientationListener(onOrientationChange);
};
}, []);
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 close = () => {
setPDFSource(null);
setVisible(false);
setPassword("");
};
const onError = async (error) => {
if (error?.message === "Password required or incorrect password.") {
await sleep(300);
presentDialog({
context: attachment?.metadata?.hash,
input: true,
inputPlaceholder: "Enter password",
positiveText: "Unlock",
title: "Decrypt",
paragraph: "Please input password to view pdf.",
positivePress: (value) => {
setTimeout(() => {
setPassword(value);
});
},
onClose: () => {
close();
}
});
}
};
return (
visible && (
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
<SheetProvider context={attachment?.metadata?.hash} />
<Dialog context={attachment?.metadata?.hash} />
<View
style={{
width: "100%",
height: "100%",
backgroundColor: "black"
}}
>
{loading ? (
<Animated.View
exiting={FadeOut}
style={{
flex: 1,
justifyContent: "center",
alignItems: "center"
}}
>
<ProgressBarComponent
indeterminate={!progress}
color={colors.accent}
borderColor="transparent"
progress={parseInt(progress?.value || "100") / 100}
/>
<Paragraph
style={{
marginTop: 10
}}
color={colors.light}
>
Loading {`${progress?.percent ? `(${progress?.percent})` : ""}`}
... Please wait
</Paragraph>
</Animated.View>
) : (
<>
<View
style={{
width: "100%",
height: 50,
marginTop: insets.top,
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 12,
paddingLeft: 6
}}
>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
color={colors.light}
name="arrow-left"
onPress={close}
customStyle={{
marginRight: 12
}}
size={SIZE.xxl}
/>
</View>
<View
style={{
flexDirection: "row",
alignItems: "center",
marginRight: 12
}}
>
<TextInput
ref={inputRef}
defaultValue={currentPage + ""}
style={{
color: colors.pri,
padding: 0,
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0,
paddingVertical: 0,
height: 25,
backgroundColor: colors.nav,
width: 40,
textAlign: "center",
marginRight: 4,
borderRadius: 3,
fontFamily: "OpenSans-Regular"
}}
selectTextOnFocus
keyboardType="decimal-pad"
onSubmitEditing={(event) => {
setCurrentPage(event.nativeEvent.text);
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
}}
blurOnSubmit
/>
<Paragraph color={colors.light}>/{numPages}</Paragraph>
</View>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
color={colors.light}
name="download"
onPress={() => {
downloadAttachment(attachment.metadata.hash, false);
}}
/>
</View>
</View>
{pdfSource ? (
<Animated.View
style={{
flex: 1
}}
entering={FadeIn}
>
<Pdf
source={{
uri: pdfSource
}}
ref={pdfRef}
onLoadComplete={(numberOfPages) => {
setNumPages(numberOfPages);
}}
onPageChanged={(page) => {
setCurrentPage(page);
inputRef.current?.setNativeProps({
text: page + ""
});
saveSnapshot({
currentPage: page,
scale: snapshot?.current?.scale
});
}}
// scale={snapshotValue.current?.scale}
// onScaleChanged={(scale) => {
// saveSnapshot({
// currentPage: snapshot?.current?.currentPage,
// scale: scale
// });
// }}
page={snapshotValue?.current?.currentPage}
password={password}
maxScale={6}
onError={onError}
onPressLink={(uri) => {
console.log(`Link pressed: ${uri}`);
}}
style={{
flex: 1,
width: width,
height: Dimensions.get("window").height
}}
/>
</Animated.View>
) : null}
</>
)}
</View>
</BaseDialog>
)
);
};
export default PDFPreview;

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -17,12 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import notifee from "@notifee/react-native";
import React, { useCallback, useEffect, useRef } from "react";
import { Platform, View } from "react-native";
import RNBootSplash from "react-native-bootsplash";
import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
import { DatabaseLogger, db } from "../../common/database";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import { eSendEvent, presentSheet } from "../../services/event-manager";
@@ -35,10 +37,11 @@ import { useNoteStore } from "../../stores/use-notes-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { AndroidModule } from "../../utils";
import { eOpenAnnouncementDialog } from "../../utils/events";
import { getGithubVersion } from "../../utils/github-version";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { SVG } from "../auth/background";
import Migrate from "../sheets/migrate";
import NewFeature from "../sheets/new-feature/index";
import { Update } from "../sheets/update";
@@ -46,13 +49,9 @@ import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import Config from "react-native-config";
import { getGithubVersion } from "../../utils/github-version";
import notifee from "@notifee/react-native";
const Launcher = React.memo(
function Launcher() {
@@ -69,7 +68,8 @@ const Launcher = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const dbInitCompleted = useRef(false);
const verifying = useRef(false);
const loadNotes = useCallback(async () => {
if (verifyUser) {
return;
@@ -80,28 +80,25 @@ const Launcher = React.memo(
initialize();
setImmediate(() => {
setLoading(false);
if (!dbInitCompleted.current) {
setImmediate(() => doAppLoadActions());
}
});
});
});
}, [doAppLoadActions, setLoading, verifyUser]);
}, [setLoading, verifyUser]);
const init = useCallback(async () => {
if (!dbInitCompleted.current) {
await RNBootSplash.hide({ fade: true });
await loadDatabase();
if (!db.isInitialized) {
RNBootSplash.hide({ fade: true });
DatabaseLogger.info("Initializing database");
await db.init();
dbInitCompleted.current = true;
}
if (db.migrations.required() && !verifyUser) {
presentSheet({
component: <Migrate />,
onClose: async () => {
await db.init();
if (!db.isInitialized) {
await db.init();
}
loadNotes();
},
disableClosing: true
@@ -120,9 +117,6 @@ const Launcher = React.memo(
if (!loading) {
doAppLoadActions();
}
return () => {
dbInitCompleted.current = false;
};
}, [doAppLoadActions, loading]);
const doAppLoadActions = useCallback(async () => {
@@ -136,7 +130,6 @@ const Launcher = React.memo(
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;
if (await checkForRateAppRequest()) return;
if (await checkNeedsBackup()) return;
if (await PremiumService.getRemainingTrialDaysStatus()) return;
if (introCompleted) {
@@ -179,25 +172,13 @@ const Launcher = React.memo(
return false;
};
const checkNeedsBackup = async () => {
return false;
// let { nextBackupRequestTime, reminder } = SettingsService.get();
// if (reminder === 'off' || !reminder) {
// if (nextBackupRequestTime < Date.now()) {
// presentSheet({
// title: 'Backup & restore',
// paragraph: 'Please enable automatic backups to keep your data safe',
// component: <SettingsBackupAndRestore isSheet={true} />
// });
// return true;
// }
// }
// return false;
};
const onUnlockBiometrics = useCallback(async () => {
if (!(await BiometricService.isBiometryAvailable())) return;
if (Platform.OS === "android") {
const activityName = await AndroidModule.getActivityName();
if (activityName !== "MainActivity") return;
}
let verified = await BiometricService.validateUser(
"Unlock to access your notes",
""
@@ -206,6 +187,9 @@ const Launcher = React.memo(
setVerifyUser(false);
enabled(false);
password.current = null;
setTimeout(() => {
verifying.current = false;
}, 1);
}
}, [setVerifyUser]);
@@ -214,7 +198,10 @@ const Launcher = React.memo(
}, [init, verifyUser]);
useEffect(() => {
if (verifying.current || useUserStore.getState().shouldBlockVerifyUser)
return;
if (verifyUser && appState === "active") {
verifying.current = true;
onUnlockBiometrics();
}
}, [appState, onUnlockBiometrics, verifyUser]);
@@ -240,18 +227,10 @@ const Launcher = React.memo(
width: "100%",
height: "100%",
position: "absolute",
zIndex: 999
zIndex: 999,
justifyContent: "center"
}}
>
<View
style={{
height: 250,
overflow: "hidden"
}}
>
<SvgView src={SVG(colors.night ? "white" : "black")} height={700} />
</View>
<View
style={{
flex: 1,
@@ -287,14 +266,13 @@ const Launcher = React.memo(
textAlign: "center"
}}
>
Unlock to access your notes
Unlock your notes
</Heading>
<Paragraph
style={{
alignSelf: "center",
textAlign: "center",
fontSize: SIZE.md,
maxWidth: "90%"
}}
>
@@ -305,8 +283,7 @@ const Launcher = React.memo(
style={{
width: "100%",
padding: 12,
backgroundColor: colors.bg,
flexGrow: 1
backgroundColor: colors.bg
}}
>
{user ? (
@@ -346,14 +323,9 @@ const Launcher = React.memo(
<Button
title="Unlock with Biometrics"
width={250}
height={45}
style={{
borderRadius: 100
}}
onPress={onUnlockBiometrics}
icon={"fingerprint"}
type={user ? "grayAccent" : "accent"}
fontSize={SIZE.md}
/>
</View>
</View>

View File

@@ -178,16 +178,29 @@ const NoteItem = ({
</View>
) : null}
<Heading
numberOfLines={1}
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
style={{
flexWrap: "wrap"
}}
size={SIZE.md}
>
{item.title}
</Heading>
{compactMode ? (
<Paragraph
numberOfLines={1}
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
style={{
flexWrap: "wrap"
}}
size={SIZE.sm}
>
{item.title}
</Paragraph>
) : (
<Heading
numberOfLines={1}
color={COLORS_NOTE[item.color?.toLowerCase()] || colors.heading}
style={{
flexWrap: "wrap"
}}
size={SIZE.md}
>
{item.title}
</Heading>
)}
{item.headline && !compactMode ? (
<Paragraph
@@ -202,161 +215,223 @@ const NoteItem = ({
</Paragraph>
) : null}
<View
style={{
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
width: "100%",
marginTop: 5,
height: SIZE.md + 2
}}
>
{!isTrash ? (
<>
{item.conflicted ? (
<Icon
name="alert-circle"
{compactMode ? null : (
<View
style={{
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
width: "100%",
marginTop: 5,
height: SIZE.md + 2
}}
>
{!isTrash ? (
<>
{item.conflicted ? (
<Icon
name="alert-circle"
style={{
marginRight: 6
}}
size={SIZE.sm}
color={colors.red}
/>
) : null}
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.icon,
marginRight: 6
}}
size={SIZE.sm}
color={colors.red}
time={item[dateBy]}
updateFrequency={
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
}
/>
) : null}
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.icon,
marginRight: 6
}}
time={item[dateBy]}
updateFrequency={
Date.now() - item[dateBy] < 60000 ? 2000 : 60000
}
/>
{attachmentCount > 0 ? (
<View
{attachmentCount > 0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
marginRight: 6
}}
>
<Icon
name="attachment"
size={SIZE.md}
color={colors.icon}
/>
<Paragraph color={colors.icon} size={SIZE.xs}>
{attachmentCount}
</Paragraph>
</View>
) : null}
{item.pinned ? (
<Icon
testID="icon-pinned"
name="pin-outline"
size={SIZE.sm}
style={{
marginRight: 6
}}
color={
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
}
/>
) : null}
{item.locked ? (
<Icon
name="lock"
testID="note-locked-icon"
size={SIZE.sm}
style={{
marginRight: 6
}}
color={colors.icon}
/>
) : null}
{item.favorite ? (
<Icon
testID="icon-star"
name="star-outline"
size={SIZE.md}
style={{
marginRight: 6
}}
color="orange"
/>
) : null}
{!isTrash && !compactMode && tags
? tags.map((item) =>
item.id ? (
<Button
title={"#" + item.alias}
key={item.id}
height={23}
type="gray"
textStyle={{
textDecorationLine: "underline"
}}
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
fontSize={SIZE.xs}
style={{
borderRadius: 5,
paddingHorizontal: 6,
marginRight: 4,
zIndex: 10,
maxWidth: tags.length > 1 ? 130 : null
}}
onPress={() => navigateToTag(item)}
/>
) : null
)
: null}
</>
) : (
<>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
flexDirection: "row",
alignItems: "center",
marginRight: 6
}}
>
<Icon name="attachment" size={SIZE.md} color={colors.icon} />
<Paragraph color={colors.icon} size={SIZE.xs}>
{attachmentCount}
</Paragraph>
</View>
) : null}
Deleted on{" "}
{item && item.dateDeleted
? new Date(item.dateDeleted).toISOString().slice(0, 10)
: null}
</Paragraph>
{item.pinned ? (
<Icon
testID="icon-pinned"
name="pin-outline"
size={SIZE.sm}
<Paragraph
color={colors.accent}
size={SIZE.xs}
style={{
marginRight: 6
}}
color={
COLORS_NOTE[item.color?.toLowerCase()] || colors.accent
}
/>
) : null}
{item.locked ? (
<Icon
name="lock"
testID="note-locked-icon"
size={SIZE.sm}
style={{
marginRight: 6
}}
color={colors.icon}
/>
) : null}
{item.favorite ? (
<Icon
testID="icon-star"
name="star"
size={SIZE.md}
style={{
marginRight: 6
}}
color="orange"
/>
) : null}
{!isTrash && !compactMode && tags
? tags.map((item) =>
item.id ? (
<Button
title={"#" + item.alias}
key={item.id}
height={23}
type="gray"
textStyle={{
textDecorationLine: "underline"
}}
hitSlop={{ top: 8, bottom: 12, left: 0, right: 0 }}
fontSize={SIZE.xs}
style={{
borderRadius: 5,
paddingHorizontal: 6,
marginRight: 4,
zIndex: 10,
maxWidth: tags.length > 1 ? 130 : null
}}
onPress={() => navigateToTag(item)}
/>
) : null
)
: null}
</>
) : (
<>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
marginRight: 6
}}
>
Deleted on{" "}
{item && item.dateDeleted
? new Date(item.dateDeleted).toISOString().slice(0, 10)
: null}
</Paragraph>
<Paragraph
color={colors.accent}
size={SIZE.xs}
style={{
marginRight: 6
}}
>
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
</Paragraph>
</>
)}
</View>
>
{item.itemType[0].toUpperCase() + item.itemType.slice(1)}
</Paragraph>
</>
)}
</View>
)}
</View>
<IconButton
testID={notesnook.listitem.menu}
color={colors.pri}
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}
customStyle={{
justifyContent: "center",
height: 35,
width: 35,
borderRadius: 100,
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
/>
>
{compactMode ? (
<>
{item.conflicted ? (
<Icon
name="alert-circle"
style={{
marginRight: 6
}}
size={SIZE.sm}
color={colors.red}
/>
) : null}
{item.locked ? (
<Icon
name="lock"
testID="note-locked-icon"
size={SIZE.sm}
style={{
marginRight: 6
}}
color={colors.icon}
/>
) : null}
{item.favorite ? (
<Icon
testID="icon-star"
name="star-outline"
size={SIZE.md}
style={{
marginRight: 6
}}
color="orange"
/>
) : null}
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.icon,
marginRight: 6
}}
time={item[dateBy]}
updateFrequency={Date.now() - item[dateBy] < 60000 ? 2000 : 60000}
/>
</>
) : null}
<IconButton
testID={notesnook.listitem.menu}
color={colors.pri}
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}
customStyle={{
justifyContent: "center",
height: 35,
width: 35,
borderRadius: 100,
alignItems: "center"
}}
/>
</View>
</>
);
};

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