Compare commits

..

110 Commits

Author SHA1 Message Date
ammarahm-ed
fd62b1b631 mobile: fix backup not created before migration 2023-09-13 13:50:05 +05:00
ammarahm-ed
2159f6720e mobile: fix delete notebook in bulk 2023-09-13 13:46:08 +05:00
ammarahm-ed
67c3a36464 editor: fix code-block ui 2023-09-13 13:44:53 +05:00
Abdullah Atta
652efc8624 ci: only get app store version if publishing to app store 2023-09-13 13:07:14 +05:00
Abdullah Atta
30da041ea3 web: ask for password if backup encryption if off 2023-09-13 12:48:58 +05:00
Abdullah Atta
e90c401503 web: bump version to 2.6.4 2023-09-13 12:47:10 +05:00
Abdullah Atta
83d919f06e web: fix login form shown for split second before login 2023-09-13 12:42:54 +05:00
Abdullah Atta
74fef0d2d9 core: run all items migrator before other migrators 2023-09-13 12:42:02 +05:00
Abdullah Atta
5906b24da1 core: add support for localOnly migrations 2023-09-13 12:40:30 +05:00
Abdullah Atta
8dd36f7adf core: set last synced to 0 right after login 2023-09-13 12:39:15 +05:00
ammarahm-ed
f2dc26dbff mobile: add support for nnbackupz format 2023-09-13 11:42:59 +05:00
ammarahm-ed
87915ecd00 core: handle null/undefined keys in backup 2023-09-13 11:41:49 +05:00
Abdullah Atta
3cf4e55621 core: restore colors from backups correctly 2023-09-13 10:45:40 +05:00
Abdullah Atta
4c7cd840df core: fix indexes getting replaced on backup restore 2023-09-13 10:23:44 +05:00
Abdullah Atta
bd238a6fe4 core: remove unnecessary exist check slowing reads 2023-09-13 10:23:44 +05:00
Abdullah Atta
beaace9b54 web: add support for streaming backup creation & restore 2023-09-13 10:23:44 +05:00
Abdullah Atta
c5352c2a73 core: add support for restoring new nnbackupz format 2023-09-13 10:23:44 +05:00
Abdullah Atta
dd6410c33e core: add support for streaming backups 2023-09-13 10:23:44 +05:00
Abdullah Atta
ac9dca8098 core: migrate db in a streaming way 2023-09-13 10:23:44 +05:00
Abdullah Atta
74ade6fecc core: make sure items sync after migration 2023-09-13 10:23:44 +05:00
Abdullah Atta
e1cfbb155d core: fix Cannot read properties of null (reading 'clear') 2023-09-13 10:23:44 +05:00
Abdullah Atta
030aba2ac3 core: add migration to remove item.remote 2023-09-13 10:23:44 +05:00
Abdullah Atta
9ab7e722e0 core: port fixes for sync issues from typescript branch 2023-09-13 10:23:44 +05:00
ammarahm-ed
14d6714209 theme: confirm before downloading code-block css 2023-09-11 12:51:01 +05:00
ammarahm-ed
e8e50bebcf theme: support code-block css in builder 2023-09-11 12:33:14 +05:00
ammarahm-ed
fa0610e392 mobile: release v2.6.3 2023-09-09 11:54:56 +05:00
Abdullah Atta
124489a5a5 web: bump to version 2.6.3 2023-09-09 11:06:56 +05:00
Abdullah Atta
22fcdc0fb1 desktop: bump to version 2.6.3 2023-09-09 11:06:20 +05:00
Abdullah Atta
1375300a91 desktop: downgrade electron to v25.8.0 2023-09-09 11:05:25 +05:00
Abdullah Atta
e6f4aea4d7 desktop: handle unhandled errors 2023-09-08 14:49:02 +05:00
Abdullah Atta
d72c4057ff theme: support ARGB values in background 2023-09-08 09:45:54 +05:00
Abdullah Atta
02d80ee4fa web: add separate plan for education discount 2023-09-07 18:39:21 +05:00
ammarahm-ed
2da3e9e0be mobile: release v2.6.2 2023-09-06 21:38:20 +05:00
ammarahm-ed
8333e7bdb3 mobile: set NSExtensionActivationDictionaryVersion to v2 2023-09-06 20:07:15 +05:00
ammarahm-ed
cef2472aae mobile: handle state when app entered background
to take some action like pick a file/image etc.
2023-09-06 20:06:46 +05:00
ammarahm-ed
a4946f7e89 mobile: hide caret when input 2fa code 2023-09-06 20:05:06 +05:00
ammarahm-ed
3e95e0feae mobile: fix db logger not logging 2023-09-06 20:04:46 +05:00
Abdullah Atta
b81843cdf7 core: sync settings before everything else 2023-09-06 19:31:06 +05:00
Abdullah Atta
eedd50fc64 web: bump version to 2.6.2 2023-09-06 17:06:02 +05:00
Abdullah Atta
1cb00bca0d core: fix collector tests 2023-09-06 17:00:04 +05:00
Abdullah Atta
8c233570a7 core: return default values for unset settings 2023-09-06 16:42:51 +05:00
Muhammad Ali
8a3c8b85bb web: fix autocomplete hint in signup form password fields (#3272) 2023-09-06 16:09:54 +05:00
Abdullah Atta
2ee8a807dc core: fix settings getting replaced on login 2023-09-06 15:56:20 +05:00
Abdullah Atta
ec85f748fa core: use serverLastSynced time from client 2023-09-06 15:43:20 +05:00
Abdullah Atta
051b7fcb8c core: notify autosync before setting remote= false 2023-09-06 15:28:56 +05:00
Abdullah Atta
9af88811d8 core: remove remote: true from settings after sync 2023-09-06 15:09:31 +05:00
Abdullah Atta
6c4672037c web: do not run sync on file picker closed 2023-09-06 14:49:56 +05:00
Abdullah Atta
d59e0d9ee2 core: sync attachments before anything else 2023-09-06 14:48:43 +05:00
Abdullah Atta
5a001a1457 core: use bulk io operations for logger storage 2023-09-06 12:30:39 +05:00
Abdullah Atta
44d6474b36 core: fix attachments not syncing 2023-09-06 12:14:03 +05:00
Abdullah Atta
0607e005f5 core: only sync syncable attachments 2023-09-06 12:04:58 +05:00
Abdullah Atta
308eb4b55f core: merge remote attachment if local attachment doesn't exist 2023-09-06 11:40:44 +05:00
Abdullah Atta
6abb6207c5 core: always keep remote attachment if it is newer 2023-09-06 11:16:53 +05:00
Abdullah Atta
89189d52ae web: add log on when sync is ignored 2023-09-06 10:28:31 +05:00
Abdullah Atta
e653ce69f8 core: push items serially tcp style 2023-09-06 10:22:53 +05:00
Abdullah Atta
9b89b328f6 core: send sync progress event after merge 2023-09-06 09:58:07 +05:00
Abdullah Atta
77ae71fab1 web: fix sync status progress 2023-09-06 09:40:24 +05:00
Abdullah Atta
7f7c1e6b93 core: use linkedom and node-fetch for tests 2023-09-06 09:40:24 +05:00
ammarahm-ed
30341bea1c mobile: run sync based on lastSyncTime 2023-09-06 09:27:02 +05:00
Abdullah Atta
82d44ea890 web: show sync status in place of placeholder 2023-09-05 20:15:34 +05:00
Abdullah Atta
5f0907168f core: always send sync request to client 2023-09-05 18:01:44 +05:00
Abdullah Atta
48061962b1 web: fix backup/export tests 2023-09-05 17:47:28 +05:00
Abdullah Atta
24141b66cf core: add log to debug relations removal issue 2023-09-05 17:47:11 +05:00
Abdullah Atta
41beee45ba core: migrate in bulk for more performance 2023-09-05 16:08:10 +05:00
Abdullah Atta
cb7c70b0d6 web: fix navigation menu ui 2023-09-05 15:47:39 +05:00
ammarahm-ed
2aa5a57557 mobile: use accentForeground on accent background 2023-09-05 15:40:25 +05:00
Abdullah Atta
17582bcefb web: fix navigation menu not taking up full space 2023-09-05 15:40:25 +05:00
Abdullah Atta
1f5a5afb10 theme: do not throw error if deprecated color is found 2023-09-05 15:40:25 +05:00
Abdullah Atta
d1c724563f web: fix themes selection not showing themes 2023-09-05 15:40:25 +05:00
Abdullah Atta
72aa5eccb7 server: fix themes pagination 2023-09-05 15:40:25 +05:00
Abdullah Atta
95e0c4b0d6 editor: reduce task list header padding 2023-09-05 15:40:25 +05:00
Abdullah Atta
9ec6ca0ded web: fix various colors 2023-09-05 15:40:25 +05:00
Abdullah Atta
85b7611214 theme: increase opacity of shade color 2023-09-05 15:40:25 +05:00
Abdullah Atta
d256d435e4 theme: mark shade and textSelection colors as deprecated 2023-09-05 15:40:25 +05:00
Abdullah Atta
577a976e01 theme: derive shade color from accent color 2023-09-05 15:40:25 +05:00
Abdullah Atta
07453834b0 docs: update list of colors in theme-engine docs 2023-09-05 15:40:25 +05:00
Abdullah Atta
59f6b5e411 theme: get rid of shade & textSelection colors 2023-09-05 15:40:25 +05:00
Abdullah Atta
ac9be8360a web: list item title should use heading color 2023-09-05 15:40:25 +05:00
Abdullah Atta
32d0b07f09 web: use accentForeground color instead of white 2023-09-05 15:40:25 +05:00
Abdullah Atta
7b57639076 theme: add new accentForeground color 2023-09-05 15:40:25 +05:00
Abdullah Atta
1fa174bab7 theme: fix input placeholder color 2023-09-05 15:40:25 +05:00
Abdullah Atta
6c52b5a77d editor: editor toolbar separators should use separator color 2023-09-05 15:40:25 +05:00
Abdullah Atta
d4a6157ed3 web: navigation menu separators should use separator color 2023-09-05 15:40:25 +05:00
Abdullah Atta
a86358bbcd web: borders between panes should use scoped border color 2023-09-05 15:40:25 +05:00
Abdullah Atta
20d0e6d047 web: fix menu borders should use border color 2023-09-05 15:40:25 +05:00
Muhammad Ali
51fc4bf3e4 core: add support for aligning images in PDF/HTML exports (#3197)
* web: added function for aligning images

* web: removed regex and added replaceAll in loop

* Update apps/web/src/common/export.ts

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

* core: add support for image alignment in html/pdf export

---------

Signed-off-by: Abdullah Atta <thecodrr@protonmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-09-05 15:39:59 +05:00
Abdullah Atta
61d0dafd21 desktop: update app-builder-lib patch 2023-09-04 16:01:04 +05:00
Abdullah Atta
89398cb5e5 misc: remove all mention of crypto-worker 2023-09-04 15:58:38 +05:00
Abdullah Atta
72ec4063de global: update lockfiles 2023-09-04 15:58:23 +05:00
Abdullah Atta
f8423eeea8 editor: update test snapshots 2023-09-04 15:51:47 +05:00
Abdullah Atta
d91203990e editor: respect text direction when exiting node using arrow keys 2023-09-04 15:51:47 +05:00
Abdullah Atta
fc84c1af51 editor: add keyboard specific android editor tests 2023-09-04 15:51:47 +05:00
Abdullah Atta
58de07073d editor: improve escaping from & deleting of nodes 2023-09-04 15:51:47 +05:00
Abdullah Atta
cafa5ff821 editor: fix blockquote padding & margin on mobile 2023-09-04 15:51:47 +05:00
Abdullah Atta
1859799ca3 editor: fix crash on adding a task list 2023-09-04 15:51:47 +05:00
Abdullah Atta
271aa11f74 editor: use list-keymap extension instead of own workarounds 2023-09-04 15:51:47 +05:00
Abdullah Atta
e42f1a65f5 editor: update tiptap packages to v2.1.7 2023-09-04 15:51:47 +05:00
Abdullah Atta
73f3a31614 editor: fix sub outline lists not getting outdented 2023-09-04 15:51:47 +05:00
Abdullah Atta
4d7792d06a editor: fix alignment of outline list indicator 2023-09-04 15:51:47 +05:00
ammarahm-ed
1a0f17c059 mobile: update relations store on open notification 2023-09-04 15:46:37 +05:00
ammarahm-ed
89d941401a mobile: fix beta tags ui 2023-09-04 15:46:23 +05:00
ammarahm-ed
a076ee640d mobile: save report issue information if app gets
restarted or accidentally closed
2023-09-04 15:45:30 +05:00
Abdullah Atta
8c1629f7dd web: ask for password when doing sensitive actions 2023-09-04 15:10:18 +05:00
ammarahm-ed
9a31c07ac5 mobile: add verification prompts 2023-09-04 15:10:18 +05:00
ammarahm-ed
11f39316b5 mobile: fix color scheme 2023-09-04 15:09:03 +05:00
ammarahm-ed
f157229e00 mobile: fix theme gets reset on opening share extension 2023-09-04 15:09:03 +05:00
ammarahm-ed
a0f25c4e6f mobile: fix list not updating on change date sort 2023-09-04 15:07:33 +05:00
Ammar Ahmed
003b3d2a84 Remove hover effect on mobile in editor (#3163)
* editor: remove background on hover on mobile

* editor: fix task item alignment and size on mobile
2023-09-04 15:06:41 +05:00
ammarahm-ed
1775e0bda1 mobile: fix flash on launch on ios 2023-09-04 14:37:24 +05:00
Ammar Ahmed
d5038ac631 mobile: fix quick notes from notifications (#3165)
- Add note to default notebook
- Respect paragraph spacing

Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2023-09-04 14:36:55 +05:00
184 changed files with 5005 additions and 4453 deletions

View File

@@ -19,7 +19,6 @@ runs:
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

View File

@@ -97,6 +97,7 @@ jobs:
- name: Get App Store Version
id: appstore
uses: streetwriters/appstore-connect-app-version@develop
if: inputs.publish-apple
with:
app-id: ${{ steps.app_metadata.outputs.apple_app_id }}
key-id: ${{ secrets.api_key_id }}

View File

@@ -103,7 +103,7 @@ All commits must include valid scopes in the commit message. **Valid commit scop
**Packages:**
1. `crypto`: changes related to the cryptographic core (including `@notesnook/crypto-worker`)
1. `crypto`: changes related to the cryptographic core
2. `editor`: changes related to the editor (including `@notesnook/editor-mobile`)
3. `logger`: changes related to the logger
4. `theme`: changes related to the theme

View File

@@ -46,7 +46,6 @@ Notesnook is built using the following technologies:
| `@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 |

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.6.1",
"version": "2.6.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.6.1",
"version": "2.6.4",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -14,7 +14,7 @@
"@trpc/client": "10.31.0",
"@trpc/server": "10.31.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.1",
"electron-updater": "6.1.4",
"icojs": "^0.17.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
@@ -24,8 +24,8 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^26.1.0",
"electron-builder": "^24.6.3",
"electron": "25.8.0",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"tree-kill": "^1.2.2",
@@ -270,13 +270,14 @@
}
},
"node_modules/@electron/notarize": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-1.2.4.tgz",
"integrity": "sha512-W5GQhJEosFNafewnS28d3bpQ37/s91CDWqxVchHfmv2dQSTWpOzNlUVQwYzC1ay5bChRV/A9BTL68yj0Pa+TSg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.1.0.tgz",
"integrity": "sha512-Q02xem1D0sg4v437xHgmBLxI2iz/fc0D4K7fiVWHa/AnW8o7D751xyKNXgziA6HrTOme9ul1JfWN5ark8WH1xA==",
"dev": true,
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1"
"fs-extra": "^9.0.1",
"promise-retry": "^2.0.1"
},
"engines": {
"node": ">= 10.0.0"
@@ -331,9 +332,9 @@
}
},
"node_modules/@electron/universal": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.3.4.tgz",
"integrity": "sha512-BdhBgm2ZBnYyYRLRgOjM5VHkyFItsbggJ0MHycOjKWdFGYwK97ZFXH54dTvUWEfha81vfvwr5On6XBjt99uDcg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.4.1.tgz",
"integrity": "sha512-lE/U3UNw1YHuowNbTmKNs9UlS3En3cPgwM5MI+agIgr/B1hSze9NdOP0qn7boZaI9Lph8IDv3/24g9IxnJP7aQ==",
"dev": true,
"dependencies": {
"@electron/asar": "^3.2.1",
@@ -1040,15 +1041,15 @@
"dev": true
},
"node_modules/app-builder-lib": {
"version": "24.6.3",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.6.3.tgz",
"integrity": "sha512-++0Zp7vcCHfXMBGVj7luFxpqvMPk5mcWeTuw7OK0xNAaNtYQTTN0d9YfWRsb1MvviTOOhyHeULWz1CaixrdrDg==",
"version": "24.6.4",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.6.4.tgz",
"integrity": "sha512-m9931WXb83teb32N0rKg+ulbn6+Hl8NV5SUpVDOVz9MWOXfhV6AQtTdftf51zJJvCQnQugGtSqoLvgw6mdF/Rg==",
"dev": true,
"dependencies": {
"@develar/schema-utils": "~2.6.5",
"@electron/notarize": "^1.2.3",
"@electron/osx-sign": "^1.0.4",
"@electron/universal": "1.3.4",
"@electron/notarize": "2.1.0",
"@electron/osx-sign": "1.0.5",
"@electron/universal": "1.4.1",
"@malept/flatpak-bundler": "^0.4.0",
"@types/fs-extra": "9.0.13",
"7zip-bin": "~5.1.1",
@@ -1678,12 +1679,12 @@
}
},
"node_modules/dmg-builder": {
"version": "24.6.3",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.6.3.tgz",
"integrity": "sha512-O7KNT7OKqtV54fMYUpdlyTOCP5DoPuRMLqMTgxxV2PO8Hj/so6zOl5o8GTs8pdDkeAhJzCFOUNB3BDhgXbUbJg==",
"version": "24.6.4",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.6.4.tgz",
"integrity": "sha512-BNcHRc9CWEuI9qt0E655bUBU/j/3wUCYBVKGu1kVpbN5lcUdEJJJeiO0NHK3dgKmra6LUUZlo+mWqc+OCbi0zw==",
"dev": true,
"dependencies": {
"app-builder-lib": "24.6.3",
"app-builder-lib": "24.6.4",
"builder-util": "24.5.0",
"builder-util-runtime": "9.2.1",
"fs-extra": "^10.1.0",
@@ -1750,9 +1751,9 @@
}
},
"node_modules/electron": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-26.1.0.tgz",
"integrity": "sha512-qEh19H09Pysn3ibms5nZ0haIh5pFoOd7/5Ww7gzmAwDQOulRi8Sa2naeueOyIb1GKpf+6L4ix3iceYRAuA5r5Q==",
"version": "25.8.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.8.0.tgz",
"integrity": "sha512-T3kC1a/3ntSaYMCVVfUUc9v7myPzi6J2GP0Ad/CyfWKDPp054dGyKxb2EEjKnxQQ7wfjsT1JTEdBG04x6ekVBw==",
"hasInstallScript": true,
"dependencies": {
"@electron/get": "^2.0.0",
@@ -1767,16 +1768,16 @@
}
},
"node_modules/electron-builder": {
"version": "24.6.3",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.6.3.tgz",
"integrity": "sha512-O6PqhRXwfxCNTXI4BlhELSeYYO6/tqlxRuy+4+xKBokQvwDDjDgZMMoSgAmanVSCuzjE7MZldI9XYrKFk+EQDw==",
"version": "24.6.4",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.6.4.tgz",
"integrity": "sha512-uNWQoU7pE7qOaIQ6CJHpBi44RJFVG8OHRBIadUxrsDJVwLLo8Nma3K/EEtx5/UyWAQYdcK4nVPYKoRqBb20hbA==",
"dev": true,
"dependencies": {
"app-builder-lib": "24.6.3",
"app-builder-lib": "24.6.4",
"builder-util": "24.5.0",
"builder-util-runtime": "9.2.1",
"chalk": "^4.1.2",
"dmg-builder": "24.6.3",
"dmg-builder": "24.6.4",
"fs-extra": "^10.1.0",
"is-ci": "^3.0.0",
"lazy-val": "^1.0.5",
@@ -1896,9 +1897,9 @@
}
},
"node_modules/electron-updater": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.1.tgz",
"integrity": "sha512-IBT3zJ4yO5UZMF2gOTC9HrlmG4OYSRtOiHKzNAShJvfuicdx6UaXoa6AvhcTxdx6zf/rJyFMRBISS9jhVwTfow==",
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz",
"integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==",
"dependencies": {
"builder-util-runtime": "9.2.1",
"fs-extra": "^10.1.0",
@@ -1907,7 +1908,7 @@
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "^7.3.8",
"typed-emitter": "^2.1.0"
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/emoji-regex": {
@@ -1931,6 +1932,12 @@
"node": ">=6"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"dev": true
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3102,6 +3109,19 @@
"node": ">=0.4.0"
}
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"dev": true,
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -3331,6 +3351,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -3674,9 +3703,9 @@
}
},
"node_modules/tar": {
"version": "6.1.15",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz",
"integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
"dev": true,
"dependencies": {
"chownr": "^2.0.0",
@@ -3700,6 +3729,11 @@
"fs-extra": "^10.0.0"
}
},
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="
},
"node_modules/tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.1",
"version": "2.6.4",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -15,7 +15,7 @@
"@trpc/client": "10.31.0",
"@trpc/server": "10.31.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.1",
"electron-updater": "6.1.4",
"icojs": "^0.17.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
@@ -25,8 +25,8 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^26.1.0",
"electron-builder": "^24.6.3",
"electron": "25.8.0",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"tree-kill": "^1.2.2",

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
index 3ba5d6a..cf8e147 100644
index fcb7f54..cb1c7f7 100644
--- a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
+++ b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
@@ -105,6 +105,7 @@ class LinuxTargetHelper {
@@ -116,6 +116,7 @@ class LinuxTargetHelper {
StartupWMClass: appInfo.productName,
...extra,
...targetSpecificOptions.desktop,
@@ -10,7 +10,7 @@ index 3ba5d6a..cf8e147 100644
};
const description = this.getDescription(targetSpecificOptions);
if (!(0, builder_util_1.isEmptyOrSpaces)(description)) {
@@ -148,6 +149,23 @@ class LinuxTargetHelper {
@@ -159,6 +160,23 @@ class LinuxTargetHelper {
data += `\n${name}=${desktopMeta[name]}`;
}
data += "\n";

View File

@@ -117,6 +117,14 @@ export const osIntegrationRouter = t.router({
writeFileSync(resolvedPath, data);
}),
resolvePath: t.procedure
.input(z.object({ filePath: z.string() }))
.query(({ input }) => {
const { filePath } = input;
if (!filePath) return;
return resolvePath(filePath);
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {

View File

@@ -53,6 +53,13 @@ if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
process.on("uncaughtException", (error) => {
console.error("uncaughtException:", error);
});
process.on("unhandledRejection", (reason) => {
console.error("unhandledRejection:", reason);
});
app.commandLine.appendSwitch("lang", "en-US");
async function createWindow() {

View File

@@ -31,7 +31,6 @@ declare global {
}
process.once("loaded", async () => {
console.log("HELLO!");
const electronTRPC: RendererGlobalElectronTRPC = {
sendMessage: (operation) =>
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),

View File

@@ -62,14 +62,9 @@ function registerProtocol() {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
try {
return net.fetch(request, {
bypassCustomProtocolHandlers: true
});
} catch (e) {
console.error(e);
}
return Response.error();
return net.fetch(request, {
bypassCustomProtocolHandlers: true
});
}
});
console.info(`${SCHEME} protocol inteception "successful"`);

View File

@@ -94,6 +94,12 @@ const App = () => {
);
};
let currTheme =
useThemeStore.getState().colorScheme === "dark"
? SettingsService.getProperty("darkTheme")
: SettingsService.getProperty("lightTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element) => {
return function AppWithThemeProvider() {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
@@ -125,11 +131,13 @@ export const withTheme = (Element) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const nextTheme = colorScheme === "dark" ? darkTheme : lightTheme;
if (JSON.stringify(nextTheme) !== JSON.stringify(currTheme)) {
useThemeEngineStore
.getState()
.setTheme(colorScheme === "dark" ? darkTheme : lightTheme);
}, [colorScheme, darkTheme, lightTheme]);
currTheme = nextTheme;
}
return <Element />;
};

View File

@@ -17,18 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { database } from "@notesnook/common";
import { initalize, logger as dbLogger } from "@notesnook/core/dist/logger";
import { logger as dbLogger } from "@notesnook/core/dist/logger";
import { Platform } from "react-native";
import { MMKVLoader } from "react-native-mmkv-storage";
import filesystem from "../filesystem";
import * as Gzip from "react-native-gzip";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import Storage, { KV } from "./storage";
import * as Gzip from "react-native-gzip";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
import filesystem from "../filesystem";
import "./logger";
import Storage from "./storage";
database.host(
__DEV__
@@ -63,7 +59,5 @@ database.setup(
}
);
initalize(new KV(LoggerStorage));
export const db = database;
export const DatabaseLogger = dbLogger;

View File

@@ -0,0 +1,29 @@
/*
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 { MMKVLoader } from "react-native-mmkv-storage";
import { initalize } from "@notesnook/core/dist/logger";
import { KV } from "./storage";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
initalize(new KV(LoggerStorage));
export {};

View File

@@ -26,4 +26,5 @@ export const MMKV = new MMKVLoader()
? ProcessingModes.MULTI_PROCESS
: ProcessingModes.SINGLE_PROCESS
)
.disableIndexing()
.initialize();

View File

@@ -66,9 +66,12 @@ export function cancelable(operation) {
}
export function copyFileAsync(source, dest) {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
ScopedStorage.copyFile(source, dest, (e, r) => {
console.log(e, r);
if (e) {
reject(e);
return;
}
resolve();
});
});

View File

@@ -128,7 +128,9 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
fontSize={SIZE.md}
buttonType={{
color: color ? color : colors.primary.accent,
text: colors.static.white,
text: color
? colors.static.white
: colors.primary.accentForeground,
selected: color ? color : colors.primary.accent,
opacity: 1
}}

View File

@@ -214,6 +214,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
code.current = value;
//onNext();
}}
caretHidden
inputStyle={{
fontSize: SIZE.lg,
height: 60,

View File

@@ -140,7 +140,7 @@ const FloatingButton = ({
>
<Icon
name={title === "Clear all trash" ? "delete" : "plus"}
color="white"
color={colors.primary.accentForeground}
size={SIZE.xxl}
/>
</View>

View File

@@ -114,7 +114,15 @@ export const Title = () => {
#
</Heading>
) : null}
{title} <Tag visible={currentScreen.beta} text="BETA" />
{title}{" "}
<Tag
visible={currentScreen.beta}
text="BETA"
style={{
backgroundColor: "transparent"
}}
textColor={colors.primary.accent}
/>
</Heading>
) : null}
</>

View File

@@ -40,6 +40,7 @@ import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useEditorStore } from "../../../stores/use-editor-store";
function navigateToTag(item) {
const tag = db.tags.tag(item.id);
@@ -98,6 +99,9 @@ const NoteItem = ({
dateBy = "dateCreated",
noOpen = false
}) => {
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(item);
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
@@ -108,6 +112,9 @@ const NoteItem = ({
const reminder = getUpcomingReminder(reminders);
const noteColor = ColorValues[item.color?.toLowerCase()];
const tags = getTags(item);
const primaryColors =
currentEditingNote === item.id ? colors.selected : colors.primary;
return (
<>
<View
@@ -148,7 +155,7 @@ const NoteItem = ({
borderRadius: 5,
marginRight: 5,
borderWidth: 0.5,
borderColor: colors.primary.border,
borderColor: primaryColors.border,
paddingHorizontal: 6,
marginBottom: 5
}}
@@ -179,7 +186,7 @@ const NoteItem = ({
<Paragraph
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || colors.primary.heading
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
}
style={{
flexWrap: "wrap"
@@ -192,7 +199,7 @@ const NoteItem = ({
<Heading
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || colors.primary.heading
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
}
style={{
flexWrap: "wrap"
@@ -208,6 +215,7 @@ const NoteItem = ({
style={{
flexWrap: "wrap"
}}
color={primaryColors.paragraph}
numberOfLines={2}
>
{decode(item.headline, {
@@ -262,7 +270,7 @@ const NoteItem = ({
<Icon
name="attachment"
size={SIZE.md}
color={colors.primary.icon}
color={primaryColors.icon}
/>
<Paragraph
color={colors.secondary.paragraph}
@@ -283,7 +291,7 @@ const NoteItem = ({
}}
color={
ColorValues[item.color?.toLowerCase()] ||
colors.primary.accent
primaryColors.accent
}
/>
) : null}
@@ -296,7 +304,7 @@ const NoteItem = ({
style={{
marginRight: 6
}}
color={colors.primary.icon}
color={primaryColors.icon}
/>
) : null}
@@ -354,7 +362,7 @@ const NoteItem = ({
</Paragraph>
<Paragraph
color={colors.primary.accent}
color={primaryColors.accent}
size={SIZE.xs}
style={{
marginRight: 6
@@ -424,7 +432,7 @@ const NoteItem = ({
<IconButton
testID={notesnook.listitem.menu}
color={colors.primary.paragraph}
color={primaryColors.paragraph}
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}

View File

@@ -53,15 +53,10 @@ const renderItems = {
const RenderItem = ({ item, index, type, ...restArgs }) => {
if (!item) return <View />;
const Item = renderItems[item.itemType || item.type] || View;
const groupOptions = db.settings?.getGroupOptions(type);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const totalNotes = getTotalNotes(item);
return (
<Item
item={item}
dateBy={dateBy}
index={index}
type={type}
totalNotes={totalNotes}
@@ -103,6 +98,12 @@ const List = ({
(type === "notes" && notesListMode === "compact") ||
type === "notebooks" ||
notebooksListMode === "compact";
const groupType =
screen === "Notes" ? "home" : screen === "Favorites" ? "favorites" : type;
const groupOptions = db.settings?.getGroupOptions(groupType);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const renderItem = React.useCallback(
({ item, index }) => (
@@ -111,6 +112,7 @@ const List = ({
index={index}
color={headerProps?.color}
title={headerProps?.heading}
dateBy={dateBy}
type={
screen === "Notes"
? "home"
@@ -122,7 +124,7 @@ const List = ({
isSheet={isSheet}
/>
),
[headerProps?.color, headerProps?.heading, screen, type, isSheet]
[headerProps?.color, headerProps?.heading, screen, type, isSheet, dateBy]
);
const _onRefresh = async () => {

View File

@@ -317,23 +317,8 @@ export const SelectionHeader = React.memo(() => {
customStyle={{
marginLeft: 10
}}
onPress={async () => {
presentDialog({
title: `Delete ${
selectedItemsList.length > 1 ? "items" : "item"
}`,
paragraph: `Are you sure you want to delete ${
selectedItemsList.length > 1 ? "these items?" : "this item?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: () => {
deleteItems();
},
positiveType: "errorShade"
});
return;
onPress={() => {
deleteItems();
}}
tooltipText="Move to trash"
tooltipPosition={1}

View File

@@ -35,10 +35,14 @@ import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { useStoredRef } from "../../../hooks/use-stored-ref";
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
const { colors } = useThemeColors();
const body = useRef(defaultBody);
const title = useRef(defaultTitle);
const body = useStoredRef("issueBody", defaultBody);
const title = useStoredRef("issueTitle", defaultTitle);
const user = useUserStore((state) => state.user);
const [loading, setLoading] = useState(false);
const bodyRef = useRef();
@@ -70,6 +74,8 @@ Logged in: ${user ? "yes" : "no"}`,
});
setLoading(false);
eSendEvent(eCloseSheet);
body.reset();
title.reset();
await sleep(300);
presentDialog({
title: "Issue reported",

View File

@@ -64,7 +64,7 @@ export const Progress = () => {
width={null}
animated={true}
useNativeDriver
progress={currentProgress || 0.1}
indeterminate
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
@@ -74,7 +74,7 @@ export const Progress = () => {
{progress ? (
<Paragraph color={colors.secondary.paragraph}>
{progress.type?.slice(0, 1).toUpperCase() + progress.type?.slice(1)}
ing {progress?.current}/{progress?.total}
ing {progress?.current}
</Paragraph>
) : null}
</View>

View File

@@ -22,7 +22,9 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
import DocumentPicker, {
DocumentPickerResponse
} from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
@@ -45,6 +47,8 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { unzip } from "react-native-zip-archive";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
@@ -129,75 +133,71 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 300);
}, 1000);
}, []);
const restore = async (item) => {
if (restoring) {
return;
}
try {
setRestoring(true);
let prefix = Platform.OS === "ios" ? "" : "file:/";
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(item.uri, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(prefix + item.path, "utf8");
}
backup = JSON.parse(backup);
if (backup.data.iv && backup.data.salt) {
withPassword(
async (value) => {
try {
await restoreBackup(backup, value);
close();
setRestoring(false);
return true;
} catch (e) {
backupError(e);
return false;
}
},
() => {
setRestoring(false);
try {
const file = Platform.OS === "ios" ? item.path : item.uri;
console.log(file);
if (file.endsWith(".nnbackupz")) {
setRestoring(true);
if (Platform.OS === "android") {
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
}
);
} else {
await restoreBackup(backup);
close();
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
console.log("copying");
await copyFileAsync(file, cacheFile);
console.log("copied");
await restoreFromZip(cacheFile);
} else {
await restoreFromZip(file, false);
}
} else if (file.endsWith(".nnbackup")) {
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(file, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(file, "utf8");
}
await restoreFromNNBackup(JSON.parse(backup));
}
} catch (e) {
console.log("error", e);
setRestoring(false);
backupError(e);
}
};
const withPassword = (onsubmit, onclose = () => {}) => {
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: onclose,
negativeText: "Cancel",
positivePress: async (password) => {
try {
return await onsubmit(password);
} catch (e) {
ToastEvent.show({
heading: "Failed to backup data",
message: e.message,
type: "error",
context: "global"
});
return false;
const withPassword = () => {
return new Promise((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password) => {
resolve(password);
resolved = true;
return true;
}
}
});
});
};
@@ -217,21 +217,164 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
files = files
.filter((file) => {
const name = Platform.OS === "android" ? file.name : file.filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
setTimeout(() => {
setLoading(false);
}, 1000);
setLoading(false);
} catch (e) {
console.log(e);
setLoading(false);
}
};
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
await db.initCollections();
initialize();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
return true;
};
const backupError = (e) => {
ToastEvent.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
/**
*
* @param {string} file
*/
async function restoreFromZip(file, remove) {
try {
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
await unzip(file, zipOutputFolder);
console.log("Unzipped files successfully to", zipOutputFolder);
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
throw new Error("Backup file is invalid");
}
let password;
console.log(`Found ${backupFiles?.length} files to restore from backup`);
for (const path of backupFiles) {
if (path === ".nnbackup") continue;
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const parsed = JSON.parse(data);
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
password = await withPassword();
if (!password) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password);
console.log("Imported", path);
}
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (remove) {
RNFetchBlob.fs.unlink(file).catch(console.log);
}
await db.initCollections();
initialize();
setRestoring(false);
close();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
} catch (e) {
backupError(e);
setRestoring(false);
}
}
/**
*
* @param {string} file
*/
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const password = await withPassword();
if (password) {
try {
await restoreBackup(backup, password);
close();
setRestoring(false);
} catch (e) {
setRestoring(false);
backupError(e);
}
} else {
setRestoring(false);
}
} else {
await restoreBackup(backup);
setRestoring(false);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
}
const button = {
title: "Restore from files",
onPress: async () => {
if (restoring) {
return;
}
try {
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
if (file.name.endsWith(".nnbackupz")) {
setRestoring(true);
await restoreFromZip(file.fileCopyUri, true);
} else if (file.name.endsWith(".nnbackup")) {
RNFetchBlob.fs.unlink(file.fileCopyUri).catch(console.log);
setRestoring(true);
const data = await fetch(file.uri);
await restoreFromNNBackup(await data.json());
}
} catch (e) {
console.log("error", e.stack);
setRestoring(false);
backupError(e);
}
}
};
const renderItem = ({ item, index }) => (
<View
style={{
@@ -271,76 +414,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View>
);
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
setRestoring(false);
initialize();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
};
const backupError = (e) => {
ToastEvent.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
const button = {
title: "Restore from files",
onPress: () => {
if (restoring) {
return;
}
DocumentPicker.pickSingle()
.then((r) => {
setRestoring(true);
fetch(r.uri)
.then(async (r) => {
try {
let backup = await r.json();
if (backup.data.iv && backup.data.salt) {
withPassword(
async (value) => {
try {
restoreBackup(backup, value).then(() => {
close();
setRestoring(false);
});
return true;
} catch (e) {
backupError(e);
setRestoring(false);
return false;
}
},
() => {
setRestoring(false);
}
);
} else {
await restoreBackup(backup);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
})
.catch(console.log);
})
.catch(console.log);
}
};
return (
<>
<View>

View File

@@ -17,20 +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 React, { useEffect, useState } from "react";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import { normalize, SIZE } from "../../utils/size";
import { SIZE, normalize } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import Tag from "../ui/tag";
export const MenuItem = React.memo(
function MenuItem({ item, index, testID, rightBtn }) {
@@ -40,6 +38,7 @@ export const MenuItem = React.memo(
);
const screenId = item.name.toLowerCase() + "_navigation";
let isFocused = headerTextState?.id === screenId;
const primaryColors = isFocused ? colors.selected : colors.primary;
const _onPress = () => {
if (item.func) {
@@ -128,14 +127,28 @@ export const MenuItem = React.memo(
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
)}
<Tag visible={item.isBeta} text="BETA" />
{item.isBeta ? (
<View
style={{
borderRadius: 100,
backgroundColor: primaryColors.accent,
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
BETA
</Paragraph>
</View>
) : null}
</View>
{item.switch ? (
<ToggleSwitch
isOn={item.on}
onColor={colors.primary.accent}
offColor={colors.primary.icon}
onColor={primaryColors.accent}
offColor={primaryColors.icon}
size="small"
animationSpeed={150}
onToggle={_onPress}

View File

@@ -108,9 +108,12 @@ export const PinItem = React.memo(
alias = item?.alias || item?.title;
const [visible, setVisible] = useState(false);
const [headerTextState, setHeaderTextState] = useState(null);
const primaryColors =
headerTextState?.id === item.id ? colors.selected : colors.primary;
const color =
headerTextState?.id === item.id
? colors.primary.accent
? colors.selected.accent
: colors.primary.icon;
const fwdRef = useRef();
@@ -230,7 +233,7 @@ export const PinItem = React.memo(
style={{
flexWrap: "wrap"
}}
color={colors.primary.heading}
color={primaryColors.heading}
size={SIZE.md}
>
{alias}
@@ -238,7 +241,7 @@ export const PinItem = React.memo(
) : (
<Paragraph
numberOfLines={1}
color={colors.primary.paragraph}
color={primaryColors.paragraph}
size={SIZE.md}
>
{alias}

View File

@@ -135,7 +135,7 @@ export const UserStatus = () => {
? "Last sync failed, tap to try again"
: syncing
? `Syncing your notes${
progress ? ` (${progress.current}/${progress.total})` : ""
progress ? ` (${progress.current})` : ""
}`
: "Tap here to sync your notes."}
</Paragraph>

View File

@@ -77,7 +77,9 @@ export const Tip = ({
alignSelf: "flex-start",
borderRadius: 100,
borderWidth: 1,
borderColor: colors.static[color as never] || colors.primary[color as never] as string
borderColor:
colors.static[color as never] ||
(colors.primary[color as never] as string)
}}
/>
@@ -139,7 +141,7 @@ export const Tip = ({
icon={tip.button.icon}
buttonType={{
color: colors.static[color as never],
text: colors.static.white
text: colors.primary.accentForeground
}}
style={{
marginTop: 10

View File

@@ -108,7 +108,7 @@ export const useButton = ({
},
accent: {
primary: accent || colors.primary.accent,
text: text || colors.primary.paragraph,
text: text || colors.primary.accentForeground,
selected: accent || colors.primary.accent
},
inverted: {

View File

@@ -16,9 +16,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { ViewStyle } from "react-native";
import { SIZE } from "../../../utils/size";
import Paragraph from "../typography/paragraph";
@@ -50,7 +50,7 @@ export default function Tag({
textAlign: "center",
...style
}}
color={textColor || colors.static.white}
color={textColor || colors.primary.accentForeground}
size={SIZE.xxs}
>
{text}

View File

@@ -101,13 +101,6 @@ const onSyncAborted = () => {
useUserStore.getState().setSyncing(false, SyncStatus.Failed);
};
const onSyncProgress = ({ type, total, current }) => {
if (type !== "download") return;
if (total < 10 || current % 10 === 0) {
initAfterSync();
}
};
const onFileEncryptionProgress = ({ total, progress }) => {
useAttachmentStore
.getState()
@@ -167,13 +160,15 @@ const onUserSubscriptionStatusChanged = async (userStatus) => {
useMessageStore.getState().setAnnouncement();
};
const onRequestPartialSync = async (full, force) => {
const onRequestPartialSync = async (full, force, lastSyncTime) => {
if (SettingsService.get().disableAutoSync) return;
DatabaseLogger.info(`onRequestPartialSync full:${full}, force:${force}`);
DatabaseLogger.info(
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
);
if (full || force) {
await Sync.run("global", force, full);
await Sync.run("global", force, full, undefined, lastSyncTime);
} else {
await Sync.run("global", false, false);
await Sync.run("global", false, false, undefined, lastSyncTime);
}
};
@@ -253,7 +248,6 @@ export const useAppEvents = () => {
]);
const syncedOnLaunch = useRef(false);
const { isDark } = useThemeColors();
const refValues = useRef({
subsriptionSuccessListener: null,
subsriptionErrorListener: null,
@@ -278,7 +272,6 @@ export const useAppEvents = () => {
const eventManager = db?.eventManager;
eventSubscriptions = [
eventManager?.subscribe(EVENTS.syncCompleted, onSyncComplete),
eventManager?.subscribe(EVENTS.syncProgress, onSyncProgress),
eventManager?.subscribe(
EVENTS.databaseSyncRequested,
onRequestPartialSync
@@ -533,6 +526,12 @@ export const useAppEvents = () => {
if (refValues.current?.isReconnecting || !refValues.current?.isUserReady)
return;
if (useSettingStore.getState().appDidEnterBackgroundForAction) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
console.log("AppDidEnterForegroundAfterAction");
return;
}
if (SettingsService.get().sessionExpired) {
refValues.current.isReconnecting = false;
return;

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/>.
*/
import { useRef } from "react";
import { MMKV } from "../common/database/mmkv";
export function useStoredRef<T>(
key: string,
initialValue: T
): {
current: T;
reset(): void;
} {
const refKey = `storedRef:${key}`;
const value = useRef(
MMKV.getMap<{ current: T }>(refKey)?.current || initialValue
);
const frameRef = useRef(0);
return {
get current() {
return value.current;
},
set current(next: T) {
value.current = next;
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
current: value.current
});
});
},
reset() {
MMKV.removeItem(refKey);
}
};
}

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/>.
*/
import { useRef, useState } from "react";
import { MMKV } from "../common/database/mmkv";
export function useStoredValue<T>(
key: string,
initialValue: T
): { value: T; reset(): void } {
const refKey = `storedState:${key}`;
const [value, setValue] = useState<T>(
MMKV.getMap<{ value: T }>(refKey)?.value || initialValue
);
const frameRef = useRef(0);
return {
get value() {
return value;
},
set value(next: T) {
setValue(next);
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
value: value
});
});
},
reset() {
MMKV.removeItem(refKey);
}
};
}

View File

@@ -34,6 +34,7 @@ import PremiumService from "../../../services/premium";
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
import { eCloseSheet } from "../../../utils/events";
import { editorController, editorState } from "./utils";
import { useSettingStore } from "../../../stores/use-setting-store";
const showEncryptionSheet = (file) => {
presentSheet({
@@ -62,6 +63,7 @@ const file = async (fileOptions) => {
let file;
try {
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
file = await DocumentPicker.pick(options);
} catch (e) {
return;
@@ -135,6 +137,7 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchCamera(
{
includeBase64: true,
@@ -156,6 +159,7 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchImageLibrary(
{
includeBase64: true,

View File

@@ -141,6 +141,7 @@ export const useEditor = (
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
lock.current = false;
useEditorStore.getState().setReadonly(false);
resetContent && postMessage(EditorEvents.title, "");
lastContentChangeTime.current = 0;

View File

@@ -55,6 +55,7 @@ export const EditorWrapper = ({ width }) => {
prevState.current = state;
return;
}
if (useSettingStore.getState().appDidEnterBackgroundForAction) return;
if (state === "active") {
editorController.current.onReady();
editorController.current.overlay(false);

View File

@@ -42,7 +42,8 @@ export async function verifyUser(
negativeText: closeText || "Cancel",
positivePress: async (value) => {
try {
let verified = await db.user.verifyPassword(value);
const user = await db.user.getUser();
let verified = !user ? true : await db.user.verifyPassword(value);
if (verified) {
sleep(300).then(async () => {
await onsuccess();
@@ -58,7 +59,7 @@ export async function verifyUser(
}
} catch (e) {
ToastEvent.show({
heading: "Failed to backup data",
heading: "Failed to verify",
message: e.message,
type: "error",
context: "global"

View File

@@ -27,6 +27,8 @@ import PremiumService from "../../../services/premium";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import { verifyUser } from "../functions";
import { Dialog } from "../../../components/dialog";
interface PickerOptions<T> {
getValue: () => T;
@@ -37,6 +39,7 @@ interface PickerOptions<T> {
options: T[];
premium?: boolean;
onCheckOptionIsPremium?: (item: T) => boolean;
requiresVerification?: () => boolean;
}
export function SettingsPicker<T>({
@@ -47,7 +50,8 @@ export function SettingsPicker<T>({
options,
getItemKey,
premium,
onCheckOptionIsPremium = () => true
onCheckOptionIsPremium = () => true,
requiresVerification = () => false
}: PickerOptions<T>) {
const { colors } = useThemeColors("contextMenu");
const menuRef = useRef<any>();
@@ -118,11 +122,18 @@ export function SettingsPicker<T>({
</PressableButton>
}
>
<Dialog context="local" />
{options.map((item) => (
<MenuItem
key={getItemKey(item)}
onPress={async () => {
onChange(item);
if (requiresVerification?.()) {
verifyUser("local", () => {
onChange(item);
});
} else {
onChange(item);
}
}}
style={{
backgroundColor: compareValue(currentValue, item)

View File

@@ -26,6 +26,7 @@ import { createSettingsPicker } from ".";
import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/dist/common";
import dayjs from "dayjs";
import { useUserStore } from "../../../stores/use-user-store";
export const FontPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
@@ -127,6 +128,12 @@ export const BackupReminderPicker = createSettingsPicker({
options: ["useroff", "daily", "weekly", "monthly"],
compareValue: (current, item) => current === item,
premium: true,
requiresVerification: () => {
return (
!useSettingStore.getState().settings.encryptedBackup &&
useUserStore.getState().user
);
},
onCheckOptionIsPremium: (item) => {
return item !== "useroff";
}

View File

@@ -782,10 +782,11 @@ export const settingsGroups: SettingSection[] = [
description: "Create a backup of your data",
modifer: async () => {
const user = useUserStore.getState().user;
if (!user) {
if (!user || SettingsService.getProperty("encryptedBackup")) {
await BackupService.run(true);
return;
}
verifyUser(null, () => BackupService.run(true));
}
},
@@ -869,9 +870,17 @@ export const settingsGroups: SettingSection[] = [
});
return;
}
SettingsService.set({
encryptedBackup: !settings.encryptedBackup
});
if (settings.encryptedBackup) {
await verifyUser(null, () => {
SettingsService.set({
encryptedBackup: false
});
});
} else {
SettingsService.set({
encryptedBackup: true
});
}
}
}
]
@@ -901,7 +910,9 @@ export const settingsGroups: SettingSection[] = [
description:
"Export all notes as pdf, markdown, html or text in a single zip file",
modifer: () => {
ExportNotesSheet.present(undefined, true);
verifyUser(null, () => {
ExportNotesSheet.present(undefined, true);
});
}
}
]

View File

@@ -748,7 +748,7 @@ const ThemeSetter = ({
paddingVertical: 12
}}
>
<Heading color={colors.accent} size={SIZE.md}>
<Heading color={colors.accentForeground} size={SIZE.md}>
{darkTheme.id === theme.id
? "Applied as dark theme"
: "Applied as light theme"}

View File

@@ -17,7 +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 { sanitizeFilename } from "@notesnook/common";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import FileViewer from "react-native-file-viewer";
@@ -30,6 +29,8 @@ import { eCloseSheet } from "../utils/events";
import { sleep } from "../utils/time";
import { ToastEvent, eSendEvent, presentSheet } from "./event-manager";
import SettingsService from "./settings";
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
import { zip } from "react-native-zip-archive";
const MS_DAY = 86400000;
const MS_WEEK = MS_DAY * 7;
@@ -152,8 +153,6 @@ async function run(progress, context) {
let androidBackupDirectory = await checkBackupDirExists(false, context);
if (!androidBackupDirectory) return;
let backup;
if (progress) {
presentSheet({
title: "Backing up your data",
@@ -163,43 +162,67 @@ async function run(progress, context) {
});
}
try {
backup = await db.backup.export(
"mobile",
SettingsService.get().encryptedBackup
);
if (!backup) throw new Error("Backup returned empty.");
} catch (e) {
await sleep(300);
eSendEvent(eCloseSheet);
ToastEvent.error(e, "Backup failed!");
return null;
let path;
let backupFilePath;
let backupFileName = "notesnook_backup_" + Date.now();
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups/");
}
try {
let backupName = "notesnook_backup_" + Date.now();
backupName =
sanitizeFilename(backupName, { replacement: "_" }) + ".nnbackup";
let path;
let backupFilePath;
const zipSourceFolder = `${cacheDir}/${backupFileName}`;
const zipOutputFile =
Platform.OS === "ios"
? `${path}/${backupFileName}.nnbackupz`
: `${cacheDir}/${backupFileName}.nnbackupz`;
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups/");
await RNFetchBlob.fs.writeFile(path + backupName, backup, "utf8");
backupFilePath = path + backupName;
} else {
backupFilePath = await ScopedStorage.writeFile(
androidBackupDirectory.uri,
backup,
backupName,
"nnbackup/json",
"utf8",
false
if (await RNFetchBlob.fs.exists(zipSourceFolder))
await RNFetchBlob.fs.unlink(zipSourceFolder);
await RNFetchBlob.fs.mkdir(zipSourceFolder);
try {
for await (const file of db.backup.export(
"mobile",
SettingsService.get().encryptedBackup
)) {
console.log("Writing backup chunk of size...", file?.data?.length);
await RNFetchBlob.fs.writeFile(
`${zipSourceFolder}/${file.path}`,
file.data,
"utf8"
);
}
await zip(zipSourceFolder, zipOutputFile);
console.log("Final zip:", await RNFetchBlob.fs.stat(zipOutputFile));
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
androidBackupDirectory.uri,
`${backupFileName}.nnbackupz`,
"application/nnbackupz"
);
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
path = file.uri;
} else {
path = zipOutputFile;
}
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
updateNextBackupTime();
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
if (context) return path;
await sleep(300);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
} else {
progress && eSendEvent(eCloseSheet);
}
ToastEvent.show({
heading: "Backup successful",
message: "Your backup is stored in Notesnook folder on your phone.",
@@ -207,18 +230,10 @@ async function run(progress, context) {
context: "global"
});
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
if (context) return backupFilePath;
await sleep(300);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
} else {
progress && eSendEvent(eCloseSheet);
}
return backupFilePath;
return path;
} catch (e) {
progress && eSendEvent(eCloseSheet);
await sleep(300);
eSendEvent(eCloseSheet);
ToastEvent.error(e, "Backup failed!");
return null;
}

View File

@@ -49,7 +49,6 @@ import NetInfo from "@react-native-community/netinfo";
import { encodeNonAsciiHTML } from "entities";
import { convertNoteToText } from "../utils/note-to-text";
export type Reminder = {
id: string;
type: string;
@@ -142,6 +141,7 @@ const onEvent = async ({ type, detail }: Event) => {
const ReminderNotify =
require("../components/sheets/reminder-notify").default;
ReminderNotify.present(reminder);
useRelationStore.getState().update();
return;
}
editorState().movedAway = false;

View File

@@ -39,7 +39,8 @@ const run = async (
context = "global",
forced = false,
full = true,
onCompleted
onCompleted,
lastSyncTime
) => {
if (useUserStore.getState().syncing) {
DatabaseLogger.log("Sync in progress");
@@ -68,7 +69,7 @@ const run = async (
try {
let res = await BackgroundSync.doInBackground(async () => {
try {
await db.sync(full, forced);
await db.sync(full, forced, lastSyncTime);
return true;
} catch (e) {
error = e;

View File

@@ -103,6 +103,8 @@ export interface SettingStore extends State {
sheetKeyboardHandler: boolean;
requestBiometrics: boolean;
setRequestBiometrics: (requestBiometrics: boolean) => void;
appDidEnterBackgroundForAction: boolean;
setAppDidEnterBackgroundForAction: (value: boolean) => void;
insets: Insets;
setInsets: (insets: Insets) => void;
timeFormat: string;
@@ -155,7 +157,7 @@ export const defaultSettings: SettingStore["settings"] = {
darkTheme: ThemeDark
};
export const useSettingStore = create<SettingStore>((set) => ({
export const useSettingStore = create<SettingStore>((set, get) => ({
settings: { ...defaultSettings },
sheetKeyboardHandler: true,
fullscreen: false,
@@ -174,6 +176,12 @@ export const useSettingStore = create<SettingStore>((set) => ({
setInsets: (insets) => set({ insets }),
timeFormat: "12-hour",
dateFormat: "DD-MM-YYYY",
setAppDidEnterBackgroundForAction: (value: boolean) => {
set({
appDidEnterBackgroundForAction: value
});
},
appDidEnterBackgroundForAction: false,
insets: initialWindowMetrics?.insets
? initialWindowMetrics.insets
: { top: 0, right: 0, left: 0, bottom: 0 }

View File

@@ -107,46 +107,43 @@ export const deleteItems = async (item, context) => {
if (topics?.length > 0) {
const result = await confirmDeleteAllNotes(topics, "topic", context);
if (result.delete) {
for (const topic of topics) {
if (result.deleteNotes) {
if (!result.delete) return;
for (const topic of topics) {
if (result.deleteNotes) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
}
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
type: "success"
});
}
if (notebooks?.length > 0) {
const result = await confirmDeleteAllNotes(notebooks, "notebook", context);
if (!result.delete) return;
let ids = notebooks.map((i) => i.id);
if (result.deleteNotes) {
for (let id of ids) {
const notebook = db.notebooks.notebook(id);
const topics = notebook.topics.all;
for (let topic of topics) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
const notes = db.relations.from(notebook.data, "note");
await db.notes.delete(...notes.map((note) => note.id));
}
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
type: "success"
});
}
}
if (notebooks?.length > 0) {
const result = await confirmDeleteAllNotes(notebooks, "notebook", context);
if (result.delete) {
let ids = notebooks.map((i) => i.id);
if (result.deleteNotes) {
for (let id of ids) {
const notebook = db.notebooks.notebook(id);
const topics = notebook.topics.all;
for (let topic of topics) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
const notes = db.relations.from(notebook.data, "note");
await db.notes.delete(...notes.map((note) => note.id));
}
}
await db.notebooks.delete(...ids);
useMenuStore.getState().setMenuPins();
}
await db.notebooks.delete(...ids);
useMenuStore.getState().setMenuPins();
}
Navigation.queueRoutesForUpdate();

View File

@@ -111,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2057
versionCode 2059
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -1,8 +1,4 @@
- Added support for custom themes
- Syntax highlighting and rendered Math in exports
- Improved copy/pasting to and from Notesnook
- Support copying link & link text in directly from the Link toolbar
- Many other bug fixes and performance improvements
Thank you for using Notesnook!
- Much improved and faster sync
- Note pinned in notifications will update automatically when content changes
- Quick notes from notifications now respect paragraph spacing settings.
- Many bug fixes and small improvements

View File

@@ -33,6 +33,8 @@
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationDictionaryVersion</key>
<integer>2</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>5</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>

View File

@@ -997,7 +997,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2050;
CURRENT_PROJECT_VERSION = 2053;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1071,7 +1071,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.1;
MARKETING_VERSION = 2.6.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1102,7 +1102,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2050;
CURRENT_PROJECT_VERSION = 2053;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1176,7 +1176,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.1;
MARKETING_VERSION = 2.6.3;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1335,7 +1335,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2050;
CURRENT_PROJECT_VERSION = 2053;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1347,7 +1347,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.1;
MARKETING_VERSION = 2.6.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1378,7 +1378,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2050;
CURRENT_PROJECT_VERSION = 2053;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1391,7 +1391,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.1;
MARKETING_VERSION = 2.6.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1421,7 +1421,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2050;
CURRENT_PROJECT_VERSION = 2053;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1495,7 +1495,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.1;
MARKETING_VERSION = 2.6.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1526,7 +1526,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2050;
CURRENT_PROJECT_VERSION = 2053;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1601,7 +1601,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.1;
MARKETING_VERSION = 2.6.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -63,7 +63,7 @@
<EnvironmentVariables>
<EnvironmentVariable
key = "NSZombieEnabled"
value = "YES"
value = "NO"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
@@ -77,13 +77,6 @@
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -327,7 +327,7 @@ PODS:
- React-Core
- react-native-keep-awake (1.2.0):
- React-Core
- react-native-mmkv-storage (0.10.0-alpha.6):
- react-native-mmkv-storage (0.10.0-alpha.7):
- MMKV (~> 1.3.1)
- React
- React-Core
@@ -347,7 +347,7 @@ PODS:
- ReactCommon/turbomodule/core
- react-native-share-extension (2.5.6):
- React
- react-native-sodium (1.5.1):
- react-native-sodium (1.5.2):
- React
- react-native-webview (11.26.1):
- React-Core
@@ -875,14 +875,14 @@ SPEC CHECKSUMS:
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
react-native-keep-awake: caee3ff89eaa21dfe29010f0d143566874a04441
react-native-mmkv-storage: a38beae34b1f7906a2f5e715f54216f9ddab14c0
react-native-mmkv-storage: ec32fd68168716755e1b30f09b476c236c6c68a8
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
react-native-safe-area-context: 36cc67648134e89465663b8172336a19eeda493d
react-native-share-extension: df66a2ee48a62277d79898375e2142bde0782063
react-native-sodium: 08d459dfcb5d246363a30712d816acea56ab8f1c
react-native-sodium: 7d2bbb9ca840dc58d63f661b2ff733a38170fec5
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
React-NativeModulesApple: 1d81d927ef1a67a3545a01e14c2e98500bf9b199
React-perflogger: 684a11499a0589cc42135d6d5cc04d0e4e0e261a

View File

@@ -44,7 +44,7 @@
"react-native-image-picker": "4.1.2",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.7",
"react-native-mmkv-storage": "^0.10.0-alpha.9",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "2.6.1",
"version": "2.6.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "2.6.1",
"version": "2.6.2",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -86,8 +86,8 @@
"cross-env": "^7.0.3",
"dotenv": "^16.0.1",
"eventsource": "^2.0.2",
"happy-dom": "^10.9.0",
"isomorphic-fetch": "^3.0.0",
"jsdom": "^22.1.0",
"mockdate": "^3.0.5",
"otplib": "^12.0.1",
"refractor": "^4.8.1",
@@ -101,40 +101,43 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@emotion/react": "^11.10.0",
"@emotion/react": "11.11.1",
"@notesnook/core": "file:../core",
"@notesnook/theme": "file:../theme",
"@notesnook/ui": "file:../ui",
"@social-embed/lib": "^0.0.2-next.1",
"@theme-ui/components": ">=0.14.0",
"@theme-ui/core": ">=0.14.0",
"@tiptap/core": "2.0.3",
"@tiptap/extension-character-count": "2.0.3",
"@tiptap/extension-color": "2.0.3",
"@tiptap/extension-font-family": "2.0.3",
"@tiptap/extension-history": "2.0.3",
"@tiptap/extension-horizontal-rule": "2.0.3",
"@tiptap/extension-link": "2.0.3",
"@tiptap/extension-placeholder": "2.0.3",
"@tiptap/extension-subscript": "2.0.3",
"@tiptap/extension-superscript": "2.0.3",
"@tiptap/extension-table": "2.0.3",
"@tiptap/extension-table-cell": "2.0.3",
"@tiptap/extension-table-header": "2.0.3",
"@tiptap/extension-table-row": "2.0.3",
"@tiptap/extension-task-item": "2.0.3",
"@tiptap/extension-task-list": "2.0.3",
"@tiptap/extension-text-align": "2.0.3",
"@tiptap/extension-text-style": "2.0.3",
"@tiptap/extension-underline": "2.0.3",
"@tiptap/pm": "2.0.3",
"@tiptap/starter-kit": "2.0.3",
"@tiptap/core": "2.1.7",
"@tiptap/extension-character-count": "2.1.7",
"@tiptap/extension-color": "2.1.7",
"@tiptap/extension-font-family": "2.1.7",
"@tiptap/extension-history": "2.1.7",
"@tiptap/extension-horizontal-rule": "2.1.7",
"@tiptap/extension-link": "2.1.7",
"@tiptap/extension-list-keymap": "2.1.7",
"@tiptap/extension-placeholder": "2.1.7",
"@tiptap/extension-subscript": "2.1.7",
"@tiptap/extension-superscript": "2.1.7",
"@tiptap/extension-table": "2.1.7",
"@tiptap/extension-table-cell": "2.1.7",
"@tiptap/extension-table-header": "2.1.7",
"@tiptap/extension-table-row": "2.1.7",
"@tiptap/extension-task-item": "2.1.7",
"@tiptap/extension-task-list": "2.1.7",
"@tiptap/extension-text-align": "2.1.7",
"@tiptap/extension-text-style": "2.1.7",
"@tiptap/extension-underline": "2.1.7",
"@tiptap/pm": "2.1.7",
"@tiptap/starter-kit": "2.1.7",
"clipboard-polyfill": "4.0.0",
"detect-indent": "^7.0.0",
"entities": "^4.5.0",
"katex": "0.16.4",
"nanoid": "^4.0.1",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.1",
"prosemirror-codemark": "^0.4.2",
"prosemirror-view": "^1.31.7",
"re-resizable": "^6.9.9",
"react-colorful": "^5.5.1",
"react-modal": "3.13.1",
@@ -191,14 +194,17 @@
"zustand": "^3.6.8"
},
"devDependencies": {
"@playwright/test": "^1.37.1",
"@testing-library/jest-dom": "^5.16.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@types/ip": "^1.1.0",
"@types/jest": "^27.0.3",
"@types/node": "^16.11.11",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"env-cmd": "^10.1.0",
"ip": "^1.1.8",
"react-error-overlay": "6.0.9",
"react-scripts": "^5.0.1",
"web-vitals": "^1.1.2"
@@ -309,7 +315,7 @@
"react-native-image-picker": "4.1.2",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.7",
"react-native-mmkv-storage": "^0.10.0-alpha.9",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
@@ -18254,9 +18260,9 @@
"integrity": "sha512-TbiO8AO055EPjaxIuKxrCEOB4r81uQCVDFbtcs+e4pEOHxf6z3JgDC0UU0poJElKfCh37b+TFRxQhg6DbsYJBA=="
},
"node_modules/react-native-mmkv-storage": {
"version": "0.10.0-alpha.7",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.10.0-alpha.7.tgz",
"integrity": "sha512-MCMLMh0NYB4U9My/AZxdcfd/Skg7Pqfe+BHI5aMgV7+HUfAtEA08OX7FGPK6WCVFkNm0JSJt6VSthla/xNWZDg==",
"version": "0.10.0-alpha.9",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.10.0-alpha.9.tgz",
"integrity": "sha512-nv1A0t5dxhcAtMnhqqBodNp94U4QziW1RO352GB3BPpic03abPHj+0HjBJH72VeKKMfYIc0PR5pmo6BbsaPExw==",
"bin": {
"mmkv-link": "autolink/postlink/run.js"
},
@@ -26433,10 +26439,10 @@
"dotenv": "^16.0.1",
"entities": "^4.3.1",
"eventsource": "^2.0.2",
"happy-dom": "^10.9.0",
"html-to-text": "^9.0.5",
"htmlparser2": "^8.0.1",
"isomorphic-fetch": "^3.0.0",
"jsdom": "^22.1.0",
"katex": "0.16.2",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
@@ -26454,7 +26460,7 @@
"@notesnook/editor": {
"version": "file:../../packages/editor",
"requires": {
"@emotion/react": "^11.10.0",
"@emotion/react": "11.11.1",
"@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.1",
"@notesnook/core": "file:../core",
@@ -26463,27 +26469,28 @@
"@social-embed/lib": "^0.0.2-next.1",
"@theme-ui/components": ">=0.14.0",
"@theme-ui/core": ">=0.14.0",
"@tiptap/core": "2.0.3",
"@tiptap/extension-character-count": "2.0.3",
"@tiptap/extension-color": "2.0.3",
"@tiptap/extension-font-family": "2.0.3",
"@tiptap/extension-history": "2.0.3",
"@tiptap/extension-horizontal-rule": "2.0.3",
"@tiptap/extension-link": "2.0.3",
"@tiptap/extension-placeholder": "2.0.3",
"@tiptap/extension-subscript": "2.0.3",
"@tiptap/extension-superscript": "2.0.3",
"@tiptap/extension-table": "2.0.3",
"@tiptap/extension-table-cell": "2.0.3",
"@tiptap/extension-table-header": "2.0.3",
"@tiptap/extension-table-row": "2.0.3",
"@tiptap/extension-task-item": "2.0.3",
"@tiptap/extension-task-list": "2.0.3",
"@tiptap/extension-text-align": "2.0.3",
"@tiptap/extension-text-style": "2.0.3",
"@tiptap/extension-underline": "2.0.3",
"@tiptap/pm": "2.0.3",
"@tiptap/starter-kit": "2.0.3",
"@tiptap/core": "2.1.7",
"@tiptap/extension-character-count": "2.1.7",
"@tiptap/extension-color": "2.1.7",
"@tiptap/extension-font-family": "2.1.7",
"@tiptap/extension-history": "2.1.7",
"@tiptap/extension-horizontal-rule": "2.1.7",
"@tiptap/extension-link": "2.1.7",
"@tiptap/extension-list-keymap": "2.1.7",
"@tiptap/extension-placeholder": "2.1.7",
"@tiptap/extension-subscript": "2.1.7",
"@tiptap/extension-superscript": "2.1.7",
"@tiptap/extension-table": "2.1.7",
"@tiptap/extension-table-cell": "2.1.7",
"@tiptap/extension-table-header": "2.1.7",
"@tiptap/extension-table-row": "2.1.7",
"@tiptap/extension-task-item": "2.1.7",
"@tiptap/extension-task-list": "2.1.7",
"@tiptap/extension-text-align": "2.1.7",
"@tiptap/extension-text-style": "2.1.7",
"@tiptap/extension-underline": "2.1.7",
"@tiptap/pm": "2.1.7",
"@tiptap/starter-kit": "2.1.7",
"@types/katex": "^0.14.0",
"@types/prismjs": "^1.26.0",
"@types/react": "17.0.2",
@@ -26493,14 +26500,16 @@
"@types/tinycolor2": "^1.4.3",
"clipboard-polyfill": "4.0.0",
"detect-indent": "^7.0.0",
"entities": "^4.5.0",
"framer-motion": "^6.5.1",
"happy-dom": "^8.9.0",
"isomorphic-fetch": "^3.0.0",
"katex": "0.16.4",
"nanoid": "^4.0.1",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.1",
"prosemirror-codemark": "^0.4.2",
"prosemirror-test-builder": "^1.1.0",
"prosemirror-view": "^1.31.7",
"re-resizable": "^6.9.9",
"react": "17.0.2",
"react-colorful": "^5.5.1",
@@ -26524,15 +26533,18 @@
"@mdi/react": "^1.6.0",
"@notesnook/editor": "file:../editor",
"@notesnook/theme": "file:../theme",
"@playwright/test": "^1.37.1",
"@testing-library/jest-dom": "^5.16.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@types/ip": "^1.1.0",
"@types/jest": "^27.0.3",
"@types/node": "^16.11.11",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"env-cmd": "^10.1.0",
"framer-motion": "^6.5.1",
"ip": "^1.1.8",
"mdi-react": "9.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -26657,7 +26669,7 @@
"react-native-image-picker": "4.1.2",
"react-native-in-app-review": "4.3.3",
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.10.0-alpha.7",
"react-native-mmkv-storage": "^0.10.0-alpha.9",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
@@ -35790,9 +35802,9 @@
"integrity": "sha512-TbiO8AO055EPjaxIuKxrCEOB4r81uQCVDFbtcs+e4pEOHxf6z3JgDC0UU0poJElKfCh37b+TFRxQhg6DbsYJBA=="
},
"react-native-mmkv-storage": {
"version": "0.10.0-alpha.7",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.10.0-alpha.7.tgz",
"integrity": "sha512-MCMLMh0NYB4U9My/AZxdcfd/Skg7Pqfe+BHI5aMgV7+HUfAtEA08OX7FGPK6WCVFkNm0JSJt6VSthla/xNWZDg==",
"version": "0.10.0-alpha.9",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.10.0-alpha.9.tgz",
"integrity": "sha512-nv1A0t5dxhcAtMnhqqBodNp94U4QziW1RO352GB3BPpic03abPHj+0HjBJH72VeKKMfYIc0PR5pmo6BbsaPExw==",
"requires": {}
},
"react-native-modal-datetime-picker": {

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.6.1",
"version": "2.6.3",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -43,4 +43,4 @@
"react": "18.2.0",
"react-native": "0.72.0"
}
}
}

View File

@@ -602,8 +602,9 @@ const ShareView = ({ quicknote = false }) => {
marginBottom: 0
}}
loading={loading}
iconColor={colors.static.white}
iconColor={colors.primary.accentForeground}
onPress={onPress}
textColor={colors.primary.accentForeground}
textStyle={{
fontSize: 16,
marginLeft: 0

View File

@@ -42,20 +42,24 @@ let appSettings = MMKV.getString(StorageKeys.appSettings);
if (appSettings) {
appSettings = JSON.parse(appSettings);
}
const systemColorScheme = Appearance.getColorScheme();
const appColorScheme = appSettings?.colorScheme;
const useSystemTheme = appSettings?.useSystemTheme;
const currentColorScheme = useSystemTheme ? systemColorScheme : appColorScheme;
const theme =
Appearance.getColorScheme() !== "dark"
? appSettings?.darkThem
currentColorScheme === "dark"
? appSettings?.darkTheme
: appSettings?.lightTheme;
useThemeEngineStore
.getState()
.setTheme(
theme || (Appearance.getColorScheme() === "dark" ? ThemeDark : ThemeLight)
);
const currentTheme =
theme || (currentColorScheme === "dark" ? ThemeDark : ThemeLight);
useThemeEngineStore.getState().setTheme(currentTheme);
export const useShareStore = create((set) => ({
theme:
theme || (Appearance.getColorScheme() === "dark" ? ThemeDark : ThemeLight),
theme: currentTheme,
appendNote: null,
setAppendNote: (note) => {
MMKV.setItem(StorageKeys.appendNote, JSON.stringify(note));

View File

@@ -210,17 +210,6 @@
},
"devDependencies": {}
},
"../../packages/crypto-worker": {
"name": "@notesnook/crypto-worker",
"version": "1.0.0",
"extraneous": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "file:../crypto",
"comlink": "^4.3.1"
},
"devDependencies": {}
},
"../../packages/editor": {
"name": "@notesnook/editor",
"version": "1.6.1",
@@ -1402,7 +1391,6 @@
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto",
"@notesnook/crypto-worker": "file:../../packages/crypto-worker",
"@notesnook/desktop": "file:../desktop",
"@notesnook/editor": "file:../../packages/editor",
"@notesnook/logger": "file:../../packages/logger",

View File

@@ -23,7 +23,8 @@ import {
ThemeDefinition,
THEME_SCOPES,
Variants,
validateTheme
validateTheme,
ALPHA_COLORS
} from "@notesnook/theme";
import { Button, Flex, Input, Text } from "@theme-ui/components";
import FileSaver from "file-saver";
@@ -44,14 +45,15 @@ const JSON_SCHEMA_URL =
"https://raw.githubusercontent.com/streetwriters/notesnook-themes/main/schemas/v1.schema.json";
const ThemeInfoTemplate: Omit<
ThemeDefinition,
"authors" | "compatibilityVersion" | "colorScheme" | "codeBlockCSS" | "scopes"
"authors" | "compatibilityVersion" | "colorScheme" | "scopes"
> = {
name: "",
id: "",
version: 0,
license: "",
homepage: "",
description: ""
description: "",
codeBlockCSS: "https://github.com/PrismJS/prism-themes/tree/master/themes"
};
export default function ThemeBuilder() {
@@ -302,7 +304,7 @@ export default function ThemeBuilder() {
paddingX: "10px"
}}
>
{Object.keys(ThemeInfoTemplate).map((key) => {
{Object.keys(ThemeInfoTemplate).map((key: string) => {
return (
<Field
key={`${getThemeKey(currentTheme)}-${key}`}
@@ -355,7 +357,7 @@ export default function ThemeBuilder() {
key="compatibilityVersion"
/>
{authors.map((author, index) => (
{authors.map((author, index: number) => (
<Flex
key={`${getThemeKey(currentTheme)}-${author.name}`}
sx={{
@@ -524,9 +526,7 @@ export default function ThemeBuilder() {
}
}}
title={
/hover|shade|backdrop|textSelection/g.test(
colorName
)
ALPHA_COLORS.includes(colorName)
? `Hex RGB & ARGB values both are supported. (e.g. #dbdbdb99)`
: `Only Hex RGB values are supported. No Alpha. (e.g. #f33ff3)`
}
@@ -555,9 +555,7 @@ export default function ThemeBuilder() {
);
}}
title={
/hover|shade|backdrop|textSelection/g.test(
colorName
)
ALPHA_COLORS.includes(colorName)
? `Only Hex RGB values are supported. No Alpha. (e.g. #f33ff3)`
: `Hex RGB & ARGB values both are supported. (e.g. #dbdbdb99)`
}
@@ -645,13 +643,25 @@ function exportTheme(theme: ThemeDefinition) {
}),
`${theme.id}.json`
);
const confirmed = window.confirm(
"Do you also want to download code-block.css for this theme?"
);
if (confirmed) {
FileSaver.saveAs(
new Blob([theme.codeBlockCSS], {
type: "text/plain"
}),
`code-block.css`
);
}
}
function themeToJSON(theme: ThemeDefinition) {
return JSON.stringify(
{
$schema: JSON_SCHEMA_URL,
...theme
...theme,
codeBlockCSS: undefined
},
undefined,
2
@@ -698,7 +708,10 @@ function getThemeFromFormData(
function toTitleCase(value: string) {
return (
value.slice(0, 1).toUpperCase() +
value.slice(1).replace(/([A-Z]+)*([A-Z][a-z])/g, "$1 $2")
value
.slice(1)
.replace(/([A-Z]+)/g, " $1")
.replace(/([A-Z][a-z])/g, " $1")
);
}

View File

@@ -11,7 +11,6 @@
"@emotion/react": "^11.10.0",
"@microsoft/signalr-protocol-msgpack": "^7.0.9",
"@notesnook/crypto": "file:../../packages/crypto",
"@notesnook/crypto-worker": "file:../../packages/crypto-worker",
"@notesnook/theme": "1.2.0",
"@theme-ui/components": "^0.14.7",
"@theme-ui/core": "^0.14.7",
@@ -42,16 +41,6 @@
},
"devDependencies": {}
},
"../../packages/crypto-worker": {
"name": "@notesnook/crypto-worker",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "file:../crypto",
"comlink": "^4.3.1"
},
"devDependencies": {}
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
@@ -915,10 +904,6 @@
"resolved": "../../packages/crypto",
"link": true
},
"node_modules/@notesnook/crypto-worker": {
"resolved": "../../packages/crypto-worker",
"link": true
},
"node_modules/@notesnook/theme": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@notesnook/theme/-/theme-1.2.0.tgz",

View File

@@ -16,7 +16,6 @@
"@emotion/react": "^11.10.0",
"@microsoft/signalr-protocol-msgpack": "^7.0.9",
"@notesnook/crypto": "file:../../packages/crypto",
"@notesnook/crypto-worker": "file:../../packages/crypto-worker",
"@notesnook/theme": "1.2.0",
"@theme-ui/components": "^0.14.7",
"@theme-ui/core": "^0.14.7",

View File

@@ -74,3 +74,30 @@ test("restore an encrypted backup", async ({ page }) => {
const notebooks = await app.goToNotebooks();
expect(await notebooks.isEmpty()).toBeFalsy();
});
test("turning off encrypted backups should ask for password", async ({
page
}) => {
const app = new AppModel(page);
await app.auth.goto();
await app.auth.login(USER.CURRENT);
const settings = await app.goToSettings();
await settings.toggleBackupEncryption(USER.CURRENT.password);
expect(await settings.isBackupEncryptionEnabled(false)).toBe(false);
});
test("turning on encrypted backups should not ask for password", async ({
page
}) => {
const app = new AppModel(page);
await app.auth.goto();
await app.auth.login(USER.CURRENT);
const settings = await app.goToSettings();
await settings.toggleBackupEncryption(USER.CURRENT.password);
await settings.toggleBackupEncryption();
expect(await settings.isBackupEncryptionEnabled(true)).toBe(true);
});

View File

@@ -84,16 +84,32 @@ export class SettingsViewModel {
return !!(await item?.getTitle());
}
async createBackup(password?: string) {
async isBackupEncryptionEnabled(state: boolean) {
const encyptBackups = this.page
.locator(getTestId("setting-encrypt-backups"))
.locator(
state ? `input[data-checked="true"]` : `input[data-checked="false"]`
);
await encyptBackups.waitFor({ state: "visible" });
return (await encyptBackups.getAttribute("data-checked")) === "true";
}
async toggleBackupEncryption(password?: string) {
const item = await this.navigation.findItem("Backup & export");
await item?.click();
if (password) {
const encyptBackups = this.page
.locator(getTestId("setting-encrypt-backups"))
.locator("label");
await encyptBackups.click();
}
const encyptBackups = this.page
.locator(getTestId("setting-encrypt-backups"))
.locator("label");
await encyptBackups.click();
if (password) await fillPasswordDialog(this.page, password);
}
async createBackup(password?: string) {
const item = await this.navigation.findItem("Backup & export");
await item?.click();
const backupData = this.page
.locator(getTestId("setting-create-backup"))

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.6.1",
"version": "2.6.4",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -11,7 +11,7 @@
.allotment-module_splitView__L-yRc.allotment-module_separatorBorder__x-rDS
> .allotment-module_splitViewContainer__rQnVa
> .allotment-module_splitViewView__MGZ6O:not(:first-child)::before {
background-color: var(--border);
background-color: transparent;
}
/* open-sans-regular - vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic */
@font-face {
@@ -138,25 +138,25 @@ textarea,
}
.rpv-core__text-layer-text::selection {
background-color: var(--textSelection) !important;
background-color: var(--background-selected) !important;
color: transparent;
}
.rpv-core__text-layer-text::-moz-selection {
/* Code for Firefox */
background-color: var(--textSelection) !important;
background-color: var(--background-selected) !important;
color: transparent;
}
::-moz-selection {
/* Code for Firefox */
background-color: var(--textSelection);
color: var(--paragraph);
background-color: var(--background-selected);
color: var(--paragraph-selected);
}
::selection {
background-color: var(--textSelection);
color: var(--paragraph);
background-color: var(--background-selected);
color: var(--paragraph-selected);
}
*::-moz-focus-inner {

View File

@@ -177,7 +177,8 @@ function DesktopAppContents({
display: "flex",
flexDirection: "column",
flex: 1,
bg: "background"
bg: "background",
borderRight: "1px solid var(--separator)"
}}
>
{isAppLoaded && <CachedRouter />}

View File

@@ -63,3 +63,17 @@ function attachListener(event: string) {
}
};
}
export async function createWritableStream(path: string) {
const resolvedPath = await desktop.integration.resolvePath.query({
filePath: path
});
if (!resolvedPath) throw new Error("invalid path.");
const fs = require("fs");
const { Writable } = require("stream");
return new WritableStream(
Writable.toWeb(
fs.createWriteStream(resolvedPath, { encoding: "utf-8" })
).getWriter()
);
}

View File

@@ -17,6 +17,10 @@ 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 { createWriteStream } from "../../utils/stream-saver";
import { type desktop as bridge } from "./index.desktop";
export const desktop: typeof bridge | undefined = undefined;
export async function createWritableStream(filename: string) {
return createWriteStream(filename);
}

View File

@@ -33,7 +33,8 @@ export async function exportToPDF(
return new Promise<boolean>((resolve) => {
const iframe = document.createElement("iframe");
iframe.srcdoc = content.replaceAll(/<p(.+?)><\/p>/gm, "<p$1><br/></p>");
iframe.srcdoc = content;
iframe.style.position = "fixed";
iframe.style.right = "0";
iframe.style.bottom = "0";

View File

@@ -1,217 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
showFeatureDialog,
showLoadingDialog,
showPasswordDialog,
showReminderDialog
} from "../common/dialog-controller";
import Config from "../utils/config";
import { hashNavigate, getCurrentHash } from "../navigation";
import { db } from "./db";
import { sanitizeFilename } from "@notesnook/common";
import { store as userstore } from "../stores/user-store";
import FileSaver from "file-saver";
import { showToast } from "../utils/toast";
import { SUBSCRIPTION_STATUS } from "./constants";
import { showFilePicker } from "../utils/file-picker";
import { logger } from "../utils/logger";
import { PATHS } from "@notesnook/desktop";
import { TaskManager } from "./task-manager";
import { EVENTS } from "@notesnook/core/dist/common";
import { getFormattedDate } from "@notesnook/common";
import { desktop } from "./desktop-bridge";
export const CREATE_BUTTON_MAP = {
notes: {
title: "Add a note",
onClick: () =>
hashNavigate("/notes/create", { addNonce: true, replace: true })
},
notebooks: {
title: "Create a notebook",
onClick: () => hashNavigate("/notebooks/create", { replace: true })
},
topics: {
title: "Create a topic",
onClick: () => hashNavigate(`/topics/create`, { replace: true })
},
tags: {
title: "Create a tag",
onClick: () => hashNavigate(`/tags/create`, { replace: true })
},
reminders: {
title: "Add a reminder",
onClick: () => hashNavigate(`/reminders/create`, { replace: true })
}
};
export async function introduceFeatures() {
const hash = getCurrentHash().replace("#", "");
if (!!hash || IS_TESTING) return;
const features = [];
for (let feature of features) {
if (!Config.get(`feature:${feature}`)) {
await showFeatureDialog(feature);
}
}
}
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
export async function createBackup() {
const encryptBackups =
userstore.get().isLoggedIn && Config.get("encryptBackups", false);
const data = await showLoadingDialog({
title: "Creating backup",
subtitle: "We are creating a backup of your data. Please wait...",
action: async () => {
return await db.backup.export("web", encryptBackups);
}
});
if (!data) {
showToast("error", "Could not create a backup of your data.");
return;
}
const filename = sanitizeFilename(`notesnook-backup-${getFormattedDate()}`);
const ext = "nnbackup";
if (IS_DESKTOP_APP) {
const directory = Config.get(
"backupStorageLocation",
PATHS.backupsDirectory
);
const filePath = `${directory}/${filename}.${ext}`;
await desktop?.integration.saveFile.query({ filePath, data });
showToast("success", `Backup saved at ${filePath}.`);
} else {
FileSaver.saveAs(new Blob([Buffer.from(data)]), `${filename}.${ext}`);
}
}
export async function selectBackupFile() {
const file = await showFilePicker({
acceptedFileTypes: ".nnbackup,application/json"
});
if (!file) return;
const reader = new FileReader();
const backup = await new Promise((resolve) => {
reader.addEventListener("load", (event) => {
const text = event.target.result;
try {
resolve(JSON.parse(text));
} catch (e) {
alert(
"Error: Could not read the backup file provided. Either it's corrupted or invalid."
);
resolve();
}
});
reader.readAsText(file);
});
return { file, backup };
}
export async function importBackup() {
const { backup } = await selectBackupFile();
await restoreBackupFile(backup);
}
export async function restoreBackupFile(backup) {
if (backup.data.iv && backup.data.salt) {
await showPasswordDialog("ask_backup_password", async ({ password }) => {
const error = await restoreWithProgress(backup, password);
return !error;
});
} else {
await restoreWithProgress(backup);
}
}
async function restoreWithProgress(backup, password) {
await TaskManager.startTask({
title: "Restoring backup",
subtitle: "This might take a while",
type: "modal",
action: (report) => {
db.eventManager.subscribe(
EVENTS.migrationProgress,
({ collection, total, current }) => {
report({
text: `Restoring ${collection}...`,
current,
total
});
}
);
report({ text: `Restoring...` });
return restore(backup, password);
}
});
}
export async function verifyAccount() {
if (!(await db.user.getUser())) return true;
return showPasswordDialog("verify_account", ({ password }) => {
return db.user.verifyPassword(password);
});
}
export function totalSubscriptionConsumed(user) {
if (!user) return 0;
const start = user.subscription?.start;
const end = user.subscription?.expiry;
if (!start || !end) return 0;
const total = end - start;
const consumed = Date.now() - start;
return Math.round((consumed / total) * 100);
}
export async function showUpgradeReminderDialogs() {
if (IS_TESTING) return;
const user = userstore.get().user;
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
const consumed = totalSubscriptionConsumed(user);
const isTrial = user?.subscription?.type === SUBSCRIPTION_STATUS.TRIAL;
const isBasic = user?.subscription?.type === SUBSCRIPTION_STATUS.BASIC;
if (isBasic && consumed >= 100) {
await showReminderDialog("trialexpired");
} else if (isTrial && consumed >= 75) {
await showReminderDialog("trialexpiring");
}
}
async function restore(backup, password) {
try {
await db.backup.import(backup, password);
showToast("success", "Backup restored!");
} catch (e) {
logger.error(e, "Could not restore the backup");
showToast("error", `Could not restore the backup: ${e.message || e}`);
}
}

View File

@@ -0,0 +1,290 @@
/*
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 {
showFeatureDialog,
showPasswordDialog,
showReminderDialog
} from "./dialog-controller";
import Config from "../utils/config";
import { hashNavigate, getCurrentHash } from "../navigation";
import { db } from "./db";
import { sanitizeFilename } from "@notesnook/common";
import { store as userstore } from "../stores/user-store";
import { showToast } from "../utils/toast";
import { SUBSCRIPTION_STATUS } from "./constants";
import { readFile, showFilePicker } from "../utils/file-picker";
import { logger } from "../utils/logger";
import { PATHS } from "@notesnook/desktop";
import { TaskManager } from "./task-manager";
import { EVENTS } from "@notesnook/core/dist/common";
import { getFormattedDate } from "@notesnook/common";
import { createWritableStream } from "./desktop-bridge";
import { ZipStream } from "../utils/streams/zip-stream";
import { register } from "../utils/stream-saver/mitm";
import { FeatureKeys } from "../dialogs/feature-dialog";
import { Reader } from "../utils/zip-reader";
export const CREATE_BUTTON_MAP = {
notes: {
title: "Add a note",
onClick: () =>
hashNavigate("/notes/create", { addNonce: true, replace: true })
},
notebooks: {
title: "Create a notebook",
onClick: () => hashNavigate("/notebooks/create", { replace: true })
},
topics: {
title: "Create a topic",
onClick: () => hashNavigate(`/topics/create`, { replace: true })
},
tags: {
title: "Create a tag",
onClick: () => hashNavigate(`/tags/create`, { replace: true })
},
reminders: {
title: "Add a reminder",
onClick: () => hashNavigate(`/reminders/create`, { replace: true })
}
};
export async function introduceFeatures() {
const hash = getCurrentHash().replace("#", "");
if (!!hash || IS_TESTING) return;
const features: FeatureKeys[] = [];
for (const feature of features) {
if (!Config.get(`feature:${feature}`)) {
await showFeatureDialog(feature);
}
}
}
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
export async function createBackup() {
await register();
const encryptBackups =
userstore.get().isLoggedIn && Config.get("encryptBackups", false);
const filename = sanitizeFilename(
`notesnook-backup-${getFormattedDate(Date.now())}`
);
const directory = Config.get("backupStorageLocation", PATHS.backupsDirectory);
const ext = "nnbackupz";
const filePath = IS_DESKTOP_APP
? `${directory}/${filename}.${ext}`
: `${filename}.${ext}`;
const encoder = new TextEncoder();
const error = await TaskManager.startTask<Error | void>({
type: "modal",
title: "Creating backup",
subtitle: "We are creating a backup of your data. Please wait...",
action: async (report) => {
const writeStream = await createWritableStream(filePath);
await new ReadableStream({
start() {},
async pull(controller) {
for await (const file of db.backup!.export("web", encryptBackups)) {
report({
text: `Saving chunk ${file.path}`
});
controller.enqueue({
path: file.path,
data: encoder.encode(file.data)
});
}
controller.close();
}
})
.pipeThrough(new ZipStream())
.pipeTo(writeStream);
}
});
if (error) {
showToast(
"error",
`Could not create a backup of your data: ${(error as Error).message}`
);
console.error(error);
} else {
showToast("success", `Backup saved at ${filePath}.`);
}
}
export async function selectBackupFile() {
const file = await showFilePicker({
acceptedFileTypes: ".nnbackup,.nnbackupz"
});
if (!file) return;
return file;
}
export async function importBackup() {
const backupFile = await selectBackupFile();
if (!backupFile) return;
await restoreBackupFile(backupFile);
}
export async function restoreBackupFile(backupFile: File) {
const isLegacy = !backupFile.name.endsWith(".nnbackupz");
if (isLegacy) {
const backup = JSON.parse(await readFile(backupFile));
if (backup.data.iv && backup.data.salt) {
await showPasswordDialog("ask_backup_password", async ({ password }) => {
if (!password) return false;
const error = await restoreWithProgress(backup, password);
return !error;
});
} else {
await restoreWithProgress(backup);
}
} else {
const error = await TaskManager.startTask<Error | void>({
title: "Restoring backup",
subtitle: "Please wait while we restore your backup...",
type: "modal",
action: async (report) => {
let cachedPassword: string | undefined = undefined;
const { read, totalFiles } = await Reader(backupFile);
let filesProcessed = 0;
for await (const entry of read()) {
if (filesProcessed++ === 0 && entry.name !== ".nnbackup")
throw new Error("Invalid backup.");
else if (entry.name === ".nnbackup") continue;
const backup = JSON.parse(await entry.text());
if (backup.encrypted) {
if (!cachedPassword) {
const result = await showPasswordDialog(
"ask_backup_password",
async ({ password }) => {
if (!password) return false;
await db.backup?.import(backup, password);
cachedPassword = password;
return true;
}
);
if (!result) break;
} else await db.backup?.import(backup, cachedPassword);
} else {
await db.backup?.import(backup, null);
}
report({
total: totalFiles,
text: `Processed ${entry.name}`,
current: filesProcessed
});
}
await db.initCollections();
}
});
if (error) {
console.error(error);
showToast("error", `Failed to restore backup: ${error.message}`);
}
}
}
async function restoreWithProgress(
backup: Record<string, unknown>,
password?: string
) {
return await TaskManager.startTask<Error | void>({
title: "Restoring backup",
subtitle: "This might take a while",
type: "modal",
action: (report) => {
db.eventManager.subscribe(
EVENTS.migrationProgress,
({
collection,
total,
current
}: {
collection: string;
total: number;
current: number;
}) => {
report({
text: `Restoring ${collection}...`,
current,
total
});
}
);
report({ text: `Restoring...` });
return restore(backup, password);
}
});
}
export async function verifyAccount() {
if (!(await db.user?.getUser())) return true;
return showPasswordDialog("verify_account", ({ password }) => {
return db.user?.verifyPassword(password) || false;
});
}
export function totalSubscriptionConsumed(user: User) {
if (!user) return 0;
const start = user.subscription?.start;
const end = user.subscription?.expiry;
if (!start || !end) return 0;
const total = end - start;
const consumed = Date.now() - start;
return Math.round((consumed / total) * 100);
}
export async function showUpgradeReminderDialogs() {
if (IS_TESTING) return;
const user = userstore.get().user;
if (!user || !user.subscription || user.subscription?.expiry === 0) return;
const consumed = totalSubscriptionConsumed(user);
const isTrial = user?.subscription?.type === SUBSCRIPTION_STATUS.TRIAL;
const isBasic = user?.subscription?.type === SUBSCRIPTION_STATUS.BASIC;
if (isBasic && consumed >= 100) {
await showReminderDialog("trialexpired");
} else if (isTrial && consumed >= 75) {
await showReminderDialog("trialexpiring");
}
}
async function restore(backup: Record<string, unknown>, password?: string) {
try {
await db.backup?.import(backup, password);
showToast("success", "Backup restored!");
} catch (e) {
logger.error(e as Error, "Could not restore the backup");
showToast(
"error",
`Could not restore the backup: ${(e as Error).message || e}`
);
}
}

View File

@@ -159,10 +159,9 @@ function ListContainer(props: ListContainerProps) {
onKeyDown={(e) => onKeyDown(e.nativeEvent)}
components={{
Scroller: CustomScrollbarsVirtualList,
Item: (props) => (
Item: ({ item: _item, ...props }) => (
<div
{...props}
style={{ paddingBottom: 1 }}
onFocus={() => onFocus(props["data-item-index"])}
onMouseDown={(e) =>
onMouseDown(e.nativeEvent, props["data-item-index"])

View File

@@ -31,8 +31,8 @@ import { alpha } from "@theme-ui/color";
type ListItemProps = {
colors?: {
heading: SchemeColors;
accent: SchemeColors;
paragraph: SchemeColors;
background: SchemeColors;
};
isFocused?: boolean;
@@ -53,9 +53,9 @@ type ListItemProps = {
function ListItem(props: ListItemProps) {
const {
colors: { paragraph, background, accent } = {
colors: { heading, background, accent } = {
heading: "heading",
accent: "accent",
paragraph: "paragraph",
background: "background"
},
isFocused,
@@ -76,6 +76,7 @@ function ListItem(props: ListItemProps) {
? store.selectedItems.length > 1 && isInSelection
: isInSelection;
});
const selected = isSelected || isMenuTarget || isFocused;
return (
<Flex
@@ -112,7 +113,7 @@ function ListItem(props: ListItemProps) {
pl={1}
pr={2}
py={1}
mb={isCompact ? 0 : 1}
mb={isCompact ? 0 : 0}
tabIndex={-1}
dir="auto"
sx={{
@@ -130,24 +131,20 @@ function ListItem(props: ListItemProps) {
borderLeft: "5px solid",
borderLeftColor: isFocused ? accent : "transparent",
ml: "2px",
mr: "1px",
backgroundColor:
isSelected || isMenuTarget || isFocused
? "background-selected"
: background,
backgroundColor: selected ? "background-selected" : background,
":hover": {
backgroundColor: isSelected ? "hover-selected" : "hover"
backgroundColor: selected ? "hover-selected" : "hover"
},
":focus": {
backgroundColor: isSelected ? "hover-selected" : "hover"
backgroundColor: selected ? "hover-selected" : "hover"
},
":focus-visible": {
outline: `1px solid`,
outlineColor: accent === "accent" ? "accent" : alpha("accent", 0.7),
backgroundColor: isSelected ? "textSelection" : background
backgroundColor:
isSelected || isFocused ? "background-selected" : background
}
}}
onKeyPress={(e) => {
@@ -172,7 +169,8 @@ function ListItem(props: ListItemProps) {
overflow: "hidden",
textOverflow: "ellipsis",
fontWeight: isCompact || isSimple ? "body" : "bold",
color: paragraph,
color:
selected && heading === "heading" ? `${heading}-selected` : heading,
display: "block"
}}
>
@@ -185,6 +183,7 @@ function ListItem(props: ListItemProps) {
variant="body"
data-test-id={`description`}
sx={{
color: selected ? "paragraph-selected" : "paragraph",
lineHeight: `1.2rem`,
overflow: "hidden",
textOverflow: "ellipsis",

View File

@@ -141,7 +141,20 @@ function NavigationMenu(props: NavigationMenuProps) {
}, [location, previousLocation, state]);
return (
<ScopedThemeProvider scope="navigationMenu" injectCssVars={false}>
<ScopedThemeProvider
scope="navigationMenu"
sx={{
display: "flex",
zIndex: 1,
position: "relative",
flex: 1,
flexDirection: "column",
height: "100%",
overflow: "hidden",
bg: "background",
borderRight: "1px solid var(--separator)"
}}
>
<AnimatedFlex
id="navigation-menu"
data-test-id="navigation-menu"
@@ -154,14 +167,10 @@ function NavigationMenu(props: NavigationMenuProps) {
}}
transition={{ duration: 0.2, ease: "easeInOut" }}
sx={{
zIndex: 1,
position: "relative",
flex: 1,
height: "100%",
overflow: "hidden",
flexDirection: "column",
justifyContent: "space-between",
bg: "background"
justifyContent: "space-between"
}}
px={0}
>
@@ -218,7 +227,7 @@ function NavigationMenu(props: NavigationMenuProps) {
/>
))}
<Box
bg="border"
bg="separator"
my={1}
sx={{ width: "85%", height: "0.8px", alignSelf: "center" }}
/>
@@ -301,7 +310,7 @@ function NavigationMenu(props: NavigationMenuProps) {
<Button
variant={"icon"}
title="Toggle dark/light mode"
sx={{ borderLeft: "1px solid var(--border)" }}
sx={{ borderLeft: "1px solid var(--separator)" }}
onClick={() => {
setFollowSystemTheme(false);
toggleNightMode();

View File

@@ -131,7 +131,7 @@ function Note(props: NoteProps) {
}}
colors={{
accent: primary,
paragraph: note.color ? primary : "heading",
heading: note.color ? primary : "heading",
background: "background"
}}
menuItems={menuItems}
@@ -179,7 +179,7 @@ function Note(props: NoteProps) {
<Flex
sx={{
fontSize: "subBody",
color: "var(--paragraph-secondary)",
color: "paragraph-secondary",
alignItems: "center",
gap: 1
}}

View File

@@ -19,13 +19,50 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Button, Flex, Text } from "@theme-ui/components";
import { Context, useTip } from "../../hooks/use-tip";
import { Info } from "../icons";
import { Info, Sync } from "../icons";
import { useStore as useAppStore } from "../../stores/app-store";
import { toTitleCase } from "@notesnook/common";
type PlaceholderProps = { context: Context; text?: string };
function Placeholder(props: PlaceholderProps) {
const { context, text } = props;
const tip = useTip(context);
const syncStatus = useAppStore((store) => store.syncStatus);
if (syncStatus.key === "syncing" && context === "notes") {
return (
<Flex
variant="columnCenter"
sx={{
position: "relative",
justifyContent: "flex-start",
alignItems: "flex-start",
alignSelf: "stretch",
px: 6
}}
>
<Flex
sx={{
border: "1px solid var(--accent)",
alignItems: "center",
borderRadius: 50,
p: 1,
py: "1.5px"
}}
>
<Sync color="accent" size={12} sx={{ mr: "small" }} />
<Text variant="subBody" sx={{ fontSize: 10 }} color="accent">
Syncing your {context}
</Text>
</Flex>
<Text variant="subBody" sx={{ fontSize: "body", mt: 1 }}>
{toTitleCase(syncStatus.type || "syncing")}ing {syncStatus.progress}{" "}
items
</Text>
</Flex>
);
}
if (!tip) return null;
return (

View File

@@ -102,7 +102,7 @@ function Header(props: RouteContainerProps) {
{!isMobile && buttons?.create && (
<Plus
data-test-id={`${type}-action-button`}
color="white"
color="accentForeground"
size={18}
sx={{
bg: "accent",

View File

@@ -43,6 +43,7 @@ import {
import useStatus from "../../hooks/use-status";
import { ScopedThemeProvider } from "../theme-provider";
import { checkForUpdate, installUpdate } from "../../utils/updater";
import { toTitleCase } from "@notesnook/common";
function StatusBar() {
const user = useUserStore((state) => state.user);
@@ -56,7 +57,7 @@ function StatusBar() {
bg="background"
sx={{
borderTop: "1px solid",
borderTopColor: "border",
borderTopColor: "separator",
justifyContent: "space-between",
display: ["none", "none", "flex"]
}}
@@ -230,9 +231,7 @@ function SyncStatus() {
title={status.tooltip}
data-test-id={`sync-status-${status.key}`}
>
{syncStatus.progress ? (
<Text variant={"subBody"}>{syncStatus.progress}</Text>
) : (
{!syncStatus.progress && (
<status.icon
size={12}
rotate={status.loading}
@@ -253,6 +252,12 @@ function SyncStatus() {
</>
) : null}
</Text>
{syncStatus.progress && (
<Text variant={"subBody"} ml={1}>
{" "}
({syncStatus.progress})
</Text>
)}
</Button>
);
}
@@ -309,7 +314,7 @@ const syncStatusFilters: SyncStatusFilter[] = [
isActive: (syncStatus) => syncStatus === "syncing",
icon: Sync,
loading: true,
text: ({ type }) => <>{type || "sync"}ing</>,
text: ({ type }) => <>{toTitleCase(type || "sync")}ing</>,
tooltip: "Syncing your notes..."
},
{

View File

@@ -127,7 +127,7 @@ export function ThemePreview(props: ThemePreviewProps) {
Notes
</Text>
<Plus
color="white"
color={theme.previewColors.list.accentForeground}
size={8}
sx={{
bg: theme.previewColors.list.accent,

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useCallback, useEffect, useRef, useState } from "react";
import { Text, Flex, Button } from "@theme-ui/components";
import { Text, Flex, Button, Link } from "@theme-ui/components";
import { Cross, Check, Loading } from "../../components/icons";
import { useStore as useUserStore } from "../../stores/user-store";
import { useStore as useThemeStore } from "../../stores/theme-store";
@@ -461,7 +461,21 @@ function SelectedPlan(props: SelectedPlanProps) {
>
{metadata.title}
</Text>
{plan.period === "education" && (
<Link
href="https://notesnook.com/education"
target="_blank"
variant="text.body"
mt={1}
sx={{
textDecorationColor: "primary",
color: "primary",
textAlign: "center"
}}
>
Apply here to get your Education discount code.
</Link>
)}
{pricingInfo ? (
<>
<Text data-test-id={`checkout-plan-country-${pricingInfo.country}`} />
@@ -602,7 +616,9 @@ function CheckoutPricing(props: CheckoutPricingProps) {
{currentTotal}
</Text>
<Text as="div" sx={{ fontSize: "body", color: "paragraph" }}>
{isDiscounted
{period === "education" && discount.amount > 0
? "for one year"
: isDiscounted
? "forever"
: `first ${getFullPeriod(period)} then ${recurringTotal}`}
</Text>

View File

@@ -141,7 +141,11 @@ function RecurringPricing(props: RecurringPricingProps) {
}
export function formatPeriod(period: Period) {
return period === "monthly" ? "/mo" : period === "yearly" ? "/yr" : "";
return period === "monthly"
? "/mo"
: period === "yearly" || period === "education"
? "/yr"
: "";
}
export function getFullPeriod(period: Period) {

View File

@@ -26,6 +26,15 @@ type PlanMetadata = {
subtitle: string;
};
export const EDUCATION_PLAN: Plan = {
id: import.meta.env.DEV || IS_TESTING ? "50305" : "658759",
period: "education",
country: "US",
currency: "USD",
discount: 0,
price: { gross: 9.99, net: 0, tax: 0 }
};
export const DEFAULT_PLANS: Plan[] = [
{
period: "monthly",
@@ -42,12 +51,17 @@ export const DEFAULT_PLANS: Plan[] = [
discount: 0,
id: import.meta.env.DEV || IS_TESTING ? "50305" : "658759",
price: { gross: 49.99, net: 0, tax: 0 }
}
},
EDUCATION_PLAN
];
export const PLAN_METADATA: Record<Period, PlanMetadata> = {
monthly: { title: "Monthly", subtitle: `Pay once a month.` },
yearly: { title: "Yearly", subtitle: `Pay once a year.` }
yearly: { title: "Yearly", subtitle: `Pay once a year.` },
education: {
title: "Education",
subtitle: "Special offer for students & teachers."
}
};
let CACHED_PLANS: Plan[];
@@ -59,6 +73,7 @@ export async function getPlans(): Promise<Plan[] | null> {
const response = await fetch(url);
if (!response.ok) return null;
const plans = (await response.json()) as Plan[];
plans.push(EDUCATION_PLAN);
CACHED_PLANS = plans;
return plans;
}

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/>.
*/
export type Period = "monthly" | "yearly";
export type Period = "monthly" | "yearly" | "education";
export enum PaddleEvents {
/** Checkout has been initialized on the page **/

View File

@@ -91,19 +91,9 @@ const features: Record<FeatureKeys, Feature> = {
]
: [
{
title: "Custom themes",
title: "Much faster & more reliable sync",
subtitle:
"You can now customize how each part of Notesnook looks to your hearts desire. To access all available themes just go to Settings > Appearance."
},
{
title: "Improved copy/pasting formatting",
subtitle:
"The copy/pasting has been significantly improved and should now cause much less frustration due to bad formatting. Give it a try!"
},
{
title: "More reliable attachment uploads",
subtitle:
"No more sudden and unexpected error popups due to broken attachment uploads. We have fixed lots of bugs in this release to make upload of attachment as smooth as possible."
"Syncing should now work more reliably & also much, much faster. Like 10x faster than before."
}
],
cta: {

View File

@@ -49,7 +49,10 @@ export const BackupExportSettings: SettingsGroup[] = [
useSettingStore.getState().encryptBackups
)
useSettingStore.getState().toggleEncryptBackups();
if (await verifyAccount()) await createBackup();
const verified =
useSettingStore.getState().encryptBackups ||
(await verifyAccount());
if (verified) await createBackup();
},
variant: "secondary"
}
@@ -92,10 +95,15 @@ export const BackupExportSettings: SettingsGroup[] = [
],
selectedOption: () =>
useSettingStore.getState().backupReminderOffset.toString(),
onSelectionChanged: (value) =>
useSettingStore
.getState()
.setBackupReminderOffset(parseInt(value))
onSelectionChanged: async (value) => {
const verified =
useSettingStore.getState().encryptBackups ||
(await verifyAccount());
if (verified)
useSettingStore
.getState()
.setBackupReminderOffset(parseInt(value));
}
}
]
},
@@ -114,7 +122,12 @@ export const BackupExportSettings: SettingsGroup[] = [
isToggled: () =>
!!useUserStore.getState().isLoggedIn &&
useSettingStore.getState().encryptBackups,
toggle: () => useSettingStore.getState().toggleEncryptBackups()
toggle: async () => {
const verified =
!useSettingStore.getState().encryptBackups ||
(await verifyAccount());
if (verified) useSettingStore.getState().toggleEncryptBackups();
}
}
]
},
@@ -128,6 +141,11 @@ export const BackupExportSettings: SettingsGroup[] = [
type: "button",
title: "Select directory",
action: async () => {
const verified =
useSettingStore.getState().encryptBackups ||
(await verifyAccount());
if (!verified) return;
const backupStorageLocation =
useSettingStore.getState().backupStorageLocation ||
PATHS.backupsDirectory;
@@ -167,11 +185,11 @@ export const BackupExportSettings: SettingsGroup[] = [
selectedOption: () => "-",
onSelectionChanged: async (value) => {
if (!db.notes || value === "-") return;
await exportNotes(
value as "txt" | "md" | "html",
db.notes.all.map((n) => n.id)
);
if (await verifyAccount())
await exportNotes(
value as "txt" | "md" | "html",
db.notes.all.map((n) => n.id)
);
}
}
]

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { httpBatchLink } from "@trpc/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useCallback, useRef, useState } from "react";
import { useCallback, useState } from "react";
import { Box, Button, Flex, Input, Text } from "@theme-ui/components";
import { CheckCircleOutline, Loading } from "../../../components/icons";
import {
@@ -74,7 +74,6 @@ function ThemesList() {
);
const [isApplying, setIsApplying] = useState(false);
const parentScrollRef = useRef<HTMLElement>();
const setCurrentTheme = useThemeStore((store) => store.setTheme);
const user = useUserStore((store) => store.user);
const darkTheme = useThemeStore((store) => store.darkTheme);
@@ -198,11 +197,6 @@ function ThemesList() {
</Flex>
<Box
ref={(ref) => {
if (ref && ref instanceof HTMLElement)
parentScrollRef.current =
(ref.closest(".ms-container") as HTMLElement) || undefined;
}}
sx={{
".virtuoso-grid-list": {
display: "grid",
@@ -216,7 +210,9 @@ function ThemesList() {
<Loader title={"Loading themes..."} />
) : (
<VirtuosoGrid
customScrollParent={parentScrollRef.current || undefined}
customScrollParent={
document.getElementById("settings-scrollbar") || undefined
}
data={[
{
...darkTheme,

View File

@@ -476,6 +476,7 @@ function SettingItem(props: { item: Setting }) {
disabled={isWorking}
onChange={() => workWithLoading(component.toggle)}
checked={component.isToggled()}
data-checked={component.isToggled()}
/>
);
case "dropdown":

View File

@@ -104,10 +104,10 @@ class AppStore extends BaseStore {
db.eventManager.subscribe(
EVENTS.databaseSyncRequested,
async (full, force) => {
async (full, force, lastSynced) => {
if (!this.get().isAutoSyncEnabled) return;
await this.get().sync(full, force);
await this.get().sync(full, force, lastSynced);
}
);
@@ -273,21 +273,30 @@ class AppStore extends BaseStore {
this.set((state) => (state.lastSynced = lastSynced));
};
sync = async (full = true, force = false) => {
sync = async (full = true, force = false, lastSynced = null) => {
if (
this.isSyncing() ||
!this.get().isSyncEnabled ||
!navigator.onLine ||
!(await networkCheck.waitForInternet())
)
) {
logger.info("Ignoring duplicate sync", {
full,
force,
lastSynced,
syncing: this.isSyncing(),
syncDisabled: !this.get().isSyncEnabled,
offline: !navigator.onLine
});
return;
}
clearTimeout(syncStatusTimeout);
this.updateLastSynced();
this.updateSyncStatus("syncing");
try {
const result = await db.sync(full, force);
const result = await db.sync(full, force, lastSynced);
if (!result) return this.updateSyncStatus("failed");
this.updateSyncStatus("completed", true);

View File

@@ -17,12 +17,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { PAGE_VISIBILITY_CHANGE } from "./page-visibility";
type FilePickerOptions = { acceptedFileTypes: string };
export function showFilePicker({
acceptedFileTypes
}: FilePickerOptions): Promise<File | undefined> {
return new Promise((resolve) => {
PAGE_VISIBILITY_CHANGE.ignore = true;
const input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("accept", acceptedFileTypes);

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { debounce } from "@notesnook/common";
export const PAGE_VISIBILITY_CHANGE = { ignore: false };
function visibilityChange() {
return "msHidden" in document
? "msvisibilityChange"
@@ -58,6 +59,10 @@ export function onPageVisibilityChanged(
document.addEventListener(
visibilityChange(),
debounce((_) => {
if (PAGE_VISIBILITY_CHANGE.ignore) {
PAGE_VISIBILITY_CHANGE.ignore = false;
return;
}
handler("visibilitychange", isDocumentHidden());
}, 1000)
);
@@ -66,7 +71,11 @@ export function onPageVisibilityChanged(
"focus",
debounce((_) => {
if (!window.document.hasFocus()) return;
if (PAGE_VISIBILITY_CHANGE.ignore) {
PAGE_VISIBILITY_CHANGE.ignore = false;
return;
}
handler("focus", false);
}, 1000)
);
}
}

View File

@@ -16,11 +16,26 @@ 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/>.
*/
export {};
let sw: ServiceWorker | null = null;
let scope = "";
let keepAlive = () => {
keepAlive = () => {};
const interval = setInterval(() => {
if (sw) {
sw.postMessage("ping");
} else {
const ping =
location.href.substr(0, location.href.lastIndexOf("/")) + "/ping";
fetch(ping).then((res) => {
!res.ok && clearInterval(interval);
return res.text();
});
}
}, 10000);
};
function registerWorker() {
return navigator.serviceWorker
.getRegistration("./")
@@ -104,9 +119,12 @@ export function postMessage(
// messageChannel.port2 to the service worker. The service worker can
// then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
const transferable = [ports[0]];
if (!data.transferringReadable) {
keepAlive();
}
return sw?.postMessage(data, transferable);
}
@@ -114,4 +132,8 @@ export async function register() {
if (navigator.serviceWorker) {
await registerWorker();
}
// FF v102 just started to supports transferable streams, but still needs to ping sw.js
// even tough the service worker dose not have to do any kind of work and listen to any
// messages... #305
keepAlive();
}

View File

@@ -272,6 +272,7 @@ function LoginPassword(props: BaseAuthComponentProps<"login:password">) {
type="login:password"
title="Your account password"
subtitle={"Your password is always hashed before leaving this device."}
loadForever
loading={{
title: "Logging you in",
subtitle: "Please wait while you are authenticated."
@@ -352,14 +353,14 @@ function Signup(props: BaseAuthComponentProps<"signup">) {
<AuthField
id="password"
type="password"
autoComplete="current-password"
autoComplete="new-password"
label="Set password"
defaultValue={form?.password}
/>
<AuthField
id="confirm-password"
type="password"
autoComplete="confirm-password"
autoComplete="new-password"
label="Confirm password"
defaultValue={form?.["confirm-password"]}
/>
@@ -806,6 +807,7 @@ type AuthFormProps<TType extends AuthRoutes> = {
loading: { title: string; subtitle: string };
type: TType;
onSubmit: (form: AuthFormData[TType]) => Promise<void>;
loadForever?: boolean;
canSkip?: boolean;
children?:
| React.ReactNode
@@ -813,7 +815,7 @@ type AuthFormProps<TType extends AuthRoutes> = {
};
export function AuthForm<T extends AuthRoutes>(props: AuthFormProps<T>) {
const { title, subtitle, children, canSkip } = props;
const { title, subtitle, children, canSkip, loadForever } = props;
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string>();
const formRef = useRef<HTMLFormElement>(null);
@@ -838,7 +840,9 @@ export function AuthForm<T extends AuthRoutes>(props: AuthFormProps<T>) {
try {
setForm(form);
await props.onSubmit(form);
if (!loadForever) setIsSubmitting(false);
} catch (e) {
setIsSubmitting(false);
const error = e as Error;
if (error.message === "invalid_grant") {
setError(
@@ -847,8 +851,6 @@ export function AuthForm<T extends AuthRoutes>(props: AuthFormProps<T>) {
return;
}
setError(error.message);
} finally {
setIsSubmitting(false);
}
}}
sx={{
@@ -999,7 +1001,7 @@ export function SubmitButton(props: SubmitButtonProps) {
}}
disabled={props.disabled}
>
{props.loading ? <Loading color="white" /> : props.text}
{props.loading ? <Loading color="accentForeground" /> : props.text}
</Button>
);
}

View File

@@ -40,10 +40,7 @@ type RecoveryKeyFormData = {
};
type BackupFileFormData = {
backupFile: {
file: File;
backup: Record<string, unknown>;
};
backupFile: File;
};
type NewPasswordFormData = BackupFileFormData & {
@@ -406,7 +403,7 @@ function BackupFileMethod(props: BaseRecoveryComponentProps<"method:backup">) {
if (!backupFile) return;
const backupFileInput = document.getElementById("backupFile");
if (!(backupFileInput instanceof HTMLInputElement)) return;
backupFileInput.value = backupFile?.file?.name;
backupFileInput.value = backupFile?.name;
}, [backupFile]);
return (
@@ -519,7 +516,7 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
throw new Error("Could not reset account password.");
if (formData?.backupFile) {
await restoreBackupFile(formData?.backupFile.backup);
await restoreBackupFile(formData?.backupFile);
await db.sync(true, true);
}

View File

@@ -120,20 +120,19 @@ Currently, Notesnook has 5 variants:
Each variant further contains a total of 12 Colors:
| Color | Description | Transparent |
| --------------- | ------------------------------------------------------------------------------------------------------------------ | ----------- |
| `accent` | Color used to make something stand out (like the primary button in dialogs). Can be both background or foreground. | ❌ |
| `shade` | Color used as a background to show prominence. | |
| `background` | Background color of elements | ❌ |
| `paragraph` | Color of paragraphs and other text | ❌ |
| `heading` | Color of headings &amp; titles | ❌ |
| `backdrop` | The color of the overlay shown behind dialogs &amp; modals | ✅ |
| `textSelection` | The text color of selected text | ✅ |
| `hover` | Background color when hovering over elements (that support it) | |
| `border` | Border color | ❌ |
| `separator` | Color of the separator line between items | ❌ |
| `placeholder` | Color of the placeholder in input fields | ❌ |
| `icon` | Color of icons | ❌ |
| Color | Description | Transparent |
| ------------------ | ------------------------------------------------------------------------------------------------------------------ | ----------- |
| `accent` | Color used to make something stand out (like the primary button in dialogs). Can be both background or foreground. | ❌ |
| `accentForeground` | Color for icons & text on accent background | |
| `background` | Background color of elements | ❌ |
| `paragraph` | Color of paragraphs and other text | ❌ |
| `heading` | Color of headings &amp; titles | ❌ |
| `backdrop` | The color of the overlay shown behind dialogs &amp; modals | ✅ |
| `hover` | Background color when hovering over elements (that support it) | ✅ |
| `border` | Border color | |
| `separator` | Color of the separator line between items | ❌ |
| `placeholder` | Color of the placeholder in input fields | ❌ |
| `icon` | Color of icons | ❌ |
### Theme Metadata

View File

@@ -31,13 +31,13 @@ textarea {
::-moz-selection {
/* Code for Firefox */
background-color: var(--textSelection);
color: var(--paragraph);
background-color: var(--background-selected);
color: var(--paragraph-selected);
}
::selection {
background-color: var(--textSelection);
color: var(--paragraph);
background-color: var(--background-selected);
color: var(--paragraph-selected);
}
.ms-track:hover,

View File

@@ -0,0 +1,4 @@
- Much improved and faster sync
- Note pinned in notifications will update automatically when content changes
- Quick notes from notifications now respect paragraph spacing settings.
- Many bug fixes and small improvements

View File

@@ -0,0 +1,4 @@
- Much improved and faster sync
- Note pinned in notifications will update automatically when content changes
- Quick notes from notifications now respect paragraph spacing settings.
- Many bug fixes and small improvements

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