Compare commits

...

301 Commits

Author SHA1 Message Date
Abdullah Atta
31726f6d86 ci: use npx electron-builder directly 2023-06-20 20:31:32 +05:00
Abdullah Atta
548eda9a26 desktop: do not load native crypto on ARM windows app 2023-06-20 20:24:10 +05:00
Abdullah Atta
dc6fa89c9c desktop: fix build script 2023-06-20 20:21:14 +05:00
Abdullah Atta
cdda26f32f Merge branch 'master' of https://github.com/streetwriters/notesnook 2023-06-20 20:17:07 +05:00
Abdullah Atta
a8470c9fea desktop: make builds work on windows 2023-06-20 20:16:48 +05:00
Abdullah Atta
4fa2bc6bb6 desktop: set cwd as data path for portable binaries 2023-06-20 20:15:58 +05:00
Abdullah Atta
8aacf49f07 ci: minor changes to desktop publish workflow 2023-06-20 18:41:00 +05:00
Abdullah Atta
d14c712b79 web: only run top-level-await transform in prod builds 2023-06-20 17:50:23 +05:00
Abdullah Atta
fb90ef3137 web: support top-level-await transform in desktop builds 2023-06-20 17:49:23 +05:00
Abdullah Atta
f733e6e95b core: convert object-id to es2015 class 2023-06-20 17:48:48 +05:00
Abdullah Atta
3ae3465e84 web: bump version to 2.5.3 2023-06-20 16:00:45 +05:00
Abdullah Atta
9abe85f1c9 web: minor UI fixes 2023-06-20 16:00:45 +05:00
ammarahm-ed
6194367c1e mobile: release 2.5.3 2023-06-20 14:43:48 +05:00
Abdullah Atta
4e793317ca desktop: set NO_COLOR=true when running scripts 2023-06-20 14:43:29 +05:00
Abdullah Atta
eb6927cbce web: add image source cache for caching blobs 2023-06-20 14:42:20 +05:00
Abdullah Atta
b7762ceaae crypto: make base64 enc compatible on node & browser 2023-06-20 13:18:11 +05:00
ammarahm-ed
53b545f84c mobile: enable encrypted backups on login 2023-06-20 12:43:37 +05:00
Abdullah Atta
4195150005 web: enable encrypted backups for all logged in users 2023-06-20 12:43:37 +05:00
Abdullah Atta
0a8c045f6f web: disable auto backups by default 2023-06-20 12:43:37 +05:00
ammarahm-ed
f4fe2c6a65 mobile: enable encrypted backups by default 2023-06-20 12:43:37 +05:00
ammarahm-ed
11e109740b core: allow encrypted backups for all users 2023-06-20 12:43:37 +05:00
ammarahm-ed
d882c7cf9f mobile: disable default auto-backup on 2023-06-20 12:43:37 +05:00
Abdullah Atta
eb8b58a559 web: migrate navigation to typescript 2023-06-20 12:36:21 +05:00
ammarahm-ed
2b94212552 mobile: fix module resolution 2023-06-20 12:04:38 +05:00
Ammar Ahmed
04d122c47c mobile: fix input hides behind keyboard on focus (#2767) 2023-06-20 11:33:48 +05:00
Ammar Ahmed
6c9c99681e mobile: fix scroll into view on keyboard shown (#2762) 2023-06-19 19:04:53 +05:00
ammarahm-ed
f0a4a61e1d mobile: add email support button in settings 2023-06-19 16:15:55 +05:00
Abdullah Atta
f88c610495 editor: make web clips selectable in readonly mode 2023-06-19 13:16:39 +05:00
Abdullah Atta
f9cb9fa3cc web: add option to contact us via email in settings 2023-06-19 12:54:34 +05:00
Abdullah Atta
3c6622ccf0 web: create vault should be under pro 2023-06-19 12:35:43 +05:00
ammarahm-ed
df82e84c83 web: add default dropdown value text 2023-06-19 12:19:56 +05:00
ammarahm-ed
abd136c7c2 web: disable import button when no files selected 2023-06-19 12:19:56 +05:00
Abdullah Atta
c61082cdee web: add manage attachments button in settings 2023-06-19 12:19:42 +05:00
ammarahm-ed
b9b21a760a mobile: limit input value between min/max 2023-06-19 11:58:50 +05:00
ammarahm-ed
acc852e23b web: limit input value between mix/max 2023-06-19 11:58:50 +05:00
ammarahm-ed
36ae9383ba web: fix onChange not fired on input 2023-06-19 11:42:39 +05:00
ammarahm-ed
c04df4b4c8 mobile: fix failing tests 2023-06-19 07:49:40 +05:00
Abdullah Atta
5c849c39b4 web: update sync status on auto sync completion 2023-06-17 19:13:54 +05:00
Abdullah Atta
9bae058d16 crypto: force convert IV to urlsafe base64 variant 2023-06-17 19:13:30 +05:00
Abdullah Atta
db2d48a083 web: fix navigation on small screens & mobiles 2023-06-17 18:20:13 +05:00
Abdullah Atta
628d3bd9ff core: do not include trashed notes in total notebook notes 2023-06-17 18:20:04 +05:00
Abdullah Atta
cdbca8ac22 web: fix crash on viewing a merge conflict 2023-06-17 18:19:58 +05:00
Abdullah Atta
8d2c0762db web: allow focusing editor by clicking at the bottom 2023-06-17 18:19:52 +05:00
Abdullah Atta
992f4fc697 web: fix pane sizes getting reset randomly on startup 2023-06-17 18:19:39 +05:00
Abdullah Atta
cb1d09904b web: remove settings test from navigation tests 2023-06-17 13:36:01 +05:00
Abdullah Atta
2441a4505e web: remove react-toggle 2023-06-17 13:36:01 +05:00
Abdullah Atta
79aae86906 web: fix broken build 2023-06-17 13:36:01 +05:00
Abdullah Atta
cd792625ac web: use named imports for icons 2023-06-17 13:36:01 +05:00
Abdullah Atta
605e045ff7 web: fix type in SearchViewModel 2023-06-17 13:36:01 +05:00
Abdullah Atta
8c17233e67 web: move dialogs out to top-most level 2023-06-17 13:36:01 +05:00
Abdullah Atta
08480188fa web: remove progress-bar component 2023-06-17 13:36:01 +05:00
Abdullah Atta
87402251a5 web: add types for svgr 2023-06-17 13:36:01 +05:00
Abdullah Atta
e3a818378d core: userId is not mandatory when sending bug report 2023-06-17 13:36:01 +05:00
Abdullah Atta
6d82b0a5ba common: use fixed react version at 17.0.2 2023-06-17 13:36:01 +05:00
Abdullah Atta
e3f0d0ada7 web: load polyfills before everything else 2023-06-17 13:36:01 +05:00
Abdullah Atta
9e2f54fd0b web: use utils from @notesnook/common 2023-06-17 13:36:01 +05:00
Abdullah Atta
19eb512198 common: add utils for debounce, random & string 2023-06-17 13:36:01 +05:00
ammarahm-ed
86a7fd9b93 mobile: refactor 2023-06-17 13:36:01 +05:00
ammarahm-ed
fa7900aef6 core: fix tests 2023-06-17 13:36:01 +05:00
ammarahm-ed
561d7a8bbc common: add @notesnook/core dep 2023-06-17 13:36:01 +05:00
ammarahm-ed
fb616a246e mobile: refactor 2023-06-17 13:36:01 +05:00
ammarahm-ed
c881e74aae web: call database.setup() before db init 2023-06-17 13:36:01 +05:00
ammarahm-ed
0f2acb2801 core: Init DB instance without platform APIs 2023-06-17 13:36:01 +05:00
Abdullah Atta
9b5e2c108b editor: persist text alignment & direction on heading creation 2023-06-16 19:11:22 +05:00
ammarahm-ed
ba900b3a0f web: fix trial expired notice shows to new users 2023-06-16 19:10:46 +05:00
ammarahm-ed
49df1bcd9e mobile: fix trial expired notice shows to new users 2023-06-16 19:10:46 +05:00
Abdullah Atta
5d1ba7fa0a web: new dialog based settings (#2745) 2023-06-16 19:10:02 +05:00
ammarahm-ed
a6d5a618b0 mobile: picker follow time format set in settings 2023-06-16 18:57:29 +05:00
Abdullah Atta
8f4bd20253 web: fix delete all notes in topic test 2023-06-16 16:54:04 +05:00
ammarahm-ed
8b4270f7df web: add null checks 2023-06-16 16:54:04 +05:00
ammarahm-ed
9a4877bdbd mobile: include topic notes in notebook search 2023-06-16 16:54:04 +05:00
ammarahm-ed
0a45e963a6 web: lookup topic notes at notebook level 2023-06-16 16:54:04 +05:00
ammarahm-ed
fb7b306bc5 web: fix search in topic returns no results 2023-06-16 16:54:04 +05:00
Abdullah Atta
4035751264 clipper(web): bump version to 0.2.2 2023-06-16 15:38:51 +05:00
Ammar Ahmed
07bb35395c clipper: fix duplicate notes created on save (#2722)
* clipper: fix duplicate notes created on save

* clipper(web): move singleton checks inside the fn

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-06-16 15:15:14 +05:00
ammarahm-ed
09cd19d3b7 core: fix incorrect snooze time format 2023-06-16 10:23:46 +05:00
ammarahm-ed
a3988fd309 mobile: fix image replaces content if note is locked
& app lock is also enabled
2023-06-16 10:06:06 +05:00
ammarahm-ed
574c4cc8a9 mobile: fix appending link to empty note stuck 2023-06-15 16:43:54 +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
687 changed files with 46758 additions and 119278 deletions

View File

@@ -20,6 +20,7 @@ const SCOPES = [
"logger",
"theme",
"core",
"fs",
"clipper",
"config",
"ci",
@@ -27,6 +28,7 @@ const SCOPES = [
"docs",
"refactor",
"misc",
"common",
"global"
];

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 nx run release --project @notesnook/desktop -- --variant=mas
npx electron-builder --mac mas --universal -p never
working-directory: ./apps/web/desktop
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
else
npx electron-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,31 +178,27 @@ 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
working-directory: ./apps/desktop
- name: Build AppImage
env:
@@ -227,13 +209,13 @@ jobs:
else
npx electron-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,29 @@ 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
- name: Build
run: npx nx run release --project @notesnook/desktop
- name: Build app
- name: Publish
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

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -2,32 +2,46 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.4.8",
"version": "2.5.3",
"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",
"postinstall": "patch-package"
},
"author": {
@@ -40,12 +54,13 @@
"productName": "Notesnook",
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
"artifactName": "notesnook_${os}_${arch}.${ext}",
"npmRebuild": false,
"files": [
"!*.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,110 @@
/*
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 { existsSync } from "fs";
import { argv, os } from "zx";
import * as childProcess from "child_process";
const sodiumNativePrebuildPath = (arch) =>
path.join(
`node_modules`,
`@notesnook`,
`crypto`,
`node_modules`,
`@notesnook`,
`sodium`,
`node_modules`,
`sodium-native`,
`prebuilds`,
`${os.platform()}-${arch}`
);
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
if (argv.rebuild) {
await fs.rm("./build/", { force: true, recursive: true });
await exec(`cd ${webAppPath} && npm run build:desktop`);
await fs.cp(path.join(webAppPath, "build"), "build", {
recursive: true,
force: true
});
}
if (argv.variant === "mas") {
await exec(`npm run bundle:mas`);
} else {
await exec(`npm run bundle`);
}
await exec(`npx tsc`);
if (existsSync(sodiumNativePrebuildPath("x64"))) {
console.log("copying sodium-native-x64");
await fs.cp(
sodiumNativePrebuildPath("x64"),
path.join("build", "prebuilds", `${process.platform}-x64`),
{
recursive: true,
force: true
}
);
}
if (existsSync(sodiumNativePrebuildPath("ia32"))) {
console.log("copying sodium-native-ia32");
await fs.cp(
sodiumNativePrebuildPath("ia32"),
path.join("build", "prebuilds", `${process.platform}-ia32`),
{
recursive: true,
force: true
}
);
}
if (existsSync(sodiumNativePrebuildPath("arm64"))) {
console.log("copying sodium-native-arm64");
await fs.cp(
sodiumNativePrebuildPath("arm64"),
path.join("build", "prebuilds", `${process.platform}-arm64`),
{
recursive: true,
force: true
}
);
}
if (argv.run) {
if (process.platform === "win32") {
await exec(`npx electron-builder --win --x64`);
// await exec(`.\\output\\notesnook_win_x64_portable.exe`);
} else {
await exec(`npx electron-builder --linux AppImage:x64`);
await exec(`./output/notesnook_linux_x86_64.AppImage`);
}
}
async function exec(cmd) {
return childProcess.execSync(cmd, {
env: process.env,
stdio: "inherit"
});
}

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

@@ -0,0 +1,116 @@
/*
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 bundle`;
await $`npx 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("Network: use --host to expose")
);
isServerRunning = true;
}
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;
$.env.NO_COLOR = "true";
$.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

@@ -0,0 +1,41 @@
/*
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 { compressionRouter } from "./compression";
import { osIntegrationRouter } from "./os-integration";
import { spellCheckerRouter } from "./spell-checker";
import { updaterRouter } from "./updater";
import { bridgeRouter } from "./bridge";
const t = initTRPC.create();
export const router = t.router({
compress: compressionRouter,
integration: osIntegrationRouter,
spellChecker: spellCheckerRouter,
updater: updaterRouter,
bridge: bridgeRouter
});
export const api = router.createCaller({});
// 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,41 @@ 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().optional())
.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,33 +18,34 @@ 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";
import path from "path";
if (!RELEASE) {
require("electron-reloader")(module);
}
if (process.platform == "win32" && process.env.PORTABLE_EXECUTABLE_DIR) {
console.log("Portable app: true");
const root = path.join(process.env.PORTABLE_EXECUTABLE_DIR, "Notesnook");
app.setPath("appData", path.join(root, "AppData"));
app.setPath("documents", path.join(root, "Documents"));
app.setPath("userData", path.join(root, "UserData"));
}
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -55,15 +56,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 +78,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 +107,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 +125,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 +149,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 +163,8 @@ function createURL(options) {
return url;
}
function setupDesktopIntegration() {
const desktopIntegration = getDesktopIntegration();
function setupDesktopIntegration(hidden: boolean) {
const desktopIntegration = config.desktopSettings;
if (
desktopIntegration.closeToSystemTray ||
@@ -179,42 +173,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,49 @@
/*
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 type { NNCrypto } from "@notesnook/crypto";
import { ipcRenderer } from "electron";
declare global {
var os: string;
var electronTRPC: RendererGlobalElectronTRPC;
var NativeNNCrypto: (new () => NNCrypto) | undefined;
}
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 =
process.platform === "win32" &&
process.arch !== "x64" &&
process.arch !== "ia32"
? undefined
: require("@notesnook/crypto").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

@@ -17,13 +17,19 @@ 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 { CancellationToken } from "builder-util-runtime";
import { autoUpdater } from "electron-updater";
import { EVENTS } from "../../events";
import { sendMessageToRenderer } from "../utils";
export default () => {
sendMessageToRenderer(EVENTS.updateDownloadProgress, { progress: 0 });
autoUpdater.cancellationToken = new CancellationToken();
autoUpdater.downloadUpdate(autoUpdater.cancellationToken);
};
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 getZoomFactor() {
let factor = parseFloat(JSONStorage.get("zoomFactor"));
return isNaN(factor) ? 1.0 : factor;
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 setZoomFactor(factor) {
return JSONStorage.set("zoomFactor", factor.toString());
export function isFlatpak() {
return existsSync("/.flatpak-info");
}
export { setZoomFactor, getZoomFactor };

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

@@ -17,15 +17,6 @@ jobs:
with:
persist-credentials: false
- name: Setup npmrc
run: |
echo "registry=https://npm.pkg.github.com" > .npmrc
echo "@streetwriters:registry=https://npm.pkg.github.com" >> .npmrc
echo "//npm.pkg.github.com/:_authToken=${{secrets.PACKAGES_TOKEN}}" >> .npmrc
- name: Setup yarnrc
run: echo "\"@streetwriters:registry\" \"https://npm.pkg.github.com\"" > .yarnrc
- uses: actions/setup-node@master
with:
node-version: "16"

View File

@@ -17,15 +17,6 @@ jobs:
with:
persist-credentials: false
- name: Setup npmrc
run: |
echo "registry=https://npm.pkg.github.com" > .npmrc
echo "@streetwriters:registry=https://npm.pkg.github.com" >> .npmrc
echo "//npm.pkg.github.com/:_authToken=${{secrets.PACKAGES_TOKEN}}" >> .npmrc
- name: Setup yarnrc
run: echo "\"@streetwriters:registry\" \"https://npm.pkg.github.com\"" > .yarnrc
- uses: actions/setup-node@master
with:
node-version: "16"

View File

@@ -16,42 +16,31 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import "react-native-gesture-handler";
import React, { useEffect } from "react";
import { View } from "react-native";
import "react-native-gesture-handler";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
import AppLockedOverlay from "./components/app-lock-overlay";
import { withErrorBoundry } from "./components/exception-handler";
import GlobalSafeAreaProvider from "./components/globalsafearea";
import Launcher from "./components/launcher";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
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();
const App = () => {
useAppEvents();
const [init, setInit] = useState(false);
const init = useAppEvents();
useEffect(() => {
let { appLockMode } = SettingsService.get();
if (appLockMode && appLockMode !== "none") {
useUserStore.getState().setVerifyUser(true);
useUserStore.getState().lockApp(true);
}
//BackgroundSync.start();
setInit(true);
init();
setTimeout(async () => {
SettingsService.onFirstLaunch();
await Notifications.get();
@@ -60,7 +49,10 @@ const App = () => {
}
TipManager.init();
}, 100);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<View
style={{
@@ -89,8 +81,8 @@ const App = () => {
}}
>
<ApplicationHolder />
{init && <Launcher />}
</GestureHandlerRootView>
<AppLockedOverlay />
</View>
);
};

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

@@ -16,8 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Database from "@notesnook/core/api/index";
import { database } from "@notesnook/common";
import { initalize, logger as dbLogger } from "@notesnook/core/logger";
import { Platform } from "react-native";
import { MMKVLoader } from "react-native-mmkv-storage";
@@ -30,13 +29,8 @@ import * as Gzip from "react-native-gzip";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
initalize(new KV(LoggerStorage), true);
export const DatabaseLogger = dbLogger;
/**
* @type {import("@notesnook/core/api/index").default}
*/
export var db = new Database(
database.setup(
Storage,
Platform.OS === "ios" ? EventSource : AndroidEventSource,
filesystem,
@@ -46,7 +40,7 @@ export var db = new Database(
}
);
db.host(
database.host(
__DEV__
? {
API_HOST: "https://api.notesnook.com",
@@ -68,3 +62,8 @@ db.host(
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
initalize(new KV(LoggerStorage), true);
export const db = database;
export const DatabaseLogger = dbLogger;

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

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

View File

@@ -34,7 +34,6 @@ import {
import PremiumService from "../../services/premium";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { formatBytes } from "../../utils";
import { eCloseAttachmentDialog, eCloseSheet } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
@@ -47,6 +46,7 @@ import { Notice } from "../ui/notice";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { formatBytes } from "@notesnook/common";
const Actions = ({ attachment, setAttachments, fwdRef }) => {
const colors = useThemeStore((state) => state.colors);
@@ -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,9 +22,8 @@ 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 { formatBytes } from "@notesnook/common";
import { SIZE } from "../../utils/size";
import SheetProvider from "../sheet-provider";
import { IconButton } from "../ui/icon-button";
@@ -36,24 +35,27 @@ function getFileExtension(filename) {
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
/**
*
* @param {any} param0
* @returns
*/
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}
@@ -116,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);
}}
@@ -144,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

@@ -34,6 +34,7 @@ import { IconButton } from "../ui/icon-button";
import { hideAuth, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
export const AuthMode = {
login: 0,
@@ -85,64 +86,82 @@ const AuthModal = () => {
transparent={false}
animated={false}
>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
changeMode={(mode) => setCurrentAuthMode(mode)}
/>
)}
<KeyboardAwareScrollView
style={{
width: "100%"
}}
contentContainerStyle={{
height: Platform.OS === "android" ? "100%" : "100%"
}}
bounces={false}
>
{currentAuthMode !== AuthMode.login ? (
<Signup
changeMode={(mode) => setCurrentAuthMode(mode)}
trial={AuthMode.trialSignup === currentAuthMode}
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
/>
) : (
<Login
welcome={initialAuthMode.current === AuthMode.welcomeSignup}
changeMode={(mode) => setCurrentAuthMode(mode)}
/>
)}
</KeyboardAwareScrollView>
<View
style={{
position: "absolute",
top: Platform.OS === "ios" ? insets.top : 0,
paddingTop: Platform.OS === "android" ? 0 : insets.top,
top: 0,
zIndex: 999,
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
width: "100%",
height: 50,
justifyContent:
initialAuthMode.current !== AuthMode.welcomeSignup
? "space-between"
: "flex-end"
backgroundColor: colors.nav,
width: "100%"
}}
>
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
<IconButton
name="arrow-left"
onPress={() => {
hideAuth();
}}
color={colors.pri}
/>
)}
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
width: "100%",
height: 50,
justifyContent:
initialAuthMode.current !== AuthMode.welcomeSignup
? "space-between"
: "flex-end"
}}
>
{initialAuthMode.current === AuthMode.welcomeSignup ? null : (
<IconButton
name="arrow-left"
onPress={() => {
hideAuth();
}}
color={colors.pri}
/>
)}
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
<Button
title="Skip for now"
onPress={() => {
hideAuth();
}}
iconSize={20}
type="gray"
iconPosition="right"
icon="chevron-right"
height={25}
iconStyle={{
marginTop: 2
}}
style={{
paddingHorizontal: 6
}}
/>
)}
{initialAuthMode.current !== AuthMode.welcomeSignup ? null : (
<Button
title="Skip"
onPress={() => {
hideAuth();
}}
iconSize={16}
type="gray"
iconPosition="right"
icon="chevron-right"
height={25}
iconStyle={{
marginTop: 2
}}
style={{
paddingHorizontal: 6
}}
/>
)}
</View>
</View>
<Toast context="local" />

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,15 @@ 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";
import { eUserLoggedIn } from "../../utils/events";
import { useUserStore } from "../../stores/use-user-store";
import Sync from "../../services/sync";
const LoginSteps = {
emailAuth: 1,
@@ -60,10 +61,16 @@ export const Login = ({ changeMode }) => {
login
} = useLogin(async () => {
hideAuth();
eSendEvent("userLoggedIn", true);
eSendEvent(eUserLoggedIn, true);
await sleep(500);
Progress.present();
setTimeout(() => {
if (!useUserStore.getState().syncing) {
Sync.run("global", false, true);
}
}, 5000);
});
const deviceMode = useSettingStore((state) => state.deviceMode);
useEffect(() => {
async () => {
@@ -82,62 +89,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 +162,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 +232,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 +270,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

@@ -22,14 +22,14 @@ import { Modal, View } from "react-native";
import { db } from "../../common/database";
import BiometricService from "../../services/biometrics";
import {
ToastEvent,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
eSubscribeEvent
} from "../../services/event-manager";
import SettingsService from "../../services/settings";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { eLoginSessionExpired, eUserLoggedIn } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { Dialog } from "../dialog";
@@ -61,7 +61,7 @@ export const SessionExpired = () => {
const [focused, setFocused] = useState(false);
const { step, password, email, passwordInputRef, loading, login } = useLogin(
() => {
eSendEvent("userLoggedIn", true);
eSendEvent(eUserLoggedIn, true);
setVisible(false);
}
);
@@ -84,9 +84,9 @@ export const SessionExpired = () => {
};
useEffect(() => {
eSubscribeEvent("session_expired", open);
const sub = eSubscribeEvent(eLoginSessionExpired, open);
return () => {
eUnSubscribeEvent("session_expired", open);
sub.unsubscribe?.();
setFocused(false);
};
}, [visible, open]);

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,76 @@ 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%",
minHeight: "100%"
alignSelf: "center",
height: "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 +223,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 +245,7 @@ export const Signup = ({ changeMode, trial }) => {
}}
color={colors.accent}
>
terms of service{" "}
Terms of Service{" "}
</Paragraph>
and{" "}
<Paragraph
@@ -276,11 +258,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

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect } from "react";
import React, { useCallback, useEffect } from "react";
import { Keyboard, Platform, View } from "react-native";
import Animated, {
Easing,
@@ -30,10 +30,10 @@ import { notesnook } from "../../../e2e/test.ids";
import { editorState } from "../../screens/editor/tiptap/utils";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { getElevation, showTooltip, TOOLTIP_POSITIONS } from "../../utils";
import { normalize, SIZE } from "../../utils/size";
import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import NativeTooltip from "../../utils/tooltip";
import { PressableButton } from "../ui/pressable";
import { useCallback } from "react";
export const FloatingButton = ({
title,
@@ -116,11 +116,11 @@ export const FloatingButton = ({
accentColor={color || "accent"}
accentText="light"
customStyle={{
...getElevation(5),
...getElevationStyle(5),
borderRadius: 100
}}
onLongPress={(event) => {
showTooltip(event, title, TOOLTIP_POSITIONS.LEFT);
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
}}
onPress={onPress}
>

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

@@ -21,7 +21,7 @@ import React from "react";
import { View } from "react-native";
import { DDS } from "../../services/device-detection";
import { useThemeStore } from "../../stores/use-theme-store";
import { getElevation } from "../../utils";
import { getElevationStyle } from "../../utils/elevation";
const DialogContainer = ({ width, height, ...restProps }) => {
const colors = useThemeStore((state) => state.colors);
@@ -30,7 +30,7 @@ const DialogContainer = ({ width, height, ...restProps }) => {
<View
{...restProps}
style={{
...getElevation(5),
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,

View File

@@ -25,7 +25,7 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { getElevation } from "../../utils";
import { getElevationStyle } from "../../utils/elevation";
import { eCloseSimpleDialog, eOpenSimpleDialog } from "../../utils/events";
import { sleep } from "../../utils/time";
import { Toast } from "../toast";
@@ -111,7 +111,7 @@ export const Dialog = ({ context = "global" }) => {
};
const style = {
...getElevation(5),
...getElevationStyle(5),
width: DDS.isTab ? 400 : "85%",
maxHeight: 450,
borderRadius: 5,

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