Compare commits

..

85 Commits

Author SHA1 Message Date
Ammar Ahmed
e4f2786e55 mobile: stop bg fetch events when toggled off 2023-12-05 08:47:17 +05:00
Ammar Ahmed
c6612a8e5f mobile: enable experimental background sync 2023-12-04 15:40:40 +05:00
Ammar Ahmed
356142bb22 mobile: fix share extension in release 2023-12-04 12:32:40 +05:00
Abdullah Atta
dad1742e80 web: fix router types 2023-12-04 11:35:42 +05:00
Abdullah Atta
c68ed0dc52 web: enable vite hot reloading 2023-12-04 11:35:29 +05:00
Abdullah Atta
978afa0899 web: fix minor build issues 2023-11-30 15:22:02 +05:00
Abdullah Atta
14d57f200b web: migrate everything to react@18 2023-11-30 15:02:37 +05:00
Abdullah Atta
2f4f351fb6 web: fix notebook tests 2023-11-30 13:04:37 +05:00
Ammar Ahmed
1344ba3821 mobile: share extension cleanup 2023-11-30 12:20:22 +05:00
Ammar Ahmed
21f9930cd0 mobile: cleanup 2023-11-30 11:21:57 +05:00
Ammar Ahmed
0789f0c075 mobile: migrate share extension 2023-11-30 11:21:57 +05:00
Ammar Ahmed
8e1d7e097c mobile: fix navigation 2023-11-30 11:21:57 +05:00
Abdullah Atta
f3a977c070 web: send noteId when decrypting real-time locked content 2023-11-28 13:56:08 +05:00
Abdullah Atta
c6fad05c56 web: fix tag tests 2023-11-28 13:56:08 +05:00
Ammar Ahmed
72a99ff751 mobile: pass tests 2023-11-27 16:33:09 +05:00
Abdullah Atta
1fcc4aa0b4 web: fix html export snapshot 2023-11-27 10:42:30 +05:00
Abdullah Atta
3a97cdeb3d core: fix md with frontmatter export 2023-11-27 10:36:16 +05:00
Ammar Ahmed
7cf6441b9c core: fix ts error 2023-11-27 10:03:24 +05:00
Ammar Ahmed
cdd3075e4f mobile: fix webpack build 2023-11-27 10:03:24 +05:00
Ammar Ahmed
162d16b2c7 mobile: push changes 2023-11-27 10:03:24 +05:00
Abdullah Atta
8e0559ca85 web: fix notes tests 2023-11-27 10:03:23 +05:00
Abdullah Atta
e92e54684e core: prevent duplicate colors based on color code 2023-11-27 10:03:23 +05:00
Abdullah Atta
9321aa1732 core: fix error on locking notes 2023-11-27 10:03:23 +05:00
Ammar Ahmed
5daeda890c mobile: push changes 2023-11-27 10:03:23 +05:00
Abdullah Atta
f6dbf253fa web: reset search results on navigation 2023-11-27 10:03:23 +05:00
Abdullah Atta
acf993f06e web: reset prepared sql stmt on error 2023-11-27 10:03:23 +05:00
Abdullah Atta
4b4a388f5f web: reuse search query 2023-11-27 10:03:23 +05:00
Abdullah Atta
3fd216bfd4 core: use porter trigram tokenizer for fts 2023-11-27 10:03:23 +05:00
Abdullah Atta
a2bbde30e6 core: properly escape fts query 2023-11-27 10:03:23 +05:00
Abdullah Atta
2e4035cd7b web: add support for assigning tags to notes 2023-11-27 10:03:23 +05:00
Abdullah Atta
e705793e0a core: fix "type" does not exist 2023-11-27 10:03:23 +05:00
Abdullah Atta
37c0107660 core: fix build error 2023-11-27 10:03:22 +05:00
Abdullah Atta
5bfcf93713 core: skip attachment if it already exists on backup restore 2023-11-27 10:03:22 +05:00
Ammar Ahmed
920ff08f16 mobile: migrate to sqlite 2023-11-27 10:03:22 +05:00
Abdullah Atta
6b85150acb web: fix note editor properties 2023-11-27 10:01:20 +05:00
Abdullah Atta
0e243fbeb4 common: type is now optional in getFormattedDate 2023-11-27 10:01:20 +05:00
Abdullah Atta
24a5953e0f core: return FilteredSelector from attachments.ofNote 2023-11-27 10:01:20 +05:00
Abdullah Atta
ec1e868510 web: show notebook & tag shortcuts when assigning notes 2023-11-27 10:01:20 +05:00
Abdullah Atta
7450585737 web: implement virtualized search for all items 2023-11-27 10:01:20 +05:00
Abdullah Atta
9a09332182 web: use lazy loading to load attachments 2023-11-27 10:01:12 +05:00
Abdullah Atta
3992aa5dc0 web: load tag & color title lazily 2023-11-27 10:01:12 +05:00
Abdullah Atta
14f9d0ac7f web: show all nested notebooks as tree when moving notes 2023-11-27 10:01:12 +05:00
Abdullah Atta
f1a615515b web: add support for nested notebooks 2023-11-27 10:01:11 +05:00
Abdullah Atta
d4ff1dd46c web: use new virtualized grouping for reminders 2023-11-27 10:01:11 +05:00
Abdullah Atta
6276bd5db3 web: use new virtualized grouping for trash 2023-11-27 10:01:11 +05:00
Abdullah Atta
34736307c3 web: use new virtualized grouping for monographs 2023-11-27 10:01:11 +05:00
Abdullah Atta
60056f3fab core: add support for fetching trash as grouped 2023-11-27 10:01:11 +05:00
Abdullah Atta
f99a00789b web: install missing @hazae41/foras 2023-11-27 10:01:11 +05:00
Abdullah Atta
fcfda03fb2 core: fix attachment extraction for locked notes 2023-11-27 10:01:11 +05:00
Abdullah Atta
cd4c2934e0 global: update lockfiles 2023-11-27 10:01:10 +05:00
Abdullah Atta
396508d4b9 web: start initial migration to sqlite 2023-11-27 10:01:10 +05:00
Abdullah Atta
ffc03460b9 ui: add support for lazily loading menu items 2023-11-27 10:01:10 +05:00
Abdullah Atta
4315503893 common: remove total-notes utility 2023-11-27 10:01:10 +05:00
Abdullah Atta
1c9e42fbfc core: changes according to client needs 2023-11-27 10:01:10 +05:00
Abdullah Atta
f8f992d11e core: fix database migration 2023-11-27 10:01:10 +05:00
Abdullah Atta
8aa36f632a core: add support for incremental grouping 2023-11-27 10:01:10 +05:00
Abdullah Atta
ac89e3851a core: fix size of nanoid in benchmarks 2023-11-27 10:01:10 +05:00
Abdullah Atta
430ad22263 core: optimize bson objectid generation & add benchmarks 2023-11-27 10:01:09 +05:00
Abdullah Atta
c46ba25408 core: fix monographs 2023-11-27 10:01:09 +05:00
Abdullah Atta
ddcddbc8a8 core: fix sync tests 2023-11-27 10:01:09 +05:00
Abdullah Atta
24b1ada636 core: remove temporary sqlite db 2023-11-27 10:01:09 +05:00
Abdullah Atta
98a3b12bb8 core: migrate everything to sqlite 2023-11-27 10:01:09 +05:00
Abdullah Atta
26dca8b1d8 core: migrate all database writes to sqlite 2023-11-27 10:00:24 +05:00
Abdullah Atta
7dc9529981 global: update lockfiles 2023-11-27 10:00:05 +05:00
Abdullah Atta
32e36325b3 core: remove outbox 2023-11-27 10:00:05 +05:00
Abdullah Atta
7b554bf781 core: convert everything else to typescript 2023-11-27 10:00:05 +05:00
Abdullah Atta
4bcc08d046 core: remove unnecessary sync tests 2023-11-27 09:57:25 +05:00
Abdullah Atta
77bb9726ab core: migrate monographs to typescript 2023-11-27 09:57:25 +05:00
Abdullah Atta
94f3dc0c52 core: make migration tests less flaky 2023-11-27 09:57:25 +05:00
Abdullah Atta
be19ff1171 core: add tests for migrations 2023-11-27 09:57:25 +05:00
Abdullah Atta
6d52f9a6a3 core: migrate settings to its own collection 2023-11-27 09:57:25 +05:00
Abdullah Atta
b3c6bedc13 core: get rid of topics; move to sub notebooks 2023-11-27 09:57:25 +05:00
Abdullah Atta
94bc61f636 core: migrate attachments to use relations for linking to notes 2023-11-27 09:57:25 +05:00
Abdullah Atta
68406a2a3a core: minor updates and fixes 2023-11-27 09:57:25 +05:00
Abdullah Atta
8a0bf20e11 core: fix types & syncing issues 2023-11-27 09:57:25 +05:00
ammarahm-ed
1055b6f13a mobile: pass all tests 2023-11-27 09:55:56 +05:00
ammarahm-ed
4994ad77b4 mobile: fix filesystem not loaded 2023-11-27 09:55:56 +05:00
ammarahm-ed
e0712ce2f3 mobile: fix navigating to tags 2023-11-27 09:55:56 +05:00
ammarahm-ed
559801ba80 mobile: migrate to typescript 2023-11-27 09:55:56 +05:00
ammarahm-ed
b69dd3b217 common: fix ts errors 2023-11-27 09:50:01 +05:00
Abdullah Atta
8328575806 mobile: use new types from core 2023-11-27 09:50:01 +05:00
Abdullah Atta
30de9cabe0 web: use new types from core 2023-11-27 09:50:00 +05:00
Abdullah Atta
506cb2dd47 core: fix more typings 2023-11-27 09:49:31 +05:00
Abdullah Atta
2f5a3db1c1 core: fix typing errors 2023-11-27 09:49:11 +05:00
Abdullah Atta
06f4fee088 core: convert almost everything to typescript 2023-11-27 09:49:11 +05:00
248 changed files with 4236 additions and 27873 deletions

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.6.13",
"version": "2.6.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.6.13",
"version": "2.6.12",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -24,8 +24,8 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "25.9.8",
"electron-builder": "^24.9.1",
"electron": "25.9.3",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"tree-kill": "^1.2.2",
@@ -173,11 +173,12 @@
}
},
"node_modules/@electron/asar": {
"version": "3.2.8",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.8.tgz",
"integrity": "sha512-cmskk5M06ewHMZAplSiF4AlME3IrnnZhKnWbtwKVLRkdJkKyUVjMLhDIiPIx/+6zQWVlKX/LtmK9xDme7540Sg==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.4.tgz",
"integrity": "sha512-lykfY3TJRRWFeTxccEKdf1I6BLl2Plw81H0bbp4Fc5iEc67foDCa5pjJQULVgo0wF+Dli75f3xVcdb/67FFZ/g==",
"dev": true,
"dependencies": {
"chromium-pickle-js": "^0.2.0",
"commander": "^5.0.0",
"glob": "^7.1.6",
"minimatch": "^3.0.4"
@@ -868,9 +869,9 @@
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
"integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==",
"dev": true,
"dependencies": {
"@types/ms": "*"
@@ -901,9 +902,9 @@
}
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
"version": "0.7.31",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==",
"dev": true
},
"node_modules/@types/node": {
@@ -969,9 +970,9 @@
}
},
"node_modules/7zip-bin": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
"integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz",
"integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==",
"dev": true
},
"node_modules/agent-base": {
@@ -1053,9 +1054,9 @@
"dev": true
},
"node_modules/app-builder-lib": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.9.1.tgz",
"integrity": "sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==",
"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",
@@ -1064,15 +1065,15 @@
"@electron/universal": "1.4.1",
"@malept/flatpak-bundler": "^0.4.0",
"@types/fs-extra": "9.0.13",
"7zip-bin": "~5.2.0",
"7zip-bin": "~5.1.1",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"builder-util": "24.5.0",
"builder-util-runtime": "9.2.1",
"chromium-pickle-js": "^0.2.0",
"debug": "^4.3.4",
"ejs": "^3.1.8",
"electron-publish": "24.8.1",
"electron-publish": "24.5.0",
"form-data": "^4.0.0",
"fs-extra": "^10.1.0",
"hosted-git-info": "^4.1.0",
@@ -1091,19 +1092,6 @@
"node": ">=14.0.0"
}
},
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -1128,9 +1116,9 @@
}
},
"node_modules/async": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
"node_modules/async-exit-hook": {
@@ -1292,16 +1280,16 @@
"dev": true
},
"node_modules/builder-util": {
"version": "24.8.1",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.8.1.tgz",
"integrity": "sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==",
"version": "24.5.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.5.0.tgz",
"integrity": "sha512-STnBmZN/M5vGcv01u/K8l+H+kplTaq4PAIn3yeuufUKSpcdro0DhJWxPI81k5XcNfC//bjM3+n9nr8F9uV4uAQ==",
"dev": true,
"dependencies": {
"@types/debug": "^4.1.6",
"7zip-bin": "~5.2.0",
"7zip-bin": "~5.1.1",
"app-builder-bin": "4.0.0",
"bluebird-lst": "^1.0.9",
"builder-util-runtime": "9.2.3",
"builder-util-runtime": "9.2.1",
"chalk": "^4.1.2",
"cross-spawn": "^7.0.3",
"debug": "^4.3.4",
@@ -1327,19 +1315,6 @@
"node": ">=12.0.0"
}
},
"node_modules/builder-util/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -1438,9 +1413,9 @@
"dev": true
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
"dev": true,
"funding": [
{
@@ -1727,14 +1702,14 @@
}
},
"node_modules/dmg-builder": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.9.1.tgz",
"integrity": "sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==",
"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.9.1",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"app-builder-lib": "24.6.4",
"builder-util": "24.5.0",
"builder-util-runtime": "9.2.1",
"fs-extra": "^10.1.0",
"iconv-lite": "^0.6.2",
"js-yaml": "^4.1.0"
@@ -1743,19 +1718,6 @@
"dmg-license": "^1.0.11"
}
},
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/dmg-license": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
@@ -1812,9 +1774,9 @@
}
},
"node_modules/electron": {
"version": "25.9.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
"integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
"version": "25.9.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.3.tgz",
"integrity": "sha512-dacaHg/PuwVcFRgPDCM5j7UDzqGJWOsbBRdS5wPKLNS/ejPeccIjuNUT1cqcrpvCJKAFW8swHWg9kdizNSEDHQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -1830,16 +1792,16 @@
}
},
"node_modules/electron-builder": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.9.1.tgz",
"integrity": "sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==",
"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.9.1",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.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.9.1",
"dmg-builder": "24.6.4",
"fs-extra": "^10.1.0",
"is-ci": "^3.0.0",
"lazy-val": "^1.0.5",
@@ -1901,19 +1863,6 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/electron-builder/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-notarize": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.2.2.tgz",
@@ -1944,33 +1893,20 @@
}
},
"node_modules/electron-publish": {
"version": "24.8.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.8.1.tgz",
"integrity": "sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==",
"version": "24.5.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.5.0.tgz",
"integrity": "sha512-zwo70suH15L15B4ZWNDoEg27HIYoPsGJUF7xevLJLSI7JUPC8l2yLBdLGwqueJ5XkDL7ucYyRZzxJVR8ElV9BA==",
"dev": true,
"dependencies": {
"@types/fs-extra": "^9.0.11",
"builder-util": "24.8.1",
"builder-util-runtime": "9.2.3",
"builder-util": "24.5.0",
"builder-util-runtime": "9.2.1",
"chalk": "^4.1.2",
"fs-extra": "^10.1.0",
"lazy-val": "^1.0.5",
"mime": "^2.5.2"
}
},
"node_modules/electron-publish/node_modules/builder-util-runtime": {
"version": "9.2.3",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/electron-trpc": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.13",
"version": "2.6.12",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -25,8 +25,8 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "25.9.8",
"electron-builder": "^24.9.1",
"electron": "25.9.3",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"tree-kill": "^1.2.2",

View File

@@ -31,16 +31,16 @@ import { RNSqliteDriver } from "./sqlite.kysely";
database.host(
__DEV__
? {
// API_HOST: "https://api.notesnook.com",
// AUTH_HOST: "https://auth.streetwriters.co",
// SSE_HOST: "https://events.streetwriters.co",
// SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
// ISSUES_HOST: "https://issues.streetwriters.co"
API_HOST: "http://192.168.43.5:5264",
AUTH_HOST: "http://192.168.43.5:8264",
SSE_HOST: "http://192.168.43.5:7264",
SUBSCRIPTIONS_HOST: "http://192.168.43.5:9264",
ISSUES_HOST: "http://192.168.43.5:2624"
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.43.108:5264",
// AUTH_HOST: "http://192.168.43.108:8264",
// SSE_HOST: "http://192.168.43.108:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.108:9264",
// ISSUES_HOST: "http://192.168.43.108:2624"
}
: {
API_HOST: "https://api.notesnook.com",
@@ -59,17 +59,20 @@ database.setup({
compress: Gzip.deflate,
decompress: Gzip.inflate
},
batchSize: 100,
batchSize: 500,
sqliteOptions: {
dialect: (name) => ({
createDriver: () => {
return new RNSqliteDriver({ async: true, dbName: name });
},
dialect: {
createDriver: () =>
new RNSqliteDriver({ async: true, dbName: "test.db" }),
createAdapter: () => new SqliteAdapter(),
createIntrospector: (db) => new SqliteIntrospector(db),
createQueryCompiler: () => new SqliteQueryCompiler()
}),
tempStore: "memory"
}
// journalMode: "MEMORY",
// synchronous: "normal",
// pageSize: 8192,
// cacheSize: -16000,
// lockingMode: "exclusive"
}
});

View File

@@ -45,15 +45,15 @@ export class RNSqliteDriver implements Driver {
}
async beginTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("BEGIN TRANSACTION"));
await connection.executeQuery(CompiledQuery.raw("begin"));
}
async commitTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("COMMIT"));
await connection.executeQuery(CompiledQuery.raw("commit"));
}
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("ROLLBACK"));
await connection.executeQuery(CompiledQuery.raw("rollback"));
}
async releaseConnection(): Promise<void> {

View File

@@ -18,22 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { formatBytes } from "@notesnook/common";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import React, { useEffect, useState } from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useDBItem } from "../../hooks/use-db-item";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from "../ui/svg/lazy";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { useDBItem } from "../../hooks/use-db-item";
function getFileExtension(filename: string) {
const ext = /^.+\.([^.]+)$/.exec(filename);
var ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
}
@@ -46,7 +46,7 @@ export const AttachmentItem = ({
hideWhenNotDownloading,
context
}: {
id: string | number;
id: string;
attachments?: VirtualizedGrouping<Attachment>;
encryption?: boolean;
setAttachments: (attachments: any) => void;
@@ -54,7 +54,7 @@ export const AttachmentItem = ({
hideWhenNotDownloading?: boolean;
context?: string;
}) => {
const [attachment] = useDBItem(id, "attachment", attachments);
const [attachment] = useDBItem(id, "attachment", attachments?.item);
const { colors } = useThemeColors();
const [currentProgress, setCurrentProgress] = useAttachmentProgress(
@@ -68,7 +68,7 @@ export const AttachmentItem = ({
};
return (hideWhenNotDownloading &&
(!currentProgress || !currentProgress.value)) ||
(!currentProgress || !(currentProgress as any).value)) ||
!attachment ? null : (
<TouchableOpacity
activeOpacity={0.9}
@@ -133,8 +133,8 @@ export const AttachmentItem = ({
{!hideWhenNotDownloading ? (
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
{formatBytes(attachment.size)}{" "}
{currentProgress?.type
? "(" + currentProgress.type + "ing - tap to cancel)"
{(currentProgress as any)?.type
? "(" + (currentProgress as any).type + "ing - tap to cancel)"
: ""}
</Paragraph>
) : null}

View File

@@ -91,7 +91,7 @@ const DownloadAttachments = ({
const successResults = () => {
const results = [];
for (const [key, value] of result.entries()) {
for (let [key, value] of result.entries()) {
if (value.status === 1) results.push(db.attachments.attachment(key));
}
return results;
@@ -99,7 +99,7 @@ const DownloadAttachments = ({
const failedResults = () => {
const results = [];
for (const [key, value] of result.entries()) {
for (let [key, value] of result.entries()) {
if (value.status === 0) results.push(db.attachments.attachment(key));
}
return results;
@@ -207,11 +207,11 @@ const DownloadAttachments = ({
</Paragraph>
</View>
}
keyExtractor={(item, index) => "attachment" + index}
renderItem={({ index }) => {
keyExtractor={(item) => item as string}
renderItem={({ item }) => {
return (
<AttachmentItem
id={index}
id={item as string}
setAttachments={() => {}}
pressable={false}
hideWhenNotDownloading={true}

View File

@@ -83,14 +83,14 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
}
clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(async () => {
const results = await db.lookup
.attachments(attachmentSearchValue.current as string)
.sorted();
let results = await db.lookup.attachments(
attachmentSearchValue.current as string
);
setAttachments(results);
}, 300);
};
const renderItem = ({ item }: { item: string | number }) => (
const renderItem = ({ item }: { item: string }) => (
<AttachmentItem
setAttachments={setAttachments}
attachments={attachments}
@@ -102,15 +102,15 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
const onCheck = async () => {
if (!attachments) return;
setLoading(true);
for (const id of attachments.ids) {
const attachment = (await attachments.item(id))?.item;
for (let id of attachments.ids) {
const attachment = await attachments.item(id as string);
if (!attachment) continue;
const result = await filesystem.checkAttachment(attachment.hash);
let result = await filesystem.checkAttachment(attachment.hash);
if (result.failed) {
await db.attachments.markAsFailed(attachment.hash, result.failed);
} else {
await db.attachments.markAsFailed(attachment.id, undefined);
await db.attachments.markAsFailed(id as string, undefined);
}
}
refresh();
@@ -306,7 +306,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
/>
}
estimatedItemSize={50}
data={attachments?.ids}
data={attachments?.ids as string[]}
renderItem={renderItem}
/>

View File

@@ -17,16 +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 { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { Platform, View } from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useUserStore } from "../../stores/use-user-store";
import { useThemeColors } from "@notesnook/theme";
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
import { sleep } from "../../utils/time";
import BaseDialog from "../dialog/base-dialog";
@@ -36,6 +34,7 @@ import { IconButton } from "../ui/icon-button";
import { hideAuth, initialAuthMode } from "./common";
import { Login } from "./login";
import { Signup } from "./signup";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
export const AuthMode = {
login: 0,
@@ -69,9 +68,6 @@ const AuthModal = () => {
}
const close = () => {
useUserStore.setState({
disableAppLockRequests: false
});
actionSheetRef.current?.hide();
setCurrentAuthMode(AuthMode.login);
setVisible(false);
@@ -81,11 +77,6 @@ const AuthModal = () => {
<BaseDialog
overlayOpacity={0}
statusBarTranslucent={false}
onShow={() => {
useUserStore.setState({
disableAppLockRequests: true
});
}}
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
visible={true}
onClose={close}
@@ -95,7 +86,6 @@ const AuthModal = () => {
transparent={false}
animated={false}
centered={false}
enableSheetKeyboardHandler
>
<KeyboardAwareScrollView
style={{

View File

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

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { useNoteStore } from "../../stores/use-notes-store";
import { AnnouncementDialog } from "../announcements";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
@@ -39,6 +40,7 @@ import RestoreDataSheet from "../sheets/restore-data";
const DialogProvider = () => {
const { colors } = useThemeColors();
const loading = useNoteStore((state) => state.loading);
return (
<>
@@ -55,7 +57,7 @@ const DialogProvider = () => {
<VaultDialog colors={colors} />
<RateAppSheet />
<ImagePreview />
<Expiring />
{loading ? null : <Expiring />}
<AnnouncementDialog />
<SessionExpired />
<PDFPreview />

View File

@@ -86,7 +86,6 @@ const BaseDialog = ({
}, []);
useEffect(() => {
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.get().appLockMode === "background") {
if (appState === "background") {
setIntervalVisible(false);

View File

@@ -18,36 +18,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View, ViewProps } from "react-native";
import { View } from "react-native";
import { DDS } from "../../services/device-detection";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../utils/elevation";
const DialogContainer = ({
width,
height,
style,
...restProps
}: ViewProps & {
width?: any;
height?: any;
}) => {
const DialogContainer = ({ width, height, ...restProps }) => {
const { colors } = useThemeColors();
return (
<View
{...restProps}
style={[
{
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
},
style
]}
style={{
...getElevationStyle(5),
width: width || DDS.isTab ? 500 : "85%",
maxHeight: height || 450,
borderRadius: 10,
backgroundColor: colors.primary.background,
paddingTop: 12
}}
/>
);
};

View File

@@ -1,302 +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 { isThemeColor, useThemeColors } from "@notesnook/theme";
import React, { useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { FlashList } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useSettingStore } from "../../../stores/use-setting-store";
import { SIZE } from "../../../utils/size";
import BaseDialog from "../../dialog/base-dialog";
import DialogContainer from "../../dialog/dialog-container";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable";
import { ToastManager } from "../../../services/event-manager";
import { Toast } from "../../toast";
import { db } from "../../../common/database";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useMenuStore } from "../../../stores/use-menu-store";
const arrayOfColors = [
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7",
"#FF5733",
"#33FF57",
"#339DFF",
"#FF33E9",
"#E9FF33",
"#FF3395",
"#95FF33",
"#FF3369",
"#6933FF",
"#33FFC7"
];
const HEX_COLOR_REGEX_ALPHA =
/^#(?:(?:[\da-fA-F]{3}){1,2}|(?:[\da-fA-F]{4}){1,2})$/;
const convertToColorObjects = (colors: string[]) => {
const colorObjects = [];
for (let i = 0; i < colors.length; i += 3) {
colorObjects.push({
colorOne: colors[i],
colorTwo: colors[i + 1],
colorThree: colors[i + 2]
});
}
return colorObjects;
};
const ColorPicker = ({
visible,
setVisible
}: {
visible: boolean;
setVisible: (value: boolean) => void;
}) => {
const [selectedColor, setSelectedColor] = useState<string>();
const { colors } = useThemeColors();
const inputRef = useRef<TextInput>(null);
const title = useRef<string>();
const renderItem = ({
item
}: {
item: { colorOne: string; colorTwo: string; colorThree: string };
}) => (
<View>
{Object.keys(item).map((key) => (
<PressableButton
key={item[key as keyof typeof item]}
type="accent"
accentColor={item[key as keyof typeof item]}
customStyle={{
width: 40,
height: 40,
borderRadius: 100,
justifyContent: "center",
alignItems: "center",
marginRight: 10,
marginBottom: 10
}}
onPress={() => {
const color = item[key as keyof typeof item];
setSelectedColor(color);
inputRef.current?.setNativeProps({
placeholder: color,
text: color
});
}}
>
{selectedColor === item[key as keyof typeof item] ? (
<Icon name="check" color="white" size={SIZE.lg} />
) : null}
</PressableButton>
))}
</View>
);
return (
<BaseDialog
visible={visible}
onRequestClose={() => {
setVisible(false);
useSettingStore.getState().setSheetKeyboardHandler(true);
}}
statusBarTranslucent={false}
centered
>
<Toast context="color-picker" />
<DialogContainer
style={{
paddingTop: 0
}}
>
<View
style={{
padding: 20
}}
>
<FlashList
extraData={selectedColor}
horizontal
data={convertToColorObjects(arrayOfColors)}
renderItem={renderItem}
/>
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingTop: 10,
columnGap: 10,
marginBottom: 10
}}
>
<Input
fwdRef={inputRef}
placeholder="#f0f0f0"
marginBottom={0}
onChangeText={(value) => {
if (HEX_COLOR_REGEX_ALPHA.test(value)) {
setSelectedColor(value);
inputRef.current?.setNativeProps({
placeholder: value,
text: value
});
}
}}
/>
<PressableButton
type="accent"
accentColor={selectedColor || colors.secondary.background}
customStyle={{
width: 45,
height: 45,
borderRadius: 100,
justifyContent: "center",
alignItems: "center"
}}
/>
</View>
<Input
marginBottom={10}
onChangeText={(value) => {
title.current = value;
}}
placeholder={title.current || "Color title"}
/>
<Button
title="Add color"
onPress={async () => {
if (!selectedColor)
return ToastManager.error(
new Error("Select a color"),
undefined,
"color-picker"
);
if (!title.current)
return ToastManager.error(
new Error("Enter a title for the color")
);
const exists = await db.colors.all.find((v) =>
v.and([v(`colorCode`, "==", selectedColor)])
);
if (exists)
return ToastManager.error(
new Error(`Color #${selectedColor} already exists`)
);
await db.colors.add({
title: title.current,
colorCode: selectedColor
});
useRelationStore.getState().update();
useMenuStore.getState().setColorNotes();
setVisible(false);
}}
type={selectedColor ? "grayAccent" : "grayBg"}
width="100%"
/>
</View>
</DialogContainer>
</BaseDialog>
);
};
export default ColorPicker;

View File

@@ -26,7 +26,7 @@ import React, {
useRef,
useState
} from "react";
import { ActivityIndicator, FlatList, ScrollView, View } from "react-native";
import { FlatList, ScrollView, View } from "react-native";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
@@ -51,20 +51,19 @@ const JumpToSectionDialog = () => {
const notes = data;
const [visible, setVisible] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
const currentScrollPosition = useRef(0);
const [loading, setLoading] = useState(false);
const [groups, setGroups] = useState<
{
index: number;
group: GroupHeader;
}[]
>();
const offsets = useRef<number[]>([]);
const timeout = useRef<NodeJS.Timeout>();
const onPress = (item: { index: number; group: GroupHeader }) => {
const onPress = (item: GroupHeader) => {
const index = notes?.ids?.findIndex((i) => {
if (typeof i === "object") {
return i.title === item.title && i.type === "header";
} else {
false;
}
});
scrollRef.current?.current?.scrollToIndex({
index: item.index,
index: index as number,
animated: true
});
close();
@@ -78,7 +77,6 @@ const JumpToSectionDialog = () => {
data: VirtualizedGrouping<Item>;
ref: RefObject<FlatList>;
}) => {
setLoading(true);
setData(data);
scrollRef.current = ref;
setVisible(true);
@@ -99,45 +97,55 @@ const JumpToSectionDialog = () => {
}, [open]);
const onScroll = (data: { x: number; y: number }) => {
currentScrollPosition.current = data.y;
const y = data.y;
if (timeout) {
clearTimeout(timeout.current);
timeout.current = undefined;
}
timeout.current = setTimeout(() => {
setCurrentIndex(
offsets.current?.findIndex(
(o, i) => o <= y && offsets.current[i + 1] > y
) || 0
);
}, 200);
};
const close = () => {
setVisible(false);
};
const loadGroupsAndOffsets = useCallback(() => {
notes?.groups?.().then((groups) => {
setGroups(groups);
offsets.current = [];
groups.map((item, index) => {
const loadOffsets = useCallback(() => {
notes?.ids
.filter((i) => typeof i === "object" && i.type === "header")
.map((item, index) => {
if (typeof item === "string") return;
let offset = 35 * index;
let groupIndex = item.index;
let ind = notes.ids.findIndex(
(i) =>
typeof i === "object" &&
i.title === item.title &&
i.type === "header"
);
const messageState = useMessageStore.getState().message;
const msgOffset = messageState?.visible ? 60 : 10;
groupIndex = groupIndex + 1;
groupIndex = groupIndex - (index + 1);
offset = offset + groupIndex * 100 + msgOffset;
ind = ind + 1;
ind = ind - (index + 1);
offset = offset + ind * 100 + msgOffset;
offsets.current.push(offset);
});
const index = offsets.current?.findIndex((o, i) => {
return (
o <= currentScrollPosition.current + 100 &&
offsets.current[i + 1] - 100 > currentScrollPosition.current
);
});
setCurrentIndex(index < 0 ? 0 : index);
setLoading(false);
});
}, [notes]);
useEffect(() => {
loadOffsets();
}, [loadOffsets, notes]);
return !visible ? null : (
<BaseDialog
onShow={() => {
loadGroupsAndOffsets();
loadOffsets();
}}
onRequestClose={close}
visible={true}
@@ -156,42 +164,35 @@ const JumpToSectionDialog = () => {
paddingTop: 30
}}
>
{loading ? (
<ActivityIndicator
size={SIZE.lg}
color={colors.primary.accent}
<ScrollView
style={{
maxHeight: "100%"
}}
>
<View
style={{
marginBottom: 20
}}
/>
) : (
<ScrollView
style={{
maxHeight: "100%"
flexDirection: "row",
flexWrap: "wrap",
alignSelf: "center",
justifyContent: "center",
paddingBottom: 20
}}
>
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
alignSelf: "center",
justifyContent: "center",
paddingBottom: 20
}}
>
{groups?.map((item, index) => {
return (
{notes?.ids
.filter((i) => typeof i === "object" && i.type === "header")
.map((item, index) => {
return typeof item === "object" && item.title ? (
<PressableButton
key={item.group.id}
key={item.title}
onPress={() => onPress(item)}
type={currentIndex === index ? "accent" : "transparent"}
type={currentIndex === index ? "selected" : "transparent"}
customStyle={{
minWidth: "20%",
width: null,
paddingHorizontal: 12,
margin: 5,
borderRadius: 100,
height: 30,
height: 25,
marginVertical: 10
}}
>
@@ -199,21 +200,20 @@ const JumpToSectionDialog = () => {
size={SIZE.sm}
color={
currentIndex === index
? colors.static.white
: colors.primary.paragraph
? colors.selected.accent
: colors.primary.accent
}
style={{
textAlign: "center"
}}
>
{item.group.title}
{item.title}
</Paragraph>
</PressableButton>
);
) : null;
})}
</View>
</ScrollView>
)}
</View>
</ScrollView>
</View>
</BaseDialog>
);

View File

@@ -49,8 +49,6 @@ export const Title = ({
const isTag = title.startsWith("#");
const onScroll = useCallback(
(data: { x: number; y: number; id?: string; route: string }) => {
if (data.route !== "Notebook") return;
if (data.route !== renderedInRoute || data.id !== id) return;
if (data.y > 150) {
if (!visible) return;

View File

@@ -37,7 +37,10 @@ import { useEditorStore } from "../../../stores/use-editor-store";
import useNavigationStore from "../../../stores/use-navigation-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { SIZE } from "../../../utils/size";
import {
NotebooksWithDateEdited,
TagsWithDateEdited
} from "../../list/list-item.wrapper";
import { Properties } from "../../properties";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
@@ -45,10 +48,6 @@ import { ReminderTime } from "../../ui/reminder-time";
import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import {
NotebooksWithDateEdited,
TagsWithDateEdited
} from "../../../stores/resolve-items";
type NoteItemProps = {
item: Note | BaseTrashItem<Note>;
@@ -72,17 +71,18 @@ const NoteItem = ({
reminder,
tags,
attachmentsCount,
noOpen = false
noOpen
}: NoteItemProps) => {
const isEditingNote = useEditorStore(
(state) => state.currentEditingNote === item.id
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(
(item as TrashItem).itemType || item.type
);
const _update = useRelationStore((state) => state.updater);
const primaryColors = isEditingNote ? colors.selected : colors.primary;
const primaryColors =
currentEditingNote === item.id ? colors.selected : colors.primary;
return (
<>
@@ -156,10 +156,10 @@ const NoteItem = ({
<Paragraph
numberOfLines={1}
color={color?.colorCode || primaryColors.heading}
size={SIZE.sm}
style={{
paddingRight: 10
flexWrap: "wrap"
}}
size={SIZE.sm}
>
{item.title}
</Paragraph>
@@ -167,10 +167,10 @@ const NoteItem = ({
<Heading
numberOfLines={1}
color={color?.colorCode || primaryColors.heading}
size={SIZE.md}
style={{
paddingRight: 10
flexWrap: "wrap"
}}
size={SIZE.md}
>
{item.title}
</Heading>

View File

@@ -33,13 +33,12 @@ import { useEditorStore } from "../../../stores/use-editor-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
import {
import type {
NotebooksWithDateEdited,
TagsWithDateEdited
} from "../../../stores/resolve-items";
} from "../../list/list-item.wrapper";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
export const openNote = async (
item: Note,
@@ -52,20 +51,15 @@ export const openNote = async (
if (!isTrash) {
note = (await db.notes.note(item.id)) as Note;
}
if (useSelectionStore.getState().selectionMode === item.type) {
const {
selectedItemsList,
selectionMode,
clearSelection,
setSelectedItem
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
} else {
clearSelection();
}
const { selectedItemsList, selectionMode, clearSelection, setSelectedItem } =
useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
return;
} else {
clearSelection();
}
if (note.conflicted) {
@@ -94,9 +88,7 @@ export const openNote = async (
)
});
} else {
if (note?.readonly) {
useEditorStore.getState().setReadonly(note?.readonly);
}
useEditorStore.getState().setReadonly(note?.readonly);
eSendEvent(eOnLoadNote, {
item: note
});
@@ -164,7 +156,6 @@ export const NoteWrapper = React.memo<
if (prev.item?.dateModified !== next.item?.dateModified) {
return false;
}
if (prev.item?.id !== next.item?.id) return false;
return true;
}

View File

@@ -57,7 +57,7 @@ export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
},
onClose: async () => {
await db.trash.delete(item.id);
useTrashStore.getState().refresh();
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(undefined);
ToastManager.show({
heading: "Permanently deleted items",
@@ -103,7 +103,6 @@ export const NotebookWrapper = React.memo(
if (prev.totalNotes !== next.totalNotes) return false;
if (prev.date !== next.date) return false;
if (prev.item?.dateModified !== next.item?.dateModified) return false;
if (prev.item?.id !== next.item?.id) return false;
return true;
}

View File

@@ -26,13 +26,13 @@ import { Item } from "@notesnook/core";
export const Filler = ({ item, color }: { item: Item; color?: string }) => {
const { colors } = useThemeColors();
const isEditingNote = useEditorStore(
(state) => state.currentEditingNote === item.id
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const [selected] = useIsSelected(item);
return isEditingNote || selected ? (
return currentEditingNote === item.id || selected ? (
<View
style={{
position: "absolute",
@@ -40,11 +40,12 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
height: "150%",
backgroundColor: colors.selected.background,
borderLeftWidth: 5,
borderLeftColor: isEditingNote
? item.color
? colors.static[item.color]
: colors.selected.accent
: "transparent"
borderLeftColor:
currentEditingNote === item.id
? color
? color
: colors.selected.accent
: "transparent"
}}
collapsable={false}
/>

View File

@@ -17,7 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { GroupingKey, Item, VirtualizedGrouping } from "@notesnook/core";
import {
GroupHeader,
GroupingKey,
Item,
VirtualizedGrouping,
isGroupHeader
} from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import { FlashList } from "@shopify/flash-list";
import React, { useEffect, useRef } from "react";
@@ -33,10 +39,11 @@ import { eSendEvent } from "../../services/event-manager";
import Sync from "../../services/sync";
import { RouteName } from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { eScrollEvent } from "../../utils/events";
import { eOpenJumpToDialog, eScrollEvent } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
import { Footer } from "../list-items/footer";
import { Header } from "../list-items/headers/header";
import { SectionHeader } from "../list-items/headers/section-header";
import { Empty, PlaceholderData } from "./empty";
import { ListItemWrapper } from "./list-item.wrapper";
@@ -52,7 +59,6 @@ type ListProps = {
isRenderedInActionSheet?: boolean;
CustomListComponent?: React.JSX.ElementType;
placeholder?: PlaceholderData;
id?: string;
};
export default function List(props: ListProps) {
@@ -86,20 +92,35 @@ export default function List(props: ListProps) {
};
const renderItem = React.useCallback(
({ index }: { index: number }) => {
return (
<ListItemWrapper
index={index}
isSheet={props.isRenderedInActionSheet || false}
items={props.data}
groupOptions={groupOptions}
group={groupType as GroupingKey}
renderedInRoute={props.renderedInRoute}
customAccentColor={props.customAccentColor}
dataType={props.dataType}
scrollRef={scrollRef}
/>
);
({ item, index }: { item: string | GroupHeader; index: number }) => {
if (isGroupHeader(item)) {
return (
<SectionHeader
screen={props.renderedInRoute}
item={item}
index={index}
dataType={props.dataType}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: scrollRef,
data: props.data
});
}}
/>
);
} else {
return (
<ListItemWrapper
id={item}
index={index}
isSheet={props.isRenderedInActionSheet || false}
items={props.data}
group={groupType as GroupingKey}
/>
);
}
},
[
groupOptions,
@@ -117,18 +138,16 @@ export default function List(props: ListProps) {
if (!event) return;
eSendEvent(eScrollEvent, {
y: event.nativeEvent.contentOffset.y,
route: props.renderedInRoute,
id: props.id || props.renderedInRoute
screen: props.renderedInRoute
});
},
[props.renderedInRoute, props.id]
[props.renderedInRoute]
);
useEffect(() => {
eSendEvent(eScrollEvent, {
y: 0,
route: props.renderedInRoute,
id: props.id || props.renderedInRoute
screen: props.renderedInRoute
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -162,10 +181,8 @@ export default function List(props: ListProps) {
onMomentumScrollEnd={() => {
tabBarRef.current?.unlock();
}}
getItemType={(item: number, index: number) => {
return props.data?.type(index);
}}
estimatedItemSize={isCompactModeEnabled ? 60 : 120}
getItemType={(item: any) => (isGroupHeader(item) ? "header" : "item")}
estimatedItemSize={isCompactModeEnabled ? 60 : 100}
directionalLockEnabled={true}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"

View File

@@ -16,53 +16,42 @@ 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 { getSortValue } from "@notesnook/core/dist/utils/grouping";
import {
Color,
GroupHeader,
GroupOptions,
GroupingKey,
Item,
ItemType,
Note,
Notebook,
VirtualizedGrouping,
Color,
Reminder,
Notebook,
Tag,
TrashItem,
VirtualizedGrouping
ItemType,
Note
} from "@notesnook/core";
import { getSortValue } from "@notesnook/core/dist/utils/grouping";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { eSendEvent } from "../../services/event-manager";
import {
NotebooksWithDateEdited,
TagsWithDateEdited,
isNoteResolvedData,
resolveItems
} from "../../stores/resolve-items";
import { RouteName } from "../../stores/use-navigation-store";
import { eOpenJumpToDialog } from "../../utils/events";
import { SectionHeader } from "../list-items/headers/section-header";
import { NoteWrapper } from "../list-items/note/wrapper";
import { db } from "../../common/database";
import { NotebookWrapper } from "../list-items/notebook/wrapper";
import ReminderItem from "../list-items/reminder";
import TagItem from "../list-items/tag";
export type WithDateEdited<T> = { items: T[]; dateEdited: number };
export type NotebooksWithDateEdited = WithDateEdited<Notebook>;
export type TagsWithDateEdited = WithDateEdited<Tag>;
type ListItemWrapperProps<TItem = Item> = {
group?: GroupingKey;
items: VirtualizedGrouping<TItem> | undefined;
id: string;
isSheet: boolean;
index: number;
renderedInRoute?: RouteName;
customAccentColor?: string;
dataType: string;
scrollRef: any;
groupOptions: GroupOptions;
};
export function ListItemWrapper(props: ListItemWrapperProps) {
const { items, group, isSheet, index, groupOptions } = props;
const { id, items, group, isSheet, index } = props;
const [item, setItem] = useState<Item>();
const tags = useRef<TagsWithDateEdited>();
const notebooks = useRef<NotebooksWithDateEdited>();
@@ -70,197 +59,83 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
const color = useRef<Color>();
const totalNotes = useRef<number>(0);
const attachmentsCount = useRef(0);
const [groupHeader, setGroupHeader] = useState<GroupHeader>();
const previousIndex = useRef<number>();
const refreshTimeout = useRef<NodeJS.Timeout>();
const currentItemId = useRef<string>();
const refreshItem = useCallback((resolvedItem: any) => {
if (!resolvedItem || !resolvedItem.data) {
tags.current = undefined;
notebooks.current = undefined;
reminder.current = undefined;
color.current = undefined;
attachmentsCount.current = 0;
totalNotes.current = 0;
}
if (resolvedItem && resolvedItem.item) {
const data = resolvedItem.data;
if (resolvedItem.item.type === "note" && isNoteResolvedData(data)) {
useEffect(() => {
(async function () {
const { item, data } = (await items?.item(id, resolveItems)) || {};
if (!item) return;
if (item.type === "note" && isNoteResolvedData(data)) {
tags.current = data.tags;
notebooks.current = data.notebooks;
reminder.current = data.reminder;
color.current = data.color;
attachmentsCount.current = data.attachmentsCount || 0;
} else if (
resolvedItem.item.type === "notebook" &&
typeof data === "number"
) {
attachmentsCount.current = data.attachmentsCount;
} else if (item.type === "notebook" && typeof data === "number") {
totalNotes.current = data;
} else if (resolvedItem.item.type === "tag" && typeof data === "number") {
} else if (item.type === "tag" && typeof data === "number") {
totalNotes.current = data;
}
currentItemId.current = resolvedItem.item.id;
setItem(resolvedItem.item);
setGroupHeader(resolvedItem.group);
}
}, []);
if (previousIndex.current !== index) {
previousIndex.current = index;
const resolvedItem = items?.cacheItem(index);
refreshItem(resolvedItem);
}
useEffect(() => {
(async function () {
try {
clearTimeout(refreshTimeout.current);
const idx = index;
refreshTimeout.current = setTimeout(async () => {
if (idx !== previousIndex.current) return;
const resolvedItem = await items?.item(idx, resolveItems);
if (idx !== previousIndex.current) return;
refreshItem(resolvedItem);
}, 100);
} catch (e) {
console.log("Error", e);
}
setItem(item);
})();
}, [index, items, refreshItem]);
}, [id, items]);
if (!item)
return (
<View
style={{
height: 120,
width: "100%"
}}
/>
);
if (!item) return <View style={{ height: 100, width: "100%" }} />;
const type = ((item as TrashItem).itemType || item.type) as ItemType;
switch (type) {
case "note": {
return (
<>
{groupHeader && previousIndex.current === index ? (
<SectionHeader
screen={props.renderedInRoute}
item={groupHeader}
index={index}
dataType={item.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: props.scrollRef,
data: items
});
}}
/>
) : null}
<NoteWrapper
item={item as Note}
tags={tags.current}
color={color.current}
notebooks={notebooks.current}
reminder={reminder.current}
attachmentsCount={attachmentsCount.current}
date={getDate(item, group)}
isRenderedInActionSheet={isSheet}
index={index}
/>
</>
<NoteWrapper
item={item as Note}
tags={tags.current}
color={color.current}
notebooks={notebooks.current}
reminder={reminder.current}
attachmentsCount={attachmentsCount.current}
date={getDate(item, group)}
isRenderedInActionSheet={isSheet}
index={index}
/>
);
}
case "notebook":
return (
<>
{groupHeader && previousIndex.current === index ? (
<SectionHeader
screen={props.renderedInRoute}
item={groupHeader}
index={index}
dataType={item.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: props.scrollRef,
data: items
});
}}
/>
) : null}
<NotebookWrapper
item={item as Notebook}
totalNotes={totalNotes.current}
date={getDate(item, group)}
index={index}
/>
</>
<NotebookWrapper
item={item as Notebook}
totalNotes={totalNotes.current}
date={getDate(item, group)}
index={index}
/>
);
case "reminder":
return (
<>
{groupHeader && previousIndex.current === index ? (
<SectionHeader
screen={props.renderedInRoute}
item={groupHeader}
index={index}
dataType={item.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: props.scrollRef,
data: items
});
}}
/>
) : null}
<ReminderItem
item={item as Reminder}
index={index}
isSheet={isSheet}
/>
</>
<ReminderItem item={item as Reminder} index={index} isSheet={isSheet} />
);
case "tag":
return (
<>
{groupHeader && previousIndex.current === index ? (
<SectionHeader
screen={props.renderedInRoute}
item={groupHeader}
index={index}
dataType={item.type}
color={props.customAccentColor}
groupOptions={groupOptions}
onOpenJumpToDialog={() => {
eSendEvent(eOpenJumpToDialog, {
ref: props.scrollRef,
data: items
});
}}
/>
) : null}
<TagItem
item={item as Tag}
index={index}
totalNotes={totalNotes.current}
/>
</>
<TagItem
item={item as Tag}
index={index}
totalNotes={totalNotes.current}
/>
);
default:
return null;
}
}
function withDateEdited<
T extends { dateEdited: number } | { dateModified: number }
>(items: T[]): WithDateEdited<T> {
let latestDateEdited = 0;
items.forEach((item) => {
const date = "dateEdited" in item ? item.dateEdited : item.dateModified;
if (latestDateEdited < date) latestDateEdited = date;
});
return { dateEdited: latestDateEdited, items };
}
function getDate(item: Item, groupType?: GroupingKey): number {
return (
getSortValue(
@@ -275,3 +150,120 @@ function getDate(item: Item, groupType?: GroupingKey): number {
) || 0
);
}
async function resolveItems(ids: string[], items: Record<string, Item>) {
const { type } = items[ids[0]];
if (type === "note") return resolveNotes(ids);
else if (type === "notebook") {
const data: Record<string, number> = {};
for (const id of ids) data[id] = await db.notebooks.totalNotes(id);
return data;
} else if (type === "tag") {
const data: Record<string, number> = {};
for (const id of ids)
data[id] = await db.relations.from({ id, type: "tag" }, "note").count();
return data;
}
return {};
}
type NoteResolvedData = {
notebooks?: NotebooksWithDateEdited;
reminder?: Reminder;
color?: Color;
tags?: TagsWithDateEdited;
attachmentsCount: number;
};
async function resolveNotes(ids: string[]) {
console.time("relations");
const relations = [
...(await db.relations
.to({ type: "note", ids }, ["notebook", "tag", "color"])
.get()),
...(await db.relations.from({ type: "note", ids }, "reminder").get())
];
console.timeEnd("relations");
const relationIds: {
notebooks: Set<string>;
colors: Set<string>;
tags: Set<string>;
reminders: Set<string>;
} = {
colors: new Set(),
notebooks: new Set(),
tags: new Set(),
reminders: new Set()
};
const grouped: Record<
string,
{
notebooks: string[];
color?: string;
tags: string[];
reminder?: string;
}
> = {};
for (const relation of relations) {
const noteId =
relation.toType === "relation" ? relation.fromId : relation.toId;
const data = grouped[noteId] || {
notebooks: [],
tags: []
};
if (relation.toType === "relation" && !data.reminder) {
data.reminder = relation.fromId;
relationIds.reminders.add(relation.fromId);
} else if (relation.fromType === "notebook" && data.notebooks.length < 2) {
data.notebooks.push(relation.fromId);
relationIds.notebooks.add(relation.fromId);
} else if (relation.fromType === "tag" && data.tags.length < 3) {
data.tags.push(relation.fromId);
relationIds.tags.add(relation.fromId);
} else if (relation.fromType === "color" && !data.color) {
data.color = relation.fromId;
relationIds.colors.add(relation.fromId);
}
grouped[relation.toId] = data;
}
console.time("resolve");
const resolved = {
notebooks: await db.notebooks.all.records(
Array.from(relationIds.notebooks)
),
tags: await db.tags.all.records(Array.from(relationIds.tags)),
colors: await db.colors.all.records(Array.from(relationIds.colors)),
reminders: await db.reminders.all.records(Array.from(relationIds.reminders))
};
console.timeEnd("resolve");
const data: Record<string, NoteResolvedData> = {};
for (const noteId in grouped) {
const group = grouped[noteId];
data[noteId] = {
color: group.color ? resolved.colors[group.color] : undefined,
reminder: group.reminder ? resolved.reminders[group.reminder] : undefined,
tags: withDateEdited(group.tags.map((id) => resolved.tags[id])),
notebooks: withDateEdited(
group.notebooks.map((id) => resolved.notebooks[id])
),
attachmentsCount:
(await db.attachments?.ofNote(noteId, "all").ids())?.length || 0
};
}
return data;
}
function isNoteResolvedData(data: unknown): data is NoteResolvedData {
return (
typeof data === "object" &&
!!data &&
"notebooks" in data &&
"reminder" in data &&
"color" in data &&
"tags" in data
);
}

View File

@@ -17,11 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getFormattedDate } from "@notesnook/common";
import { useThemeColors } from "@notesnook/theme";
import KeepAwake from "@sayem314/react-native-keep-awake";
import React, { useEffect, useRef, useState } from "react";
import { SafeAreaView, Text, View } from "react-native";
import { Modal, SafeAreaView, Text, View } from "react-native";
import Animated from "react-native-reanimated";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
@@ -35,7 +33,7 @@ import {
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeColors } from "@notesnook/theme";
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
@@ -47,6 +45,8 @@ import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import { useSettingStore } from "../../stores/use-setting-store";
import { getFormattedDate } from "@notesnook/common";
const MergeConflicts = () => {
const { colors } = useThemeColors();
@@ -62,7 +62,7 @@ const MergeConflicts = () => {
const applyChanges = async () => {
let _content = keep;
let note = await db.notes.note(_content.noteId);
let note = db.notes.note(_content.noteId).data;
await db.notes.add({
id: note.id,
conflicted: false,
@@ -100,7 +100,7 @@ const MergeConflicts = () => {
};
const show = async (item) => {
let noteContent = await db.content.get(item.contentId);
let noteContent = await db.content.raw(item.contentId);
content.current = { ...noteContent };
if (__DEV__) {
if (!noteContent.conflicted) {
@@ -320,7 +320,7 @@ const MergeConflicts = () => {
readonly
editorId=":conflictPrimary"
onLoad={async () => {
const note = await db.notes.note(content.current?.noteId);
const note = db.notes.note(content.current?.noteId)?.data;
if (!note) return;
await sleep(300);
eSendEvent(eOnLoadNote + ":conflictPrimary", {
@@ -357,7 +357,7 @@ const MergeConflicts = () => {
readonly
editorId=":conflictSecondary"
onLoad={async () => {
const note = await db.notes.note(content.current?.noteId);
const note = db.notes.note(content.current?.noteId)?.data;
if (!note) return;
await sleep(300);
eSendEvent(eOnLoadNote + ":conflictSecondary", {

View File

@@ -84,7 +84,7 @@ export default function NotePreview({ session, content, note }) {
context: "local",
positivePress: async () => {
await db.trash.delete(note.id);
useTrashStore.getState().refresh();
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastManager.show({
heading: "Permanently deleted items",

View File

@@ -17,10 +17,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Color, Note } from "@notesnook/core/dist/types";
import { DefaultColors } from "@notesnook/core/dist/collections/colors";
import { Color, ItemReference, Note } from "@notesnook/core/dist/types";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { ScrollView, View } from "react-native";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
@@ -31,29 +32,87 @@ import { useRelationStore } from "../../stores/use-relation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { refreshNotesPage } from "../../utils/events";
import { SIZE } from "../../utils/size";
import ColorPicker from "../dialogs/color-picker";
import { PressableButton } from "../ui/pressable";
import { FlashList } from "react-native-actions-sheet";
import { Button } from "../ui/button";
const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
export const ColorTags = ({ item }: { item: Note }) => {
const { colors } = useThemeColors();
const [isLinked, setIsLinked] = useState<boolean>();
const setColorNotes = useMenuStore((state) => state.setColorNotes);
useEffect(() => {
const checkIsLinked = async (color: Color) => {
const hasRelation = await db.relations.from(color, "note").has(note.id);
return hasRelation;
const isTablet = useSettingStore((state) => state.deviceMode) !== "mobile";
const updater = useRelationStore((state) => state.updater);
const getColorInfo = async (colorCode: string) => {
const dbColor = await db.colors.all.find((v) =>
v.and([v(`colorCode`, "==", colorCode)])
);
let isLinked = false;
if (dbColor) {
const hasRelation = await db.relations.from(dbColor, "note").has(item.id);
if (hasRelation) {
isLinked = true;
}
}
return {
linked: isLinked,
item: dbColor
};
};
checkIsLinked(item).then((info) => setIsLinked(info));
}, [item, note.id]);
const ColorItem = ({ name }: { name: keyof typeof DefaultColors }) => {
const color = DefaultColors[name];
const [colorInfo, setColorInfo] = useState<{
linked: boolean;
item: Color | undefined;
}>();
const toggleColor = async () => {
await db.relations.unlinkOfType("color", [item.id]);
useEffect(() => {
getColorInfo(color).then((info) => setColorInfo(info));
}, [color]);
if (!isLinked) {
await db.relations.add(item, note);
return (
<PressableButton
type="accent"
accentColor={color}
accentText={colors.static.white}
testID={notesnook.ids.dialogs.actionsheet.color(name)}
key={color}
onPress={() => changeColor(name)}
customStyle={{
width: 30,
height: 30,
borderRadius: 100,
justifyContent: "center",
alignItems: "center",
marginRight: isTablet ? 10 : undefined
}}
>
{colorInfo?.linked ? (
<Icon testID="icon-check" name="check" color="white" size={SIZE.lg} />
) : null}
</PressableButton>
);
};
const changeColor = async (color: string) => {
const colorInfo = await getColorInfo(DefaultColors[color]);
if (colorInfo.item) {
if (colorInfo.linked) {
await db.relations.unlink(colorInfo.item, item);
} else {
await db.relations.add(colorInfo.item, item);
}
} else {
const colorId = await db.colors.add({
title: color,
colorCode: DefaultColors[color]
});
const dbColor = await db.colors.color(colorId);
if (dbColor) {
await db.relations.add(dbColor as unknown as ItemReference, item);
}
}
useRelationStore.getState().update();
@@ -63,109 +122,20 @@ const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
};
return (
<PressableButton
type="accent"
accentColor={item.colorCode}
accentText={colors.static.white}
testID={notesnook.ids.dialogs.actionsheet.color(item.colorCode)}
key={item.id}
onPress={toggleColor}
customStyle={{
width: 30,
height: 30,
borderRadius: 100,
justifyContent: "center",
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
flexGrow: isTablet ? undefined : 1,
paddingHorizontal: 12,
paddingRight: 0,
alignItems: "center",
marginRight: 5
justifyContent: isTablet ? "center" : "space-between"
}}
>
{isLinked ? (
<Icon testID="icon-check" name="check" color="white" size={SIZE.lg} />
) : null}
</PressableButton>
);
};
export const ColorTags = ({ item }: { item: Note }) => {
const { colors } = useThemeColors();
const colorNotes = useMenuStore((state) => state.colorNotes);
const isTablet = useSettingStore((state) => state.deviceMode) !== "mobile";
const updater = useRelationStore((state) => state.updater);
const [visible, setVisible] = useState(false);
const note = item;
const renderItem = useCallback(
({ item }: { item: Color }) => (
<ColorItem note={note} key={item.id} item={item} />
),
[note]
);
return (
<>
<ColorPicker visible={visible} setVisible={setVisible} />
<View
style={{
flexGrow: isTablet ? undefined : 1,
paddingRight: 0,
flexDirection: "row"
}}
>
{!colorNotes || !colorNotes.length ? (
<Button
onPress={async () => {
useSettingStore.getState().setSheetKeyboardHandler(false);
setVisible(true);
}}
buttonType={{
text: colors.primary.accent
}}
title="Add color"
type="grayBg"
icon="plus"
iconPosition="right"
height={30}
fontSize={SIZE.xs}
style={{
marginRight: 5,
borderRadius: 100,
paddingHorizontal: 8
}}
/>
) : (
<PressableButton
customStyle={{
width: 30,
height: 30,
borderRadius: 100,
justifyContent: "center",
alignItems: "center",
marginRight: 5
}}
type="grayBg"
onPress={() => {
useSettingStore.getState().setSheetKeyboardHandler(false);
setVisible(true);
}}
>
<Icon
testID="icon-plus"
name="plus"
color={colors.primary.icon}
size={SIZE.lg}
/>
</PressableButton>
)}
<FlashList
data={colorNotes}
estimatedItemSize={30}
horizontal
extraData={updater}
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
/>
</View>
</>
{Object.keys(DefaultColors).map((name: keyof typeof DefaultColors) => {
return <ColorItem key={name} name={name} />;
})}
</View>
);
};

View File

@@ -120,15 +120,13 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
<Line bottom={0} />
{item.type === "note" ? <Tags close={close} item={item} /> : null}
{item.type === "note" ? (
<View
style={{
paddingHorizontal: 12
}}
>
<Notebooks note={item} close={close} />
</View>
) : null}
<View
style={{
paddingHorizontal: 12
}}
>
<Notebooks note={item} close={close} />
</View>
<Items
item={item}

View File

@@ -218,7 +218,8 @@ export const Items = ({ item, buttons, close }) => {
<ScrollView
horizontal
style={{
paddingHorizontal: 12
paddingHorizontal: 12,
paddingVertical: 12
}}
>
{topBarItems.map(renderTopBarItem)}

View File

@@ -17,24 +17,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note } from "@notesnook/core";
import { GroupHeader, Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useCallback, useEffect } from "react";
import {
ActivityIndicator,
Keyboard,
TouchableOpacity,
View
} from "react-native";
import { Keyboard, TouchableOpacity, View } from "react-native";
import { ActionSheetRef, FlashList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { presentSheet } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { ItemSelection } from "../../../stores/item-selection-store";
import {
useNotebookStore,
useNotebooks
} from "../../../stores/use-notebook-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useSettingStore } from "../../../stores/use-setting-store";
@@ -47,7 +39,6 @@ import { IconButton } from "../../ui/icon-button";
import Paragraph from "../../ui/typography/paragraph";
import { NotebookItem } from "./notebook-item";
import { useNotebookItemSelectionStore } from "./store";
import { SIZE } from "../../../utils/size";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -94,7 +85,7 @@ const MoveNoteSheet = ({
actionSheetRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
const [notebooks, loading] = useNotebooks();
const notebooks = useNotebookStore((state) => state.notebooks);
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
@@ -144,9 +135,10 @@ const MoveNoteSheet = ({
};
const renderNotebook = useCallback(
({ item, index }: { item: string | number; index: number }) => (
<NotebookItem items={notebooks} id={index} index={index} />
),
({ item, index }: { item: string | GroupHeader; index: number }) =>
(item as GroupHeader).type === "header" ? null : (
<NotebookItem items={notebooks} id={item as string} index={index} />
),
[notebooks]
);
@@ -228,11 +220,12 @@ const MoveNoteSheet = ({
}}
>
<FlashList
data={notebooks?.ids}
data={notebooks?.ids?.filter((id) => typeof id === "string")}
style={{
width: "100%"
}}
estimatedItemSize={50}
keyExtractor={(item) => item as string}
renderItem={renderNotebook}
ListEmptyComponent={
<View
@@ -243,16 +236,7 @@ const MoveNoteSheet = ({
height: 200
}}
>
{loading ? (
<ActivityIndicator
size={SIZE.lg}
color={colors.primary.accent}
/>
) : (
<Paragraph color={colors.primary.icon}>
No notebooks
</Paragraph>
)}
<Paragraph color={colors.primary.icon}>No notebooks</Paragraph>
</View>
}
ListFooterComponent={<View style={{ height: 50 }} />}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import React, { useMemo } from "react";
import { View, useWindowDimensions } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { useTotalNotes } from "../../../hooks/use-db-item";
@@ -46,41 +46,31 @@ export const NotebookItem = ({
parent,
items
}: {
id: string | number;
id: string;
currentLevel?: number;
index: number;
parent?: NotebookParentProp;
items?: VirtualizedGrouping<Notebook>;
}) => {
const { nestedNotebooks, notebook: item } = useNotebook(id, items, true);
const expanded = useNotebookExpandedStore((state) =>
item?.id ? state.expanded[item?.id] : false
);
const { totalNotes: totalNotes, getTotalNotes } = useTotalNotes("notebook");
const { nestedNotebooks, notebook: item } = useNotebook(id, items);
const ids = useMemo(() => (id ? [id] : []), [id]);
const { totalNotes: totalNotes } = useTotalNotes(ids, "notebook");
const focusedRouteId = useNavigationStore((state) => state.focusedRouteId);
const { colors } = useThemeColors("sheet");
const selection = useNotebookItemSelectionStore((state) =>
item?.id ? state.selection[item?.id] : undefined
id ? state.selection[id] : undefined
);
const isSelected = selection === "selected";
const isFocused = focusedRouteId === id;
const { fontScale } = useWindowDimensions();
useEffect(() => {
if (item?.id) {
getTotalNotes([item?.id]);
}
}, [getTotalNotes, item?.id]);
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
const onPress = () => {
if (!item) return;
const state = useNotebookItemSelectionStore.getState();
if (isSelected) {
state.markAs(
item,
!state.initialState[item?.id] ? undefined : "deselected"
);
state.markAs(item, !state.initialState[id] ? undefined : "deselected");
return;
}
@@ -122,7 +112,7 @@ export const NotebookItem = ({
item,
!isSelected
? "selected"
: !state.initialState[item?.id]
: !state.initialState[id]
? undefined
: "deselected"
);
@@ -151,8 +141,7 @@ export const NotebookItem = ({
size={SIZE.xl}
color={isSelected ? colors.selected.icon : colors.primary.icon}
onPress={() => {
if (!item?.id) return;
useNotebookExpandedStore.getState().setExpanded(item?.id);
useNotebookExpandedStore.getState().setExpanded(id);
}}
top={0}
left={0}
@@ -212,9 +201,9 @@ export const NotebookItem = ({
alignItems: "center"
}}
>
{item?.id && totalNotes?.(item?.id) ? (
{totalNotes?.(id) ? (
<Paragraph size={SIZE.sm} color={colors.secondary.paragraph}>
{totalNotes(item?.id)}
{totalNotes(id)}
</Paragraph>
) : null}
<IconButton
@@ -242,8 +231,8 @@ export const NotebookItem = ({
? null
: nestedNotebooks?.ids.map((id, index) => (
<NotebookItem
key={item?.id + "_" + index}
id={index}
key={id as string}
id={id as string}
index={index}
currentLevel={currentLevel + 1}
items={nestedNotebooks}

View File

@@ -115,7 +115,7 @@ const ManageTagsSheet = (props: {
});
} else {
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
console.log("items loaded tags", items.ids);
console.log("items loaded tags");
setTags(items);
});
}
@@ -177,7 +177,7 @@ const ManageTagsSheet = (props: {
}
useRelationStore.getState().update();
useTagStore.getState().refresh();
useTagStore.getState().setTags();
setQuery(undefined);
} catch (e) {
ToastManager.show({
@@ -225,7 +225,7 @@ const ManageTagsSheet = (props: {
console.error(e);
}
}
useTagStore.getState().refresh();
useTagStore.getState().setTags();
useRelationStore.getState().update();
refreshTags();
setTimeout(() => {
@@ -237,10 +237,11 @@ const ManageTagsSheet = (props: {
);
const renderTag = useCallback(
({ item, index }: { item: string | number; index: number }) => (
({ item }: { item: string; index: number }) => (
<TagItem
key={item as string}
tags={tags as VirtualizedGrouping<Tag>}
id={index}
id={item as string}
onPress={onPress}
/>
),
@@ -302,13 +303,13 @@ const ManageTagsSheet = (props: {
) : null}
<FlatList
data={tags?.ids}
data={tags?.ids?.filter((id) => typeof id === "string") as string[]}
style={{
width: "100%"
}}
keyboardShouldPersistTaps
keyboardDismissMode="interactive"
keyExtractor={(item) => item + "_tag"}
keyExtractor={(item) => item as string}
renderItem={renderTag}
ListEmptyComponent={
<View
@@ -351,38 +352,29 @@ const TagItem = ({
tags,
onPress
}: {
id: string | number;
id: string;
tags: VirtualizedGrouping<Tag>;
onPress: (id: string) => void;
}) => {
const { colors } = useThemeColors();
const [tag] = useDBItem(id, "tag", tags);
const selection = useTagItemSelection((state) =>
tag?.id ? state.selection[tag?.id] : false
);
const selection = useTagItemSelection((state) => state.selection[id]);
return !tag ? null : (
return (
<PressableButton
key={tag?.id}
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "flex-start",
height: 40
}}
onPress={() => {
if (!tag) return;
onPress(tag.id);
}}
onPress={() => onPress(id)}
type="gray"
>
{!tag ? null : (
<Icon
size={22}
onPress={() => {
if (!tag) return;
onPress(tag.id);
}}
onPress={() => onPress(id)}
color={
selection === "selected" || selection === "intermediate"
? colors.selected.icon
@@ -414,6 +406,7 @@ const TagItem = ({
style={{
width: 200,
height: 30,
// backgroundColor: colors.secondary.background,
borderRadius: 5
}}
/>

View File

@@ -36,7 +36,6 @@ import { IconButton } from "../../ui/icon-button";
import { PressableButton } from "../../ui/pressable";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { useDBItem } from "../../../hooks/use-db-item";
export const MoveNotes = ({
notebook,
@@ -46,13 +45,11 @@ export const MoveNotes = ({
fwdRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
const currentNotebook = notebook;
const [currentNotebook, setCurrentNotebook] = useState(notebook);
const { height } = useWindowDimensions();
const [selectedNoteIds, setSelectedNoteIds] = useState<string[]>([]);
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const [existingNoteIds, setExistingNoteIds] = useState<string[]>([]);
useEffect(() => {
db.notes?.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
setNotes(notes);
@@ -88,18 +85,17 @@ export const MoveNotes = ({
);
const renderItem = React.useCallback(
({ index }: { item: string | number; index: number }) => {
({ item }: { item: string }) => {
return (
<SelectableNoteItem
id={index}
id={item}
items={notes}
select={select}
selected={(id) => selectedNoteIds?.indexOf(id) > -1}
exists={(id) => existingNoteIds.indexOf(id) > -1}
selected={selectedNoteIds?.indexOf(item) > -1}
/>
);
},
[existingNoteIds, notes, select, selectedNoteIds]
[notes, select, selectedNoteIds]
);
return (
@@ -132,7 +128,9 @@ export const MoveNotes = ({
</Paragraph>
</View>
}
data={notes?.ids}
data={(notes?.ids as string[])?.filter(
(id) => existingNoteIds?.indexOf(id) === -1
)}
renderItem={renderItem}
/>
{selectedNoteIds.length > 0 ? (
@@ -159,19 +157,21 @@ const SelectableNoteItem = ({
id,
items,
select,
selected,
exists
selected
}: {
id: string | number;
id: string;
items?: VirtualizedGrouping<Note>;
select: (id: string) => void;
selected?: (id: string) => boolean;
exists: (id: string) => boolean;
selected?: boolean;
}) => {
const { colors } = useThemeColors();
const [item] = useDBItem(id, "note", items);
const [item, setItem] = useState<Note>();
return !item || exists(item.id) ? null : (
useEffect(() => {
items?.item(id).then((item) => setItem(item));
}, [id, items]);
return !item ? null : (
<PressableButton
testID="listitem.select"
onPress={() => {
@@ -197,14 +197,10 @@ const SelectableNoteItem = ({
select(item?.id);
}}
name={
selected?.(item?.id)
? "check-circle-outline"
: "checkbox-blank-circle-outline"
selected ? "check-circle-outline" : "checkbox-blank-circle-outline"
}
type="selected"
color={
selected?.(item?.id) ? colors.selected.icon : colors.primary.icon
}
color={selected ? colors.selected.icon : colors.primary.icon}
/>
<View

View File

@@ -16,7 +16,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { GroupHeader, Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, {
createContext,
@@ -123,11 +123,7 @@ export const NotebookSheet = () => {
nestedNotebooks: notebooks,
nestedNotebookNotesCount: totalNotes,
groupOptions
} = useNotebook(
currentRoute === "Notebook" ? root : undefined,
undefined,
true
);
} = useNotebook(currentRoute === "Notebook" ? root : undefined);
const PLACEHOLDER_DATA = {
heading: "Notebooks",
@@ -144,16 +140,17 @@ export const NotebookSheet = () => {
item,
index
}: {
item: string | number;
item: string | GroupHeader;
index: number;
}) => (
<NotebookItem
items={notebooks}
id={index}
index={index}
totalNotes={totalNotes}
/>
);
}) =>
(item as GroupHeader).type === "header" ? null : (
<NotebookItem
items={notebooks}
id={item as string}
index={index}
totalNotes={totalNotes}
/>
);
const selectionContext = {
selection: selection,
@@ -180,9 +177,11 @@ export const NotebookSheet = () => {
useEffect(() => {
if (canShow) {
setImmediate(async () => {
setTimeout(async () => {
if (!focusedRouteId) return;
const nextRoot = await findRootNotebookId(focusedRouteId);
setRoot(nextRoot);
if (nextRoot !== currentItem.current) {
console.log(
"NotebookSheet.useEffect.canShow",
@@ -204,9 +203,8 @@ export const NotebookSheet = () => {
ref.current?.show(snapPoint);
}
console.log("NotebookSheet.useEffect.canShow", focusedRouteId);
setRoot(nextRoot);
onRequestUpdate();
});
}, 0);
} else {
setSelection([]);
setEnabled(false);
@@ -304,48 +302,28 @@ export const NotebookSheet = () => {
}}
>
{enabled ? (
<>
<IconButton
customStyle={{
marginLeft: 10,
width: 40 * fontScale,
height: 40 * fontScale
}}
onPress={async () => {
await deleteItems(
selection.map((notebook) => notebook.id),
"notebook"
);
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
return;
}}
color={colors.primary.icon}
tooltipText="Move to trash"
tooltipPosition={1}
name="delete"
size={22}
/>
<IconButton
customStyle={{
marginLeft: 10,
width: 40 * fontScale,
height: 40 * fontScale
}}
onPress={() => {
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
}}
color={colors.primary.icon}
tooltipText="Clear selection"
tooltipPosition={1}
name="close"
size={22}
/>
</>
<IconButton
customStyle={{
marginLeft: 10,
width: 40 * fontScale,
height: 40 * fontScale
}}
onPress={async () => {
await deleteItems(
selection.map((notebook) => notebook.id),
"notebook"
);
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
return;
}}
color={colors.primary.icon}
tooltipText="Move to trash"
tooltipPosition={1}
name="delete"
size={22}
/>
) : (
<>
<IconButton
@@ -418,6 +396,7 @@ export const NotebookSheet = () => {
progressBackgroundColor={colors.primary.background}
/>
}
keyExtractor={(item) => item as string}
renderItem={renderNotebook}
ListEmptyComponent={
<View
@@ -447,7 +426,7 @@ const NotebookItem = ({
parent,
items
}: {
id: string | number;
id: string;
totalNotes: (id: string) => number;
currentLevel?: number;
index: number;
@@ -458,7 +437,7 @@ const NotebookItem = ({
nestedNotebookNotesCount,
nestedNotebooks,
notebook: item
} = useNotebook(id, items, true);
} = useNotebook(id, items);
const isFocused = useNavigationStore((state) => state.focusedRouteId === id);
const { colors } = useThemeColors("sheet");
const selection = useSelection();
@@ -466,9 +445,7 @@ const NotebookItem = ({
selection.selection.findIndex((selected) => selected.id === item?.id) > -1;
const { fontScale } = useWindowDimensions();
const expanded = useNotebookExpandedStore((state) =>
item?.id ? state.expanded[item?.id] : undefined
);
const expanded = useNotebookExpandedStore((state) => state.expanded[id]);
return (
<View
@@ -498,7 +475,8 @@ const NotebookItem = ({
width: "100%",
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 12,
paddingLeft: 0,
paddingRight: 12,
borderRadius: 0
}}
>
@@ -508,33 +486,6 @@ const NotebookItem = ({
alignItems: "center"
}}
>
{nestedNotebooks?.ids.length ? (
<IconButton
size={SIZE.lg}
color={isSelected ? colors.selected.icon : colors.primary.icon}
onPress={() => {
if (!item?.id) return;
useNotebookExpandedStore.getState().setExpanded(item?.id);
}}
top={0}
left={0}
bottom={0}
right={0}
customStyle={{
width: 35,
height: 35
}}
name={expanded ? "chevron-down" : "chevron-right"}
/>
) : (
<View
style={{
width: 35,
height: 35
}}
/>
)}
{selection.enabled ? (
<IconButton
size={SIZE.lg}
@@ -544,9 +495,8 @@ const NotebookItem = ({
bottom={0}
right={0}
customStyle={{
width: 35,
height: 35,
marginRight: 5
width: 40,
height: 40
}}
name={
isSelected
@@ -556,6 +506,36 @@ const NotebookItem = ({
/>
) : null}
{nestedNotebooks?.ids.length ? (
<IconButton
size={SIZE.lg}
color={isSelected ? colors.selected.icon : colors.primary.icon}
onPress={() => {
useNotebookExpandedStore.getState().setExpanded(id);
}}
top={0}
left={0}
bottom={0}
right={0}
customStyle={{
width: 40,
height: 40
}}
name={expanded ? "chevron-down" : "chevron-right"}
/>
) : (
<>
{selection?.enabled ? null : (
<View
style={{
width: 40,
height: 40
}}
/>
)}
</>
)}
<Paragraph
color={
isFocused ? colors.selected.paragraph : colors.secondary.paragraph
@@ -573,9 +553,9 @@ const NotebookItem = ({
alignItems: "center"
}}
>
{item?.id && totalNotes?.(item?.id) ? (
{totalNotes?.(id) ? (
<Paragraph size={SIZE.sm} color={colors.secondary.paragraph}>
{totalNotes(item?.id)}
{totalNotes(id)}
</Paragraph>
) : null}
<IconButton
@@ -600,11 +580,10 @@ const NotebookItem = ({
{!expanded
? null
: item &&
nestedNotebooks?.ids.map((id, index) => (
: nestedNotebooks?.ids.map((id, index) => (
<NotebookItem
key={item.id + "_" + index}
id={index}
key={id as string}
id={id as string}
index={index}
totalNotes={nestedNotebookNotesCount}
currentLevel={currentLevel + 1}

View File

@@ -143,9 +143,7 @@ const Sort = ({ type, screen }) => {
/>
) : (
Object.keys(SORT).map((item) =>
(item === "title" &&
groupOptions.groupBy !== "none" &&
screen !== "TopicSheet") ||
(item === "title" && groupOptions.groupBy !== "none") ||
((screen !== "Tags" || screen !== "Reminders") &&
item === "dateModified") ||
((screen === "Tags" || screen === "Reminders") &&
@@ -173,7 +171,7 @@ const Sort = ({ type, screen }) => {
...groupOptions,
sortBy: type === "trash" ? "dateDeleted" : item
};
if (screen === "TopicSheet") {
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);

View File

@@ -17,29 +17,26 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Color } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { ColoredNotes } from "../../screens/notes/colored";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { SIZE, normalize } from "../../utils/size";
import { presentDialog } from "../dialog/functions";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Color } from "@notesnook/core";
export const ColorSection = React.memo(
function ColorSection() {
const [colorNotes, loadingColors] = useMenuStore((state) => [
state.colorNotes,
state.loadingColors
]);
const loading = useSettingStore((state) => state.isAppLoading);
const colorNotes = useMenuStore((state) => state.colorNotes);
const loading = useNoteStore((state) => state.loading);
const setColorNotes = useMenuStore((state) => state.setColorNotes);
useEffect(() => {
@@ -48,11 +45,9 @@ export const ColorSection = React.memo(
}
}, [loading, setColorNotes]);
return !loadingColors
? colorNotes.map((item) => {
return <ColorItem key={item.id} item={item} />;
})
: null;
return colorNotes.map((item) => {
return <ColorItem key={item.id} item={item} />;
});
},
() => true
);
@@ -61,10 +56,36 @@ const ColorItem = React.memo(
function ColorItem({ item }: { item: Color }) {
const { colors, isDark } = useThemeColors();
const setColorNotes = useMenuStore((state) => state.setColorNotes);
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.id
const [headerTextState, setHeaderTextState] = useState<{
id: string | undefined;
}>({
id: undefined
});
const isFocused = headerTextState?.id === item.id;
const onHeaderStateChange = useCallback(
(state: any) => {
setTimeout(() => {
let id = state.focusedRouteId;
if (id === item.id) {
setHeaderTextState({ id: state.currentScreen.id });
} else {
if (headerTextState !== null) {
setHeaderTextState({ id: undefined });
}
}
}, 300);
},
[headerTextState, item.id]
);
useEffect(() => {
const remove = useNavigationStore.subscribe(onHeaderStateChange);
return () => {
remove();
};
}, [headerTextState, onHeaderStateChange]);
const onPress = (item: Color) => {
ColoredNotes.navigate(item, false);

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 { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import { FlatList, View } from "react-native";
import { notesnook } from "../../../e2e/test.ids";
@@ -25,16 +24,18 @@ import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useNoteStore } from "../../stores/use-notes-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useThemeColors } from "@notesnook/theme";
import { useUserStore } from "../../stores/use-user-store";
import { MenuItemsList } from "../../utils/menu-items";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
import { MenuItemsList } from "../../utils/menu-items";
import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";
import { TagsSection } from "./pinned-section";
import { UserStatus } from "./user-status";
import { useThemeStore } from "../../stores/use-theme-store";
export const SideMenu = React.memo(
function SideMenu() {
@@ -43,8 +44,7 @@ export const SideMenu = React.memo(
const subscriptionType = useUserStore(
(state) => state.user?.subscription?.type
);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const loading = useNoteStore((state) => state.loading);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
@@ -96,7 +96,7 @@ export const SideMenu = React.memo(
[noTextMode]
);
return !isAppLoading && introCompleted ? (
return !loading && introCompleted ? (
<View
style={{
height: "100%",

View File

@@ -33,9 +33,10 @@ import Paragraph from "../ui/typography/paragraph";
export const MenuItem = React.memo(
function MenuItem({ item, index, testID, rightBtn }) {
const { colors } = useThemeColors();
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.name
const [headerTextState, setHeaderTextState] = useState(
useNavigationStore.getState().focusedRouteId
);
let isFocused = headerTextState?.id === item.name;
const primaryColors = isFocused ? colors.selected : colors.primary;
const _onPress = () => {
@@ -43,10 +44,7 @@ export const MenuItem = React.memo(
item.func();
} else {
if (useNavigationStore.getState().currentRoute !== item.name) {
Navigation.navigate(item.name, {
canGoBack: false,
beta: item.isBeta
});
Navigation.push(item.name, { canGoBack: false, beta: item.isBeta });
}
}
if (item.close) {
@@ -56,6 +54,29 @@ export const MenuItem = React.memo(
}
};
const onHeaderStateChange = useCallback(
(state) => {
setTimeout(() => {
let id = state.focusedRouteId;
if (id === item.name) {
setHeaderTextState({ id: state.focusedRouteId });
} else {
if (headerTextState !== null) {
setHeaderTextState(null);
}
}
}, 300);
},
[headerTextState, item.name]
);
useEffect(() => {
let unsub = useNavigationStore.subscribe(onHeaderStateChange);
return () => {
unsub();
};
}, [headerTextState, onHeaderStateChange]);
return (
<PressableButton
testID={testID}

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Notebook, Tag } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { FlatList, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
@@ -28,7 +28,7 @@ import { TaggedNotes } from "../../screens/notes/tagged";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { SIZE, normalize } from "../../utils/size";
import { Properties } from "../properties";
import { Button } from "../ui/button";
@@ -41,8 +41,8 @@ import Paragraph from "../ui/typography/paragraph";
export const TagsSection = React.memo(
function TagsSection() {
const [menuPins] = useMenuStore((state) => [state.menuPins]);
const loading = useSettingStore((state) => state.isAppLoading);
const menuPins = useMenuStore((state) => state.menuPins);
const loading = useNoteStore((state) => state.loading);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
useEffect(() => {
@@ -109,13 +109,44 @@ export const PinItem = React.memo(
const setMenuPins = useMenuStore((state) => state.setMenuPins);
const [visible, setVisible] = useState(false);
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === item.id
);
const primaryColors = isFocused ? colors.selected : colors.primary;
const color = isFocused ? colors.selected.accent : colors.primary.icon;
const [headerTextState, setHeaderTextState] = useState<{
id?: string;
}>({});
const primaryColors =
headerTextState?.id === item.id ? colors.selected : colors.primary;
const isFocused = headerTextState?.id === item.id;
const color =
headerTextState?.id === item.id
? colors.selected.accent
: colors.primary.icon;
const fwdRef = useRef();
const onHeaderStateChange = useCallback(
(state: any) => {
setTimeout(() => {
const id = state.focusedRouteId;
if (id === item.id) {
setHeaderTextState({
id
});
} else {
if (headerTextState !== null) {
setHeaderTextState({});
}
}
}, 300);
},
[headerTextState, item.id]
);
useEffect(() => {
const remove = useNavigationStore.subscribe(onHeaderStateChange);
return () => {
remove();
};
}, [headerTextState, onHeaderStateChange]);
const icons = {
topic: "bookmark",
notebook: "book-outline",

View File

@@ -90,7 +90,6 @@ const SheetWrapper = ({
};
useEffect(() => {
if (useUserStore.getState().disableAppLockRequests) return;
if (SettingsService.get().appLockMode === "background") {
if (appState === "background") {
const ref = fwdRef || localRef;

View File

@@ -192,7 +192,7 @@ export const useActions = ({
});
InteractionManager.runAfterInteractions(() => {
useTagStore.getState().refresh();
useTagStore.getState().setTags();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
@@ -223,7 +223,7 @@ export const useActions = ({
await db.tags.remove(item.id);
}
setImmediate(() => {
useTagStore.getState().refresh();
useTagStore.getState().setTags();
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
});

View File

@@ -78,10 +78,11 @@ import {
import PremiumService from "../services/premium";
import SettingsService from "../services/settings";
import Sync from "../services/sync";
import { initAfterSync } from "../stores";
import { initAfterSync, initialize } from "../stores";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { useEditorStore } from "../stores/use-editor-store";
import { useMessageStore } from "../stores/use-message-store";
import { useNoteStore } from "../stores/use-notes-store";
import { useSettingStore } from "../stores/use-setting-store";
import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { updateStatusBarColor } from "../utils/colors";
@@ -277,7 +278,7 @@ const onSubscriptionError = async (error: RNIap.PurchaseError) => {
const SodiumEventEmitter = new NativeEventEmitter(NativeModules.Sodium);
export const useAppEvents = () => {
const loading = useSettingStore((state) => state.isAppLoading);
const loading = useNoteStore((state) => state.loading);
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
state.setLastSynced,
state.setUser,
@@ -627,7 +628,8 @@ export const useAppEvents = () => {
if (!db.isInitialized) {
await db.init();
}
useSettingStore.getState().setAppLoading(false);
await initialize();
useNoteStore.getState().setLoading(false);
},
disableClosing: true
});
@@ -654,7 +656,10 @@ export const useAppEvents = () => {
}
}
if (IsDatabaseMigrationRequired()) return;
useSettingStore.getState().setAppLoading(false);
await initialize();
setImmediate(() => {
useNoteStore.getState().setLoading(false);
});
Walkthrough.init();
}, [IsDatabaseMigrationRequired]);
@@ -665,7 +670,7 @@ export const useAppEvents = () => {
if (
!state.appLocked &&
db.isInitialized &&
useSettingStore.getState().isAppLoading
useNoteStore.getState().loading
) {
initializeDatabase();
sub();

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useEffect, useState } from "react";
import { useAttachmentStore } from "../stores/use-attachment-store";
import { Attachment } from "@notesnook/core";
type AttachmentProgress = {
type: string;
@@ -28,7 +27,7 @@ type AttachmentProgress = {
};
export const useAttachmentProgress = (
attachment?: Attachment,
attachment: any,
encryption?: boolean
): [
AttachmentProgress | undefined,
@@ -46,9 +45,7 @@ export const useAttachmentProgress = (
);
useEffect(() => {
const attachmentProgress = !attachment
? null
: progress?.[attachment?.hash];
const attachmentProgress = progress?.[attachment?.metadata?.hash];
if (attachmentProgress) {
const type = attachmentProgress.type;
const loaded =

View File

@@ -26,7 +26,7 @@ import {
Tag,
VirtualizedGrouping
} from "@notesnook/core";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useState } from "react";
import { db } from "../common/database";
import {
eSendEvent,
@@ -45,36 +45,22 @@ type ItemTypeKey = {
shortcut: Shortcut;
};
function isValidIdOrIndex(idOrIndex?: string | number) {
return typeof idOrIndex === "number" || typeof idOrIndex === "string";
}
export const useDBItem = <T extends keyof ItemTypeKey>(
idOrIndex?: string | number,
id?: string,
type?: T,
items?: VirtualizedGrouping<ItemTypeKey[T]>
): [ItemTypeKey[T] | undefined, () => void] => {
const [item, setItem] = useState<ItemTypeKey[T]>();
const itemIdRef = useRef<string>();
const prevIdOrIndexRef = useRef<string | number>();
if (prevIdOrIndexRef.current !== idOrIndex) {
itemIdRef.current = undefined;
prevIdOrIndexRef.current = idOrIndex;
}
useEffect(() => {
const onUpdateItem = (itemId?: string) => {
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (typeof itemId === "string" && itemId !== id) return;
if (!id) return;
console.log("useDBItem.onUpdateItem", id, type);
if (!isValidIdOrIndex(idOrIndex)) return;
console.log("useDBItem.onUpdateItem", idOrIndex, type);
if (items && typeof idOrIndex === "number") {
items.item(idOrIndex).then((item) => {
setItem(item.item);
itemIdRef.current = item.item.id;
if (items) {
items.item(id).then((item) => {
setItem(item);
});
} else {
if (!(db as any)[type + "s"][type]) {
@@ -83,14 +69,9 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
`db.${type}s.${type}(id: string)`
);
} else {
console.log("get notebook");
(db as any)[type + "s"]
?.[type]?.(idOrIndex as string)
.then((item: ItemTypeKey[T]) => {
setItem(item);
itemIdRef.current = item.id;
});
?.[type]?.(id)
.then((item: ItemTypeKey[T]) => setItem(item));
}
}
};
@@ -99,42 +80,46 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
return () => {
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);
};
}, [idOrIndex, type, items]);
}, [id, type, items]);
return [
isValidIdOrIndex(idOrIndex) ? (item as ItemTypeKey[T]) : undefined,
id ? (item as ItemTypeKey[T]) : undefined,
() => {
if (idOrIndex) {
eSendEvent(eDBItemUpdate, itemIdRef.current || idOrIndex);
if (id) {
eSendEvent(eDBItemUpdate, id);
}
}
];
};
export const useTotalNotes = (type: "notebook" | "tag" | "color") => {
export const useTotalNotes = (
ids: string[],
type: "notebook" | "tag" | "color"
) => {
const [totalNotesById, setTotalNotesById] = useState<{
[id: string]: number;
}>({});
const getTotalNotes = React.useCallback(
(ids: string[]) => {
if (!ids || !ids.length || !type) return;
db.relations
.from({ type: type, ids: ids as string[] }, ["note"])
.get()
.then((relations) => {
const totalNotesById: any = {};
for (const id of ids) {
totalNotesById[id] = relations.filter(
(relation) => relation.fromId === id && relation.toType === "note"
)?.length;
}
setTotalNotesById(totalNotesById);
});
console.log("useTotalNotes.getTotalNotes");
},
[type]
);
const getTotalNotes = React.useCallback(() => {
if (!ids || !ids.length || !type) return;
db.relations
.from({ type: "notebook", ids: ids as string[] }, ["notebook", "note"])
.get()
.then((relations) => {
const totalNotesById: any = {};
for (const id of ids) {
totalNotesById[id] = relations.filter(
(relation) => relation.fromId === id && relation.toType === "note"
)?.length;
}
setTotalNotesById(totalNotesById);
});
console.log("useTotalNotes.getTotalNotes");
}, [ids, type]);
useEffect(() => {
getTotalNotes();
}, [ids, type, getTotalNotes]);
return {
totalNotes: (id: string) => {

View File

@@ -17,78 +17,74 @@ 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 { Notebook, VirtualizedGrouping } from "@notesnook/core";
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { db } from "../common/database";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { eGroupOptionsUpdated, eOnNotebookUpdated } from "../utils/events";
import { useDBItem, useTotalNotes } from "./use-db-item";
export const useNotebook = (
id?: string | number,
items?: VirtualizedGrouping<Notebook>,
nestedNotebooks?: boolean
id?: string,
items?: VirtualizedGrouping<Notebook>
) => {
const [item, refresh] = useDBItem(id, "notebook", items);
const groupOptions = db.settings.getGroupOptions("notebooks");
const [groupOptions, setGroupOptions] = useState(
db.settings.getGroupOptions("notebooks")
);
const [notebooks, setNotebooks] = useState<VirtualizedGrouping<Notebook>>();
const { totalNotes: nestedNotebookNotesCount, getTotalNotes } =
useTotalNotes("notebook");
const { totalNotes: nestedNotebookNotesCount } = useTotalNotes(
notebooks?.ids as string[],
"notebook"
);
const onRequestUpdate = React.useCallback(() => {
if (!item?.id) return;
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
const selector = db.relations.from(
{
type: "notebook",
id: item.id
},
"notebook"
).selector;
selector.ids().then((notebookIds) => {
getTotalNotes(notebookIds);
});
selector
.sorted(db.settings.getGroupOptions("notebooks"))
if (!id) return;
console.log("useNotebook.onRequestUpdate", id, Date.now());
db.relations
.from(
{
type: "notebook",
id: id
},
"notebook"
)
.selector.sorted(db.settings.getGroupOptions("notebooks"))
.then((notebooks) => {
setNotebooks(notebooks);
});
}, [getTotalNotes, item?.id]);
}, [id]);
useEffect(() => {
if (nestedNotebooks) {
console.log("useNotebook.useEffect.onRequestUpdate");
console.log("useNotebook.useEffect.onRequestUpdate");
onRequestUpdate();
}, [onRequestUpdate]);
const onUpdate = useCallback(
(type: string) => {
if (type !== "notebooks") return;
setGroupOptions({ ...(db.settings.getGroupOptions("notebooks") as any) });
onRequestUpdate();
}
}, [item?.id, onRequestUpdate, nestedNotebooks]);
console.log("useNotebook.onUpdate", id, Date.now());
},
[id, onRequestUpdate]
);
useEffect(() => {
const onNotebookUpdate = (id?: string) => {
if (typeof id === "string" && id !== id) return;
setImmediate(() => {
if (nestedNotebooks) {
onRequestUpdate();
}
onRequestUpdate();
refresh();
});
};
const onUpdate = (type: string) => {
console.log("event", type);
if (type !== "notebooks") return;
onRequestUpdate();
console.log("useNotebook.onUpdate", item?.id, Date.now());
};
eSubscribeEvent(eGroupOptionsUpdated, onUpdate);
eSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
return () => {
eUnSubscribeEvent(eGroupOptionsUpdated, onUpdate);
eUnSubscribeEvent(eOnNotebookUpdated, onNotebookUpdate);
};
}, [onRequestUpdate, item?.id, refresh, nestedNotebooks]);
}, [onUpdate, onRequestUpdate, id, refresh]);
return {
notebook: item,

View File

@@ -132,7 +132,7 @@ const Tabs = React.memo(_Tabs, () => true);
const _NavigationStack = () => {
const clearSelection = useSelectionStore((state) => state.clearSelection);
const loading = useSettingStore((state) => state.isAppLoading);
const loading = useNoteStore((state) => state.loading);
const onStateChange = React.useCallback(() => {
if (useSelectionStore.getState().selectionMode) {
clearSelection(true);

View File

@@ -135,7 +135,7 @@ export const useEditorEvents = (
const deviceMode = useSettingStore((state) => state.deviceMode);
const fullscreen = useSettingStore((state) => state.fullscreen);
const corsProxy = useSettingStore((state) => state.settings.corsProxy);
const loading = useSettingStore((state) => state.isAppLoading);
const loading = useNoteStore((state) => state.loading);
const [dateFormat, timeFormat] = useSettingStore((state) => [
state.dateFormat,
state.timeFormat
@@ -190,7 +190,7 @@ export const useEditorEvents = (
readonly: readonly || editorPropReadonly,
tools: tools || getDefaultPresets().default,
noHeader: noHeader,
noToolbar: noToolbar,
noToolbar: readonly || editorPropReadonly || noToolbar,
doubleSpacedLines: doubleSpacedLines,
corsProxy: corsProxy,
fontSize:
@@ -395,7 +395,7 @@ export const useEditorEvents = (
db.relations
.unlink(editorMessage.value as ItemReference, editor.note.current)
.then(async () => {
useTagStore.getState().refresh();
useTagStore.getState().setTags();
useRelationStore.getState().update();
await editor.commands.setTags(editor.note.current);
Navigation.queueRoutesForUpdate();

View File

@@ -48,7 +48,7 @@ import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { TipManager } from "../../../services/tip-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useNoteStore } from "../../../stores/use-notes-store";
import { useTagStore } from "../../../stores/use-tag-store";
import { eClearEditor, eOnLoadNote } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
@@ -90,7 +90,7 @@ export const useEditor = (
const sessionHistoryId = useRef<number>();
const state = useRef<Partial<EditorState>>(defaultState);
const placeholderTip = useRef(TipManager.placeholderTip());
const tags = useTagStore((state) => state.items);
const tags = useTagStore((state) => state.tags);
const insets = useGlobalSafeAreaInsets();
const isDefaultEditor = editorId === "";
const saveCount = useRef(0);
@@ -653,9 +653,9 @@ export const useEditor = (
tabBarRef.current?.goToPage(1, false);
}
if (appState.note) {
if (useSettingStore.getState().isAppLoading) {
const remove = useSettingStore.subscribe((state) => {
if (!state.isAppLoading && appState.note) {
if (useNoteStore.getState().loading) {
const remove = useNoteStore.subscribe((state) => {
if (!state.loading && appState.note) {
loadNote({
item: appState.note
});

View File

@@ -21,18 +21,21 @@ import React from "react";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import { useFavorites } from "../../stores/use-favorite-store";
import { useFavoriteStore } from "../../stores/use-favorite-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import SelectionHeader from "../../components/selection-header";
export const Favorites = ({
navigation,
route
}: NavigationProps<"Favorites">) => {
const [favorites, loading, refresh] = useFavorites();
const favorites = useFavoriteStore((state) => state.favorites);
const setFavorites = useFavoriteStore((state) => state.setFavorites);
const loading = useNoteStore((state) => state.loading);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
@@ -40,7 +43,7 @@ export const Favorites = ({
Navigation.routeUpdateFunctions[route.name]
);
useNavigationStore.getState().setFocusedRouteId(route?.name);
return false;
return !prev?.current;
},
onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1
@@ -72,10 +75,10 @@ export const Favorites = ({
data={favorites}
dataType="note"
onRefresh={() => {
refresh();
setFavorites();
}}
renderedInRoute="Favorites"
loading={loading}
loading={loading || !isFocused}
placeholder={{
title: "Your favorites",
paragraph: "You have not added any notes to favorites yet.",

View File

@@ -22,16 +22,17 @@ import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotes } from "../../stores/use-notes-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { openEditor } from "../notes/common";
import useNavigationStore from "../../stores/use-navigation-store";
import SelectionHeader from "../../components/selection-header";
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
const [notes, loading] = useNotes();
const notes = useNoteStore((state) => state.notes);
const loading = useNoteStore((state) => state.loading);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
@@ -64,7 +65,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
id={route.name}
onPressDefaultRightButton={openEditor}
/>
<DelayLayout wait={loading}>
<DelayLayout wait={loading} delay={500}>
<List
data={notes}
dataType="note"

View File

@@ -39,23 +39,11 @@ import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { openEditor, setOnFirstSave } from "../notes/common";
import SelectionHeader from "../../components/selection-header";
import Paragraph from "../../components/ui/typography/paragraph";
import { View } from "react-native";
import { SIZE } from "../../utils/size";
import { IconButton } from "../../components/ui/icon-button";
import { PressableButton } from "../../components/ui/pressable";
import { resolveItems } from "../../stores/resolve-items";
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const params = useRef<NotebookScreenParams>(route?.params);
const [loading, setLoading] = useState(true);
const [breadcrumbs, setBreadcrumbs] = useState<
{
id: string;
title: string;
}[]
>([]);
useNavigationFocus(navigation, {
onFocus: () => {
@@ -101,16 +89,13 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const notebook = await db.notebooks?.notebook(
params?.current?.item?.id
);
if (notebook) {
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
setBreadcrumbs(breadcrumbs);
params.current.item = notebook;
const notes = await db.relations
.from(notebook, "note")
.selector.grouped(db.settings.getGroupOptions("notes"));
setNotes(notes);
await notes.item(0, resolveItems);
setNotes(
await db.relations
.from(notebook, "note")
.selector.grouped(db.settings.getGroupOptions("notes"))
);
syncWithNavigation();
}
setLoading(false);
@@ -161,66 +146,13 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
id={params.current.item?.id}
onPressDefaultRightButton={openEditor}
/>
{breadcrumbs ? (
<View
style={{
width: "100%",
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center",
flexWrap: "wrap"
}}
>
<IconButton
name="notebook-outline"
size={16}
customStyle={{ width: 20, height: 25 }}
onPress={() => {
Navigation.push("Notebooks", {
canGoBack: true
});
}}
/>
{breadcrumbs.map((item) => (
<PressableButton
onPress={async () => {
const notebook = await db.notebooks.notebook(item.id);
if (!notebook) return;
NotebookScreen.navigate(notebook, true);
}}
key={item.id}
customStyle={{
width: undefined,
flexDirection: "row",
paddingHorizontal: 0,
alignItems: "center"
}}
>
<IconButton
name="chevron-right"
size={16}
top={0}
left={0}
right={0}
bottom={0}
customStyle={{ width: 20, height: 25 }}
/>
<Paragraph size={SIZE.xs + 1}>{item.title}</Paragraph>
</PressableButton>
))}
</View>
) : null}
<DelayLayout wait={loading}>
<DelayLayout>
<List
data={notes}
dataType="note"
onRefresh={() => {
onRequestUpdate();
}}
id={params.current.item?.id}
renderedInRoute="Notebook"
headerTitle={params.current.title}
loading={loading}

View File

@@ -23,14 +23,14 @@ import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { Walkthrough } from "../../components/walkthroughs";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotebooks } from "../../stores/use-notebook-store";
import { useNotebookStore } from "../../stores/use-notebook-store";
import SelectionHeader from "../../components/selection-header";
const onButtonPress = () => {
AddNotebookSheet.present();
@@ -40,8 +40,7 @@ export const Notebooks = ({
navigation,
route
}: NavigationProps<"Notebooks">) => {
const [notebooks, loading] = useNotebooks();
const notebooks = useNotebookStore((state) => state.notebooks);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
@@ -49,7 +48,7 @@ export const Notebooks = ({
Navigation.routeUpdateFunctions[route.name]
);
useNavigationStore.getState().setFocusedRouteId(route.name);
return false;
return !prev?.current;
},
onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1
@@ -84,11 +83,12 @@ export const Notebooks = ({
}}
onPressDefaultRightButton={onButtonPress}
/>
<DelayLayout wait={loading}>
<DelayLayout delay={1}>
<List
data={notebooks}
dataType="notebook"
renderedInRoute="Notebooks"
loading={!isFocused}
placeholder={{
title: "Your notebooks",
paragraph: "You have not added any notebooks yet.",

View File

@@ -107,7 +107,7 @@ export async function onNoteCreated(noteId: string, data: FirstSaveData) {
}
editorState().onNoteCreated = null;
useTagStore.getState().refresh();
useTagStore.getState().setTags();
useRelationStore.getState().update();
break;
}

View File

@@ -38,7 +38,6 @@ import useNavigationStore, {
} from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { setOnFirstSave } from "./common";
import { resolveItems } from "../../stores/resolve-items";
export const WARNING_DATA = {
title: "Some notes in this topic are not synced"
};
@@ -75,6 +74,7 @@ const NotesPage = ({
>) => {
const params = useRef<NotesScreenParams>(route?.params);
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const loading = useNoteStore((state) => state.loading);
const [loadingNotes, setLoadingNotes] = useState(true);
const isMonograph = route.name === "Monographs";
const title =
@@ -119,6 +119,7 @@ const NotesPage = ({
async (data?: NotesScreenParams) => {
const isNew = data && data?.item?.id !== params.current?.item?.id;
if (data) params.current = data;
const { item } = params.current;
try {
if (isNew) setLoadingNotes(true);
const notes = (await get(
@@ -126,10 +127,14 @@ const NotesPage = ({
true
)) as VirtualizedGrouping<Note>;
if (
(item.type === "tag" || item.type === "color") &&
(!notes || notes.ids.length === 0)
) {
return Navigation.goBack();
}
if (notes.ids.length === 0) setLoadingNotes(false);
setNotes(notes);
await notes.item(0, resolveItems);
setLoadingNotes(false);
syncWithNavigation();
} catch (e) {
console.error(e);
@@ -139,11 +144,10 @@ const NotesPage = ({
);
useEffect(() => {
if (loadingNotes) {
if (loadingNotes && !loading) {
get(params.current, true)
.then(async (items) => {
.then((items) => {
setNotes(items as VirtualizedGrouping<Note>);
await (items as VirtualizedGrouping<Note>).item(0, resolveItems);
setLoadingNotes(false);
})
.catch((e) => {
@@ -151,7 +155,7 @@ const NotesPage = ({
setLoadingNotes(false);
});
}
}, [loadingNotes, get]);
}, [loadingNotes, loading, get]);
useEffect(() => {
eSubscribeEvent(route.name, onRequestUpdate);
@@ -185,14 +189,13 @@ const NotesPage = ({
headerRightButtons={rightButtons?.(params?.current)}
/>
<DelayLayout color={accentColor} wait={loadingNotes}>
<DelayLayout color={accentColor} wait={loading || loadingNotes}>
<List
data={notes}
dataType="note"
onRefresh={onRequestUpdate}
loading={!isFocused}
loading={loading || !isFocused}
renderedInRoute={route.name}
id={params.current.item?.id}
headerTitle={title}
customAccentColor={accentColor}
placeholder={placeholder}

View File

@@ -22,19 +22,19 @@ import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import ReminderSheet from "../../components/sheets/reminder";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useReminders } from "../../stores/use-reminder-store";
import { useReminderStore } from "../../stores/use-reminder-store";
import SelectionHeader from "../../components/selection-header";
export const Reminders = ({
navigation,
route
}: NavigationProps<"Reminders">) => {
const [reminders, loading] = useReminders();
const reminders = useReminderStore((state) => state.reminders);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
@@ -43,7 +43,7 @@ export const Reminders = ({
);
useNavigationStore.getState().setFocusedRouteId(route.name);
return false;
return !prev?.current;
},
onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1
@@ -72,13 +72,13 @@ export const Reminders = ({
}}
/>
<DelayLayout wait={loading}>
<DelayLayout>
<List
data={reminders}
dataType="reminder"
headerTitle="Reminders"
renderedInRoute="Reminders"
loading={loading}
loading={!isFocused}
placeholder={{
title: "Your reminders",
paragraph: "You have not set any reminders yet.",

View File

@@ -22,6 +22,7 @@ import create, { State } from "zustand";
import { persist, StateStorage } from "zustand/middleware";
import { db } from "../../../common/database";
import { MMKV } from "../../../common/database/mmkv";
import { useNoteStore } from "../../../stores/use-notes-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { presets } from "./toolbar-definition";
export type ToolDefinition = string | string[];
@@ -123,7 +124,7 @@ export const useDragState = create<DragState>(
getStorage: () => MMKV as unknown as StateStorage,
onRehydrateStorage: () => {
return () => {
if (!useSettingStore.getState().isAppLoading) {
if (!useNoteStore.getState().loading) {
useDragState.getState().init();
} else {
setTimeout(() => {

View File

@@ -57,10 +57,10 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
return;
}
if (!item.property) return;
item.onChange?.(!settings[item.property]);
SettingsService.set({
[item.property]: !settings[item.property]
});
item.onChange?.(!settings[item.property]);
};
const styles =

View File

@@ -68,6 +68,7 @@ import { useDragState } from "./editor/state";
import { verifyUser } from "./functions";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import { BackgroundSync } from "../../services/background-sync";
type User = any;
export const settingsGroups: SettingSection[] = [
@@ -297,7 +298,6 @@ export const settingsGroups: SettingSection[] = [
await BiometicService.resetCredentials();
MMKV.clearStore();
clearAllStores();
Navigation.queueRoutesForUpdate();
SettingsService.resetSettings();
useUserStore.getState().setUser(null);
useUserStore.getState().setSyncing(false);
@@ -305,7 +305,7 @@ export const settingsGroups: SettingSection[] = [
Navigation.popToTop();
setTimeout(() => {
eSendEvent("settings-loading", false);
}, 3000);
}, 2000);
}, 300);
} catch (e) {
ToastManager.error(e as Error, "Error logging out");
@@ -398,6 +398,21 @@ export const settingsGroups: SettingSection[] = [
type: "switch",
property: "disableSync"
},
{
id: "background-sync",
name: "Background sync (Experimental)",
description:
"Periodically wake up the app in background to sync your notes from other devices.",
type: "switch",
property: "backgroundSync",
onChange: (value) => {
if (value) {
BackgroundSync.start();
} else {
BackgroundSync.stop();
}
}
},
{
id: "sync-issues-fix",
name: "Having problems with sync",

View File

@@ -21,15 +21,16 @@ import React from "react";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import SelectionHeader from "../../components/selection-header";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useTags } from "../../stores/use-tag-store";
import { useTagStore } from "../../stores/use-tag-store";
import { db } from "../../common/database";
import SelectionHeader from "../../components/selection-header";
export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
const [tags, loading] = useTags();
const tags = useTagStore((state) => state.tags);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(
@@ -60,7 +61,7 @@ export const Tags = ({ navigation, route }: NavigationProps<"Tags">) => {
});
}}
/>
<DelayLayout wait={loading}>
<DelayLayout>
<List
data={tags}
dataType="tag"

View File

@@ -29,7 +29,7 @@ import { ToastManager } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useTrash, useTrashStore } from "../../stores/use-trash-store";
import { useTrashStore } from "../../stores/use-trash-store";
import SelectionHeader from "../../components/selection-header";
const onPressFloatingButton = () => {
@@ -40,7 +40,7 @@ const onPressFloatingButton = () => {
negativeText: "Cancel",
positivePress: async () => {
await db.trash?.clear();
useTrashStore.getState().refresh();
useTrashStore.getState().setTrash();
useSelectionStore.getState().clearSelection();
ToastManager.show({
heading: "Trash cleared",
@@ -63,7 +63,7 @@ const PLACEHOLDER_DATA = (trashCleanupInterval = 7) => ({
});
export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
const [trash, loading] = useTrash();
const trash = useTrashStore((state) => state.trash);
const isFocused = useNavigationFocus(navigation, {
onFocus: () => {
Navigation.routeNeedsUpdate(
@@ -71,6 +71,12 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
Navigation.routeUpdateFunctions[route.name]
);
useNavigationStore.getState().setFocusedRouteId(route.name);
if (
!useTrashStore.getState().trash ||
useTrashStore.getState().trash?.ids?.length === 0
) {
useTrashStore.getState().setTrash();
}
return false;
},
onBlur: () => false
@@ -93,7 +99,7 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
});
}}
/>
<DelayLayout wait={loading}>
<DelayLayout>
<List
data={trash}
dataType="trash"

View File

@@ -21,6 +21,11 @@ import {
beginBackgroundTask,
endBackgroundTask
} from "react-native-begin-background-task";
import BackgroundFetch from "@ammarahmed/react-native-background-fetch";
import { DatabaseLogger, db } from "../common/database";
import { AppState, AppRegistry } from "react-native";
import Notifications from "./notifications";
import SettingsService from "./settings";
async function doInBackground(callback: () => Promise<void>) {
if (Platform.OS === "ios") {
@@ -45,107 +50,120 @@ async function doInBackground(callback: () => Promise<void>) {
}
}
// import BackgroundFetch from "react-native-background-fetch";
// import { DatabaseLogger, db } from "../common/database";
// import { AppState, AppRegistry } from "react-native";
// import Notifications from "./notifications";
// import SettingsService from "./settings";
let backgroundFetchStarted = false;
async function start() {
if (backgroundFetchStarted) return;
backgroundFetchStarted = true;
if (!SettingsService.getProperty("backgroundSync")) {
return;
}
// BackgroundFetch event handler.
const onEvent = async (taskId: string) => {
DatabaseLogger.info(
`BACKGROUND FETCH ON EVENT ${taskId}, ${AppState.currentState}}`
);
// Do your background work...
await onBackgroundSyncStarted();
// IMPORTANT: You must signal to the OS that your task is complete.
BackgroundFetch.finish(taskId);
};
// let backgroundFetchStarted = false;
// async function start() {
// if (backgroundFetchStarted) return;
// backgroundFetchStarted = true;
// // BackgroundFetch event handler.
// const onEvent = async (taskId: string) => {
// console.log("[BackgroundFetch] task: ", taskId, AppState.currentState);
// // Do your background work...
// await onBackgroundSyncStarted();
// // IMPORTANT: You must signal to the OS that your task is complete.
// BackgroundFetch.finish(taskId);
// };
// Timeout callback is executed when your Task has exceeded its allowed running-time.
// You must stop what you're doing immediately BackgroundFetch.finish(taskId)
const onTimeout = async (taskId: string) => {
DatabaseLogger.info(`BACKGROUND FETCH TIMEOUT: ${taskId}`);
BackgroundFetch.finish(taskId);
};
// // Timeout callback is executed when your Task has exceeded its allowed running-time.
// // You must stop what you're doing immediately BackgroundFetch.finish(taskId)
// const onTimeout = async (taskId: string) => {
// console.warn("[BackgroundFetch] TIMEOUT: ", taskId);
// BackgroundFetch.finish(taskId);
// };
// Initialize BackgroundFetch only once when component mounts.
const status = await BackgroundFetch.configure(
{
minimumFetchInterval: 15,
enableHeadless: true,
startOnBoot: true,
stopOnTerminate: false,
requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY
},
onEvent,
onTimeout
);
// // Initialize BackgroundFetch only once when component mounts.
// const status = await BackgroundFetch.configure(
// {
// minimumFetchInterval: 15,
// enableHeadless: true,
// startOnBoot: true,
// stopOnTerminate: false,
// requiredNetworkType: BackgroundFetch.NETWORK_TYPE_ANY
// },
// onEvent,
// onTimeout
// );
// DatabaseLogger.info(`[BackgroundFetch] configure status: ${status}`);
// console.log(`[BackgroundFetch] configure status: ${status}`);
// }
DatabaseLogger.info(`[BackgroundFetch] configure status: ${status}`);
}
// const task = async (event: { taskId: string; timeout: boolean }) => {
// // Get task id from event {}:
// const taskId = event.taskId;
// const isTimeout = event.timeout; // <-- true when your background-time has expired.
// if (isTimeout) {
// console.log("[BackgroundFetch] Headless TIMEOUT:", taskId);
// BackgroundFetch.finish(taskId);
// return;
// }
// DatabaseLogger.info(
// "[BackgroundFetch HeadlessTask] start: " + taskId + AppState.currentState
// );
// await onBackgroundSyncStarted();
// BackgroundFetch.finish(taskId);
// };
const task = async (event: { taskId: string; timeout: boolean }) => {
// Get task id from event {}:
const taskId = event.taskId;
const isTimeout = event.timeout; // <-- true when your background-time has expired.
if (isTimeout) {
console.log(`BACKGROUND FETCH TIMEOUT: ${taskId}`);
BackgroundFetch.finish(taskId);
return;
}
DatabaseLogger.info("BACKGROUND SYNC START" + taskId + AppState.currentState);
await onBackgroundSyncStarted();
BackgroundFetch.finish(taskId);
};
// BackgroundFetch.registerHeadlessTask(task);
BackgroundFetch.registerHeadlessTask(task);
// async function onBackgroundSyncStarted() {
// try {
// DatabaseLogger.info("Background Sync" + "start");
// await db.init();
// const user = await db.user?.getUser();
// if (user) {
// await db.sync(true, false);
// }
// await Notifications.setupReminders();
// DatabaseLogger.info("Background Sync" + "end");
// } catch (e) {
// DatabaseLogger.error(e as Error);
// console.log("Background Sync Error", (e as Error).message);
// }
// }
async function onBackgroundSyncStarted() {
try {
if (!db.isInitialized) {
await db.init();
} else {
await db.initCollections();
}
// const onBoot = async () => {
// try {
// DatabaseLogger.info("BOOT TASK STARTED");
// await db.init();
// await Notifications.setupReminders();
// SettingsService.init();
// if (SettingsService.get().notifNotes) {
// Notifications.pinQuickNote(false);
// }
// DatabaseLogger.info("BOOT TASK COMPLETE");
// } catch (e) {
// console.log(e);
// }
// };
const user = await db.user?.getUser();
if (user) {
await db.sync(true, false);
}
await Notifications.setupReminders();
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
} catch (e) {
DatabaseLogger.error(e as Error);
console.log("BACKGROUND SYNC ERROR", (e as Error).message);
}
}
// const registerHeadlessTask = () =>
// AppRegistry.registerHeadlessTask(
// "com.streetwriters.notesnook.BOOT_TASK",
// () => {
// return onBoot;
// }
// );
const onBoot = async () => {
try {
if (!SettingsService.getProperty("backgroundSync")) {
return;
}
DatabaseLogger.info("BOOT TASK STARTED");
if (!db.isInitialized) {
await db.init();
} else {
await db.initCollections();
}
await Notifications.setupReminders();
SettingsService.init();
if (SettingsService.get().notifNotes) {
Notifications.pinQuickNote(false);
}
DatabaseLogger.info("BOOT TASK COMPLETE");
} catch (e) {
console.log(e);
}
};
const registerHeadlessTask = () =>
AppRegistry.registerHeadlessTask(
"com.streetwriters.notesnook.BOOT_TASK",
() => {
return onBoot;
}
);
export const BackgroundSync = {
start,
registerHeadlessTask,
stop: () => {
BackgroundFetch.stop();
}
};
// export const BackgroundSync = {
// start,
// registerHeadlessTask
// };
export default { doInBackground };

View File

@@ -62,7 +62,6 @@ export const eSubscribeEvent = <T = unknown>(
eventName: string,
action?: (data: T) => void
) => {
if (!action) return;
return eventManager.subscribe(eventName, action);
};
@@ -70,7 +69,6 @@ export const eUnSubscribeEvent = <T = unknown>(
eventName: string,
action?: (data: T) => void
) => {
if (!action) return;
eventManager.unsubscribe(eventName, action);
};
@@ -131,7 +129,7 @@ export function hideSheet() {
export type ToastOptions = {
heading?: string;
message?: string;
context?: any;
context?: "global" | "local";
type?: "error" | "success" | "info";
duration?: number;
func?: () => void;
@@ -159,7 +157,7 @@ export const ToastManager = {
});
},
hide: () => eSendEvent(eHideToast),
error: (e: Error, title?: string, context?: any) => {
error: (e: Error, title?: string, context?: "global" | "local") => {
ToastManager.show({
heading: title,
message: e?.message || "",

View File

@@ -73,18 +73,18 @@ export type NavigationProps<T extends RouteName> = NativeStackScreenProps<
const routeUpdateFunctions: {
[name: string]: (...params: GenericRouteParam[]) => void;
} = {
Notes: () => useNoteStore.getState().refresh(),
Notebooks: () => useNotebookStore.getState().refresh(),
Tags: () => useTagStore.getState().refresh(),
Favorites: () => useFavoriteStore.getState().refresh(),
Trash: () => useTrashStore.getState().refresh(),
Notes: () => useNoteStore.getState().setNotes(),
Notebooks: () => useNotebookStore.getState().setNotebooks(),
Tags: () => useTagStore.getState().setTags(),
Favorites: () => useFavoriteStore.getState().setFavorites(),
Trash: () => useTrashStore.getState().setTrash(),
Notebook: (params) => eSendEvent(eUpdateNotebookRoute, params),
NotesPage: (params) => eSendEvent("NotesPage", params),
TaggedNotes: (params) => eSendEvent("TaggedNotes", params),
ColoredNotes: (params) => eSendEvent("ColoredNotes", params),
TopicNotes: (params) => eSendEvent("TopicNotes", params),
Monographs: (params) => eSendEvent("Monographs", params),
Reminders: () => useReminderStore.getState().refresh(),
Reminders: () => useReminderStore.getState().setReminders(),
Search: () => eSendEvent(eOnRefreshSearch)
};

View File

@@ -17,38 +17,38 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Reminder } from "@notesnook/core/dist/types";
import notifee, {
AndroidStyle,
AuthorizationStatus,
DisplayedNotification,
Event,
EventType,
RepeatFrequency,
TimestampTrigger,
Trigger,
TriggerType,
Event,
TriggerNotification,
TriggerType
TimestampTrigger
} from "@notifee/react-native";
import NetInfo from "@react-native-community/netinfo";
import dayjs, { Dayjs } from "dayjs";
import { encodeNonAsciiHTML } from "entities";
import { Platform } from "react-native";
import { db } from "../common/database";
import { MMKV } from "../common/database/mmkv";
import { presentDialog } from "../components/dialog/functions";
import { editorState } from "../screens/editor/tiptap/utils";
import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { useSettingStore } from "../stores/use-setting-store";
import { useNoteStore } from "../stores/use-notes-store";
import { eOnLoadNote } from "../utils/events";
import { tabBarRef } from "../utils/global-refs";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
import { DDS } from "./device-detection";
import { eSendEvent } from "./event-manager";
import Navigation from "./navigation";
import SettingsService from "./settings";
import { useSettingStore } from "../stores/use-setting-store";
import { sleep } from "../utils/time";
import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { presentDialog } from "../components/dialog/functions";
import NetInfo from "@react-native-community/netinfo";
import { encodeNonAsciiHTML } from "entities";
import { convertNoteToText } from "../utils/note-to-text";
import { Reminder } from "@notesnook/core/dist/types";
let pinned: DisplayedNotification[] = [];
@@ -136,7 +136,19 @@ const onEvent = async ({ type, detail }: Event) => {
}
editorState().movedAway = false;
const noteId = notification?.id;
loadNote(noteId as string, true);
if (useNoteStore?.getState()?.loading === false) {
loadNote(noteId as string, false);
return;
}
const unsub = useNoteStore.subscribe(
(loading) => {
if (loading === false) {
loadNote(noteId as string, true);
}
unsub();
},
(state) => state.loading
);
}
if (type === EventType.ACTION_PRESS) {
@@ -161,7 +173,7 @@ const onEvent = async ({ type, detail }: Event) => {
await db.reminders?.reminder(reminder?.id)
);
useRelationStore.getState().update();
useReminderStore.getState().refresh();
useReminderStore.getState().setReminders();
break;
}
case "REMINDER_DISABLE": {
@@ -179,7 +191,7 @@ const onEvent = async ({ type, detail }: Event) => {
await db.reminders?.reminder(reminder?.id)
);
useRelationStore.getState().update();
useReminderStore.getState().refresh();
useReminderStore.getState().setReminders();
break;
}
case "UNPIN": {
@@ -195,7 +207,7 @@ const onEvent = async ({ type, detail }: Event) => {
disabled: true
});
useRelationStore.getState().update();
useReminderStore.getState().refresh();
useReminderStore.getState().setReminders();
}
break;
}
@@ -241,7 +253,7 @@ const onEvent = async ({ type, detail }: Event) => {
console.log(e, (e as Error).stack);
}
}
Navigation.queueRoutesForUpdate();
useNoteStore.getState().setNotes();
break;
}
}
@@ -352,18 +364,17 @@ async function scheduleNotification(
}
});
}
await notifee.createTriggerNotification(
{
id: trigger.id,
title: !title ? undefined : title,
body: !description ? undefined : description,
title: title,
body: description,
data: {
type: "reminder",
payload: payload || "",
dateModified: reminder.dateModified + ""
},
subtitle: !description ? undefined : description,
subtitle: description,
android: {
channelId: await getChannelId(priority),
smallIcon: "ic_stat_name",
@@ -410,17 +421,14 @@ async function loadNote(id: string, jump: boolean) {
tabBarRef.current?.goToPage(1);
}
eSendEvent("loadingNote", note);
setTimeout(
() => {
eSendEvent(eOnLoadNote, {
item: note
});
if (!jump && !DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
},
tabBarRef?.current ? 0 : 2000
);
setTimeout(() => {
eSendEvent(eOnLoadNote, {
item: note
});
if (!jump && !DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
}, 2000);
}
async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") {

View File

@@ -303,9 +303,7 @@ const subscriptions = {
console.log("Subscription.verify", requestData);
try {
let result = await fetch(
__DEV__
? "http://192.168.43.5:4264/apple/verify"
: "https://payments.streetwriters.co/apple/verify",
"http://192.168.43.5:4264/apple/verify",
requestData
);
console.log("Subscribed", result);

View File

@@ -1,93 +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 { Item, VirtualizedGrouping } from "@notesnook/core";
import create, { State, StoreApi, UseBoundStore } from "zustand";
import { resolveItems } from "./resolve-items";
import { useSettingStore } from "./use-setting-store";
export interface DBCollectionStore<Type extends Item> extends State {
items: VirtualizedGrouping<Type> | undefined;
loading: boolean;
refresh: () => Promise<void>;
clear: () => void;
}
const ALL_STORES: UseBoundStore<
DBCollectionStore<Item>,
StoreApi<DBCollectionStore<Item>>
>[] = [];
export function clearAllStores() {
ALL_STORES.forEach((store) => {
store?.getState().clear();
});
}
export function refreshAllStores() {
ALL_STORES.forEach((store) => store?.getState().refresh());
}
export default function createDBCollectionStore<Type extends Item>({
getCollection,
eagerlyFetchFirstBatch
}: {
getCollection: () => Promise<VirtualizedGrouping<Type>>;
eagerlyFetchFirstBatch?: boolean;
}) {
const useDBCollectionStore = create<DBCollectionStore<Type>>((set, get) => ({
items: undefined,
loading: true,
refresh: async () => {
const items = await getCollection();
if (get().loading && eagerlyFetchFirstBatch) {
await items.item(0, resolveItems);
}
set({
items,
loading: false
});
},
clear: () => set({ items: undefined, loading: true })
}));
const useStoreHook = (): [
VirtualizedGrouping<Type> | undefined,
boolean,
() => Promise<void>
] => {
const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [items, loading] = useDBCollectionStore((state) => [
state.items,
state.loading
]);
if (!items && !isAppLoading) {
useDBCollectionStore.getState().refresh();
}
return [items, loading, useDBCollectionStore.getState().refresh];
};
ALL_STORES.push(useDBCollectionStore as any);
return {
useStore: useDBCollectionStore,
useCollection: useStoreHook
};
}

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { db } from "../common/database";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
import { useFavoriteStore } from "./use-favorite-store";
@@ -27,26 +28,36 @@ import { useRelationStore } from "./use-relation-store";
import { useReminderStore } from "./use-reminder-store";
import { useTagStore } from "./use-tag-store";
import { useTrashStore } from "./use-trash-store";
export function initAfterSync() {
useMenuStore.getState().setColorNotes();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate();
// Whenever sync completes, try to reschedule
// any new/updated reminders.
Notifications.setupReminders(true);
useRelationStore.getState().update();
}
export async function initialize() {
if (!db) return;
useMenuStore.getState().setColorNotes();
useMenuStore.getState().setMenuPins();
useNotebookStore.getState().setNotebooks();
useTrashStore.getState().setTrash();
useTagStore.getState().setTags();
useFavoriteStore.getState().setFavorites();
await useNoteStore.getState().setNotes();
useReminderStore.getState().setReminders();
Notifications.setupReminders();
}
export async function initialize() {}
export function clearAllStores() {
useNotebookStore.getState().clear();
useTagStore.getState().clear();
useFavoriteStore.getState().clear();
useNotebookStore.getState().clearNotebooks();
useTagStore.getState().clearTags();
useFavoriteStore.getState().clearFavorites();
useMenuStore.getState().clearAll();
useNoteStore.getState().clear();
useNoteStore.getState().clearNotes();
useMenuStore.getState().clearAll();
useTrashStore.getState().clear();
useReminderStore.getState().clear();
useTrashStore.getState().clearTrash();
useReminderStore.getState().cleareReminders();
}

View File

@@ -1,158 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Color, Item, Notebook, Reminder, Tag } from "@notesnook/core";
import { db } from "../common/database";
export type WithDateEdited<T> = { items: T[]; dateEdited: number };
export type NotebooksWithDateEdited = WithDateEdited<Notebook>;
export type TagsWithDateEdited = WithDateEdited<Tag>;
function withDateEdited<
T extends { dateEdited: number } | { dateModified: number }
>(items: T[]): WithDateEdited<T> {
let latestDateEdited = 0;
items.forEach((item) => {
const date = "dateEdited" in item ? item.dateEdited : item.dateModified;
if (latestDateEdited < date) latestDateEdited = date;
});
return { dateEdited: latestDateEdited, items };
}
export async function resolveItems(ids: string[], items: Item[]) {
if (!ids.length || !items.length) return [];
const { type } = items[0];
if (type === "note") return resolveNotes(ids);
else if (type === "notebook") {
return Promise.all(ids.map((id) => db.notebooks.totalNotes(id)));
} else if (type === "tag") {
return Promise.all(
ids.map((id) => db.relations.from({ id, type: "tag" }, "note").count())
);
}
return [];
}
type NoteResolvedData = {
notebooks?: NotebooksWithDateEdited;
reminder?: Reminder;
color?: Color;
tags?: TagsWithDateEdited;
attachmentsCount?: number;
};
async function resolveNotes(ids: string[]) {
console.time("relations");
const relations = [
...(await db.relations
.to({ type: "note", ids }, ["notebook", "tag", "color"])
.get()),
...(await db.relations.from({ type: "note", ids }, "reminder").get())
];
console.timeEnd("relations");
const relationIds: {
notebooks: Set<string>;
colors: Set<string>;
tags: Set<string>;
reminders: Set<string>;
} = {
colors: new Set(),
notebooks: new Set(),
tags: new Set(),
reminders: new Set()
};
const grouped: Record<
string,
{
notebooks: string[];
color?: string;
tags: string[];
reminder?: string;
}
> = {};
for (const relation of relations) {
const noteId =
relation.toType === "reminder" ? relation.fromId : relation.toId;
const data = grouped[noteId] || {
notebooks: [],
tags: []
};
if (relation.toType === "reminder" && !data.reminder) {
data.reminder = relation.toId;
relationIds.reminders.add(relation.toId);
} else if (relation.fromType === "notebook" && data.notebooks.length < 2) {
data.notebooks.push(relation.fromId);
relationIds.notebooks.add(relation.fromId);
} else if (relation.fromType === "tag" && data.tags.length < 3) {
data.tags.push(relation.fromId);
relationIds.tags.add(relation.fromId);
} else if (relation.fromType === "color" && !data.color) {
data.color = relation.fromId;
relationIds.colors.add(relation.fromId);
}
grouped[noteId] = data;
}
console.time("resolve");
const resolved = {
notebooks: await db.notebooks.all.records(
Array.from(relationIds.notebooks)
),
tags: await db.tags.all.records(Array.from(relationIds.tags)),
colors: await db.colors.all.records(Array.from(relationIds.colors)),
reminders: await db.reminders.all.records(Array.from(relationIds.reminders))
};
console.timeEnd("resolve");
const data: NoteResolvedData[] = [];
for (const noteId of ids) {
const group = grouped[noteId];
if (!group) {
data.push({});
continue;
}
data.push({
color: group.color ? resolved.colors[group.color] : undefined,
reminder: group.reminder ? resolved.reminders[group.reminder] : undefined,
tags: withDateEdited(
group.tags.map((id) => resolved.tags[id]).filter(Boolean)
),
notebooks: withDateEdited(
group.notebooks.map((id) => resolved.notebooks[id]).filter(Boolean)
),
attachmentsCount:
(await db.attachments?.ofNote(noteId, "all").ids())?.length || 0
});
}
return data;
}
export function isNoteResolvedData(data: unknown): data is NoteResolvedData {
return (
typeof data === "object" &&
!!data &&
"notebooks" in data &&
"reminder" in data &&
"color" in data &&
"tags" in data
);
}

View File

@@ -17,14 +17,26 @@ 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 create, { State } from "zustand";
import { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
import { Note, VirtualizedGrouping } from "@notesnook/core";
const { useStore: useFavoriteStore, useCollection: useFavorites } =
createDBCollectionStore({
getCollection: () =>
db.notes.favorites.grouped(db.settings.getGroupOptions("favorites")),
eagerlyFetchFirstBatch: true
});
export interface FavoriteStore extends State {
favorites: VirtualizedGrouping<Note> | undefined;
setFavorites: (items?: Note[]) => void;
clearFavorites: () => void;
}
export { useFavoriteStore, useFavorites };
export const useFavoriteStore = create<FavoriteStore>((set) => ({
favorites: undefined,
setFavorites: () => {
db.notes.favorites
.grouped(db.settings.getGroupOptions("favorites"))
.then((notes) => {
set({
favorites: notes
});
});
},
clearFavorites: () => set({ favorites: undefined })
}));

View File

@@ -27,25 +27,20 @@ export interface MenuStore extends State {
setMenuPins: () => void;
setColorNotes: () => void;
clearAll: () => void;
loadingShortcuts: boolean;
loadingColors: boolean;
}
export const useMenuStore = create<MenuStore>((set) => ({
menuPins: [],
colorNotes: [],
loadingShortcuts: true,
loadingColors: true,
setMenuPins: () => {
db.shortcuts.resolved().then((shortcuts) => {
set({ menuPins: [...(shortcuts as [])], loadingShortcuts: false });
set({ menuPins: [...(shortcuts as [])] });
});
},
setColorNotes: () => {
db.colors?.all.items().then((colors) => {
set({
colorNotes: colors,
loadingColors: false
colorNotes: colors
});
});
},

View File

@@ -17,14 +17,26 @@ 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 create, { State } from "zustand";
import { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
import { VirtualizedGrouping, Notebook } from "@notesnook/core";
const { useStore: useNotebookStore, useCollection: useNotebooks } =
createDBCollectionStore({
getCollection: () =>
db.notebooks.roots.grouped(db.settings.getGroupOptions("notebooks")),
eagerlyFetchFirstBatch: true
});
export interface NotebookStore extends State {
notebooks: VirtualizedGrouping<Notebook> | undefined;
setNotebooks: (items?: Notebook[]) => void;
clearNotebooks: () => void;
}
export { useNotebookStore, useNotebooks };
export const useNotebookStore = create<NotebookStore>((set) => ({
notebooks: undefined,
setNotebooks: () => {
db.notebooks.roots
.grouped(db.settings.getGroupOptions("notebooks"))
.then((notebooks) => {
set({
notebooks: notebooks
});
});
},
clearNotebooks: () => set({ notebooks: undefined })
}));

View File

@@ -17,14 +17,28 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Note, VirtualizedGrouping } from "@notesnook/core";
import create, { State } from "zustand";
import { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
const { useStore: useNoteStore, useCollection: useNotes } =
createDBCollectionStore({
getCollection: () =>
db.notes.all.grouped(db.settings.getGroupOptions("home")),
eagerlyFetchFirstBatch: true
});
export interface NoteStore extends State {
notes: VirtualizedGrouping<Note> | undefined;
loading: boolean;
setLoading: (loading: boolean) => void;
setNotes: () => void;
clearNotes: () => void;
}
export { useNoteStore, useNotes };
export const useNoteStore = create<NoteStore>((set) => ({
notes: undefined,
loading: true,
setLoading: (loading) => set({ loading: loading }),
setNotes: () => {
db.notes.all.grouped(db.settings.getGroupOptions("home")).then((notes) => {
set({
notes: notes
});
});
},
clearNotes: () => set({ notes: undefined })
}));

View File

@@ -17,14 +17,26 @@ 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 create, { State } from "zustand";
import { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
import { Reminder, VirtualizedGrouping } from "@notesnook/core";
const { useStore: useReminderStore, useCollection: useReminders } =
createDBCollectionStore({
getCollection: () =>
db.reminders.all.grouped(db.settings.getGroupOptions("reminders")),
eagerlyFetchFirstBatch: true
});
export interface ReminderStore extends State {
reminders: VirtualizedGrouping<Reminder> | undefined;
setReminders: (items?: Reminder[]) => void;
cleareReminders: () => void;
}
export { useReminderStore, useReminders };
export const useReminderStore = create<ReminderStore>((set) => ({
reminders: undefined,
setReminders: () => {
db.reminders.all
.grouped(db.settings.getGroupOptions("reminders"))
.then((reminders) => {
set({
reminders: reminders
});
});
},
cleareReminders: () => set({ reminders: undefined })
}));

View File

@@ -74,6 +74,7 @@ export type Settings = {
colorScheme: "dark" | "light";
lighTheme: ThemeDefinition;
darkTheme: ThemeDefinition;
backgroundSync?: boolean;
};
type DimensionsType = {
@@ -97,8 +98,8 @@ export interface SettingStore extends State {
setFullscreen: (fullscreen: boolean) => void;
setDeviceMode: (mode: string) => void;
setDimensions: (dimensions: DimensionsType) => void;
isAppLoading: boolean;
setAppLoading: (isAppLoading: boolean) => void;
appLoading: boolean;
setAppLoading: (appLoading: boolean) => void;
setSheetKeyboardHandler: (sheetKeyboardHandler: boolean) => void;
sheetKeyboardHandler: boolean;
requestBiometrics: boolean;
@@ -163,12 +164,12 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
fullscreen: false,
deviceMode: null,
dimensions: { width, height },
isAppLoading: true,
appLoading: true,
setSettings: (settings) => set({ settings }),
setFullscreen: (fullscreen) => set({ fullscreen }),
setDeviceMode: (mode) => set({ deviceMode: mode }),
setDimensions: (dimensions) => set({ dimensions: dimensions }),
setAppLoading: (isAppLoading) => set({ isAppLoading }),
setAppLoading: (appLoading) => set({ appLoading }),
setSheetKeyboardHandler: (sheetKeyboardHandler) =>
set({ sheetKeyboardHandler }),
requestBiometrics: false,

View File

@@ -17,14 +17,24 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import create, { State } from "zustand";
import { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
import { Tag, VirtualizedGrouping } from "@notesnook/core";
const { useStore: useTagStore, useCollection: useTags } =
createDBCollectionStore({
getCollection: () =>
db.tags.all.grouped(db.settings.getGroupOptions("tags")),
eagerlyFetchFirstBatch: true
});
export interface TagStore extends State {
tags: VirtualizedGrouping<Tag> | undefined;
setTags: (items?: Tag[]) => void;
clearTags: () => void;
}
export { useTagStore, useTags };
export const useTagStore = create<TagStore>((set) => ({
tags: undefined,
setTags: () => {
db.tags.all.grouped(db.settings.getGroupOptions("tags")).then((tags) => {
set({
tags: tags
});
});
},
clearTags: () => set({ tags: undefined })
}));

View File

@@ -17,13 +17,25 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import create, { State } from "zustand";
import { db } from "../common/database";
import createDBCollectionStore from "./create-db-collection-store";
import { GroupedItems, TrashItem } from "@notesnook/core/dist/types";
import { VirtualizedGrouping } from "@notesnook/core";
const { useStore: useTrashStore, useCollection: useTrash } =
createDBCollectionStore({
getCollection: () => db.trash.grouped(db.settings.getGroupOptions("trash")),
eagerlyFetchFirstBatch: true
});
export interface TrashStore extends State {
trash: VirtualizedGrouping<TrashItem> | undefined;
setTrash: (items?: GroupedItems<TrashItem>) => void;
clearTrash: () => void;
}
export { useTrashStore, useTrash };
export const useTrashStore = create<TrashStore>((set, get) => ({
trash: undefined,
setTrash: () => {
db.trash.grouped(db.settings.getGroupOptions("trash")).then((trash) => {
set({
trash: trash
});
});
},
clearTrash: () => set({ trash: undefined })
}));

View File

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

View File

@@ -161,12 +161,12 @@
android:resource="@xml/file_viewer_provider_paths" />
</provider>
<!-- <receiver android:exported="true" android:name=".BootRecieverService" >-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.BOOT_COMPLETED" />-->
<!-- <action android:name="android.intent.action.QUICKBOOT_POWERON" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<receiver android:exported="true" android:name=".BootRecieverService" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
</application>

View File

@@ -1,6 +1,5 @@
- Added support to check/uncheck all items in a checklist
- Now you can open pdfs/images in readonly mode
- Fixed a bug where users are unable to login when app lock is enabled
- Added support for markdown with frontmatter in exports
- Fixed a bug that would randomly cause the app to freeze on launch after using the app for some days.
- Bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -9,6 +9,8 @@ import appJson from './app.json';
import Notifications from "../app/services/notifications";
import NetInfo from "@react-native-community/netinfo";
import { enableFreeze } from "react-native-screens";
import {BackgroundSync} from '../app/services/background-sync';
enableFreeze(true);
NetInfo.configure({
reachabilityUrl: "https://notesnook.com",
@@ -17,7 +19,7 @@ NetInfo.configure({
return response?.status >= 200 && response?.status < 300;
}
});
BackgroundSync.start();
Notifications.init();
const appName = appJson.name;

View File

@@ -1003,7 +1003,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2065;
CURRENT_PROJECT_VERSION = 2063;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1077,7 +1077,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.14;
MARKETING_VERSION = 2.6.12;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1106,11 +1106,9 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2065;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2063;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
HEADER_SEARCH_PATHS = (
@@ -1182,7 +1180,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.6.14;
MARKETING_VERSION = 2.6.12;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1340,7 +1338,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2065;
CURRENT_PROJECT_VERSION = 2063;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1352,7 +1350,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.14;
MARKETING_VERSION = 2.6.12;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1382,7 +1380,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2065;
CURRENT_PROJECT_VERSION = 2063;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1394,7 +1392,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.14;
MARKETING_VERSION = 2.6.12;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1423,7 +1421,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2065;
CURRENT_PROJECT_VERSION = 2063;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1497,7 +1495,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.14;
MARKETING_VERSION = 2.6.12;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1527,7 +1525,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2065;
CURRENT_PROJECT_VERSION = 2063;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1601,7 +1599,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.6.14;
MARKETING_VERSION = 2.6.12;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -63,7 +63,8 @@
"react-native-vector-icons": "9.2.0",
"react-native-webview": "^11.14.1",
"react-native-zip-archive": "6.0.9",
"react-native-quick-sqlite": "^8.0.6"
"react-native-quick-sqlite": "^8.0.6",
"@ammarahmed/react-native-background-fetch": "^4.2.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",

View File

@@ -100,7 +100,6 @@ module.exports = (env) => {
"react-native-blob-util": path.join(__dirname, "../node_modules/react-native-blob-util"),
"@mdi/js": path.join(__dirname, "../node_modules/@mdi/js/mdi.js"),
"katex": path.join(__dirname, "../node_modules/katex"),
"tinycolor2": path.join(__dirname, "../node_modules/tinycolor2"),
},
},
/**

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.6.14",
"version": "2.6.12",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -44,7 +44,6 @@
"kysely": "^0.26.3",
"react": "18.2.0",
"react-native": "0.72.0",
"react-native-quick-sqlite": "^8.0.6",
"tinycolor2": "1.6.0"
"react-native-quick-sqlite": "^8.0.6"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
diff --git a/node_modules/recyclerlistview/dist/reactnative/core/layoutmanager/LayoutManager.js b/node_modules/recyclerlistview/dist/reactnative/core/layoutmanager/LayoutManager.js
index 9cd3c57..e5bcb69 100644
index 9cd3c57..970421a 100644
--- a/node_modules/recyclerlistview/dist/reactnative/core/layoutmanager/LayoutManager.js
+++ b/node_modules/recyclerlistview/dist/reactnative/core/layoutmanager/LayoutManager.js
@@ -111,7 +111,7 @@ var WrapGridLayoutManager = /** @class */ (function (_super) {
@@ -7,19 +7,7 @@ index 9cd3c57..e5bcb69 100644
var itemRect = null;
var oldLayout = null;
- for (var i = startIndex; i < itemCount; i++) {
+ for (var i = startIndex; i < Math.min(startIndex + 500, itemCount); i++) {
+ for (var i = startIndex; i < Math.min(startIndex + 5000, itemCount); i++) {
oldLayout = this._layouts[i];
var layoutType = this._layoutProvider.getLayoutTypeForIndex(i);
if (oldLayout && oldLayout.isOverridden && oldLayout.type === layoutType) {
@@ -184,6 +184,11 @@ var WrapGridLayoutManager = /** @class */ (function (_super) {
}
var i = startIndex - 1;
for (; i >= 0; i--) {
+
+ if (!this._layouts[i]) {
+ break;
+ }
+
if (this._isHorizontal) {
if (this._layouts[i].y === 0) {
break;

View File

@@ -36,7 +36,6 @@ const EXTRA_ICON_NAMES = [
"star-outline",
"link-variant-remove",
"link-variant",
"link-variant-off",
"bell",
"bell-off-outline",
"check",

View File

@@ -17,24 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
Item,
Note,
Notebook,
Tag,
VirtualizedGrouping
} from "@notesnook/core";
import { Item, Note, Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import {
FlatList,
Platform,
StatusBar,
Text,
TextInput,
TouchableOpacity,
View,
useWindowDimensions
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import create from "zustand";
import { db } from "../app/common/database";
@@ -125,29 +121,23 @@ const NotebookItem = ({
items,
level = 0
}: {
id: string | number;
id: string;
mode: SearchMode;
close?: () => void;
level?: number;
items?: VirtualizedGrouping<Notebook>;
}) => {
const isExpanded = useNotebookExpandedStore((state) => state.expanded[id]);
const { nestedNotebooks, notebook } = useNotebook(id, items);
const isExpanded = useNotebookExpandedStore((state) =>
!notebook ? false : state.expanded[notebook.id]
);
const { colors } = useThemeColors();
const isSelected = useShareStore((state) =>
!notebook
? false
: state.selectedNotebooks.findIndex(
(selectedId) => notebook?.id === selectedId
) > -1
const isSelected = useShareStore(
(state) =>
state.selectedNotebooks.findIndex((selectedId) => id === selectedId) > -1
);
const set = SearchSetters[mode];
const onSelectItem = async () => {
if (!notebook) return;
set(notebook.id);
set(id);
};
return !notebook ? (
@@ -182,8 +172,7 @@ const NotebookItem = ({
alignItems: "center"
}}
onPress={() => {
if (!notebook) return;
useNotebookExpandedStore.getState().setExpanded(notebook.id);
useNotebookExpandedStore.getState().setExpanded(id);
}}
activeOpacity={1}
>
@@ -241,10 +230,10 @@ const NotebookItem = ({
marginTop: 5
}}
>
{nestedNotebooks.ids.map((item, index) => (
{(nestedNotebooks.ids as string[]).map((id) => (
<NotebookItem
key={notebook?.id + index}
id={index}
key={id}
id={id}
mode={mode}
close={close}
level={level + 1}
@@ -262,7 +251,7 @@ const ListItem = ({
close,
items
}: {
id: string | number;
id: string;
mode: SearchMode;
close?: () => void;
items?: VirtualizedGrouping<Item>;
@@ -270,23 +259,21 @@ const ListItem = ({
const [item] = useDBItem(
id,
mode === "appendNote" ? "note" : "tag",
items as VirtualizedGrouping<Note | Tag>
items as any
);
const { colors } = useThemeColors();
const isSelected = useShareStore((state) =>
mode === "appendNote" || !item
? false
: state.selectedTags.indexOf(item?.id as never) > -1
mode === "appendNote" ? false : state.selectedTags.indexOf(id as never) > -1
);
const set = SearchSetters[mode];
const onSelectItem = async () => {
if ((item as Note)?.locked || !item) {
if ((item as Note)?.locked) {
return;
}
set(item.id);
set(id);
};
return !item ? null : (
@@ -369,6 +356,12 @@ export const Search = ({
.then((exists) => setQueryExists(!!exists));
};
const insets =
Platform.OS === "android"
? { top: StatusBar.currentHeight }
: // eslint-disable-next-line react-hooks/rules-of-hooks
useSafeAreaInsets();
const get = SearchGetters[mode];
const lookup = SearchLookup[mode];
@@ -396,16 +389,16 @@ export const Search = ({
}, [get]);
const renderItem = React.useCallback(
({ index }: { item: string | number; index: number }) =>
({ item }: { item: string }) =>
mode === "selectNotebooks" ? (
<NotebookItem
id={index}
id={item}
mode={mode}
close={close}
items={items as VirtualizedGrouping<Notebook>}
items={items as any}
/>
) : (
<ListItem id={index} mode={mode} close={close} items={items} />
<ListItem id={item} mode={mode} close={close} items={items} />
),
[close, mode, items]
);
@@ -509,7 +502,7 @@ export const Search = ({
) : null}
<FlatList
data={items?.ids}
data={items?.ids as string[]}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
renderItem={renderItem}

View File

@@ -331,7 +331,6 @@
"version": "1.2.0",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@emotion/react": "11.11.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
@@ -340,7 +339,6 @@
"@types/tinycolor2": "^1.4.3",
"isomorphic-fetch": "^3.0.0",
"react": "18.2.0",
"tinycolor2": "^1.6.0",
"ts-json-schema-generator": "^1.2.0",
"zustand": "4.4.7"
},
@@ -977,7 +975,7 @@
},
"../web": {
"name": "@notesnook/web",
"version": "2.6.13",
"version": "2.6.12",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",
@@ -1004,14 +1002,13 @@
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.1",
"@tanstack/react-virtual": "^3.0.0-beta.68",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"allotment": "^1.19.3",
"async-mutex": "^0.4.0",
"axios": "^1.3.4",
"clipboard-polyfill": "4.0.0",
"comlink": "^4.3.1",
@@ -1042,7 +1039,6 @@
"react-modal": "3.16.1",
"react-qrcode-logo": "^2.9.0",
"react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2",
"timeago.js": "4.0.2",
"tinycolor2": "^1.6.0",
"w3c-keyname": "^2.2.6",

View File

@@ -81,7 +81,7 @@ export class AppModel {
async goToTags() {
await this.navigateTo("Tags");
return new ItemsViewModel(this.page);
return new ItemsViewModel(this.page, "tags");
}
async goToColor(color: string) {

View File

@@ -35,7 +35,7 @@ test("chunked stream should create equal sized chunks", async (t) => {
path.join(__dirname, "..", "__e2e__", "data", "importer-data.zip")
)
) as ReadableStream<Uint8Array>
).pipeThrough(new ChunkedStream(CHUNK_SIZE, "nocopy"))
).pipeThrough(new ChunkedStream(CHUNK_SIZE))
);
t.expect(await Promise.all(chunks.map((a) => xxhash64(a)))).toMatchObject([

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "2.6.13",
"version": "2.6.12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "2.6.13",
"version": "2.6.12",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",
@@ -33,14 +33,13 @@
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.1",
"@tanstack/react-virtual": "^3.0.0-beta.68",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"allotment": "^1.19.3",
"async-mutex": "^0.4.0",
"axios": "^1.3.4",
"clipboard-polyfill": "4.0.0",
"comlink": "^4.3.1",
@@ -71,7 +70,6 @@
"react-modal": "3.16.1",
"react-qrcode-logo": "^2.9.0",
"react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2",
"timeago.js": "4.0.2",
"tinycolor2": "^1.6.0",
"w3c-keyname": "^2.2.6",
@@ -247,7 +245,7 @@
"@types/spark-md5": "^3.0.2",
"@types/streetwriters__showdown": "npm:@types/showdown@^2.0.0",
"@types/ws": "^8.5.5",
"@vitest/coverage-v8": "^1.0.1",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
"better-sqlite3": "^8.6.0",
"bson-objectid": "^2.0.4",
@@ -262,7 +260,7 @@
"nanoid": "^5.0.1",
"otplib": "^12.0.1",
"refractor": "^4.8.1",
"vitest": "^1.0.1",
"vitest": "^0.34.1",
"vitest-fetch-mock": "^0.2.2",
"ws": "^8.13.0"
}
@@ -385,7 +383,6 @@
"version": "1.2.0",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@emotion/react": "11.11.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
@@ -394,7 +391,6 @@
"@types/tinycolor2": "^1.4.3",
"isomorphic-fetch": "^3.0.0",
"react": "18.2.0",
"tinycolor2": "^1.6.0",
"ts-json-schema-generator": "^1.2.0",
"zustand": "4.4.7"
},
@@ -465,7 +461,7 @@
},
"../desktop": {
"name": "@notesnook/desktop",
"version": "2.6.13",
"version": "2.6.12",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -483,8 +479,8 @@
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "25.9.8",
"electron-builder": "^24.9.1",
"electron": "25.9.3",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"tree-kill": "^1.2.2",
@@ -4233,11 +4229,11 @@
}
},
"node_modules/@tanstack/react-virtual": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.1.tgz",
"integrity": "sha512-IFOFuRUTaiM/yibty9qQ9BfycQnYXIDHGP2+cU+0LrFFGNhVxCXSQnaY6wkX8uJVteFEBjUondX0Hmpp7TNcag==",
"version": "3.0.0-beta.68",
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.68.tgz",
"integrity": "sha512-YEFNCf+N3ZlNou2r4qnh+GscMe24foYEjTL05RS0ZHvah2RoUDPGuhnuedTv0z66dO2vIq6+Bl4TXatht5T7GQ==",
"dependencies": {
"@tanstack/virtual-core": "3.0.0"
"@tanstack/virtual-core": "3.0.0-beta.68"
},
"funding": {
"type": "github",
@@ -4249,9 +4245,9 @@
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0.tgz",
"integrity": "sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==",
"version": "3.0.0-beta.68",
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.68.tgz",
"integrity": "sha512-CnvsEJWK7cugigckt13AeY80FMzH+OMdEP0j0bS3/zjs44NiRe49x8FZC6R9suRXGMVMXtUHet0zbTp/Ec9Wfg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
@@ -4796,14 +4792,6 @@
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
"dev": true
},
"node_modules/async-mutex": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
"integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -8419,18 +8407,6 @@
"react-dom": "16.x || 17.x || 18.x"
}
},
"node_modules/react-virtuoso": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.6.2.tgz",
"integrity": "sha512-vvlqvzPif+MvBrJ09+hJJrVY0xJK9yran+A+/1iwY78k0YCVKsyoNPqoLxOxzYPggspNBNXqUXEcvckN29OxyQ==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16 || >=17 || >= 18",
"react-dom": ">=16 || >=17 || >= 18"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.6.13",
"version": "2.6.12",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",
@@ -32,14 +32,13 @@
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.1",
"@tanstack/react-virtual": "^3.0.0-beta.68",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"allotment": "^1.19.3",
"async-mutex": "^0.4.0",
"axios": "^1.3.4",
"clipboard-polyfill": "4.0.0",
"comlink": "^4.3.1",
@@ -61,22 +60,21 @@
"phone": "^3.1.14",
"platform": "^1.3.6",
"qclone": "^1.2.0",
"react": "18.2.0",
"react-complex-tree": "^2.2.4",
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.4.1",
"react-loading-skeleton": "^3.3.1",
"react-modal": "3.16.1",
"react-qrcode-logo": "^2.9.0",
"react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2",
"timeago.js": "4.0.2",
"tinycolor2": "^1.6.0",
"w3c-keyname": "^2.2.6",
"web-streams-polyfill": "^3.1.1",
"wouter": "2.12.1",
"zustand": "4.4.7"
"zustand": "4.4.7",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@babel/core": "^7.22.5",
@@ -129,8 +127,7 @@
"build:beta": "cross-env PLATFORM=web BETA=true vite build",
"build:desktop": "cross-env PLATFORM=desktop vite build",
"analyze": "cross-env ANALYZING=true PLATFORM=web vite build",
"test": "playwright test -u",
"postinstall": "patch-package"
"test": "playwright test -u"
},
"browserslist": {
"production": [

File diff suppressed because it is too large Load Diff

View File

@@ -18,14 +18,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { lazify } from "../utils/lazify";
import { Attachment } from "@notesnook/core";
import { db } from "./db";
async function download(hash: string) {
const attachment = await db.attachments.attachment(hash);
const attachment = db.attachments.attachment(hash);
if (!attachment) return;
const downloadResult = await db
.fs()
.downloadFile(attachment.hash, attachment.hash, attachment.chunkSize);
.downloadFile(
attachment.metadata.hash,
attachment.metadata.hash,
attachment.chunkSize,
attachment.metadata
);
if (!downloadResult) throw new Error("Failed to download file.");
const key = await db.attachments.decryptKey(attachment.key);
@@ -40,7 +46,7 @@ export async function saveAttachment(hash: string) {
const { attachment, key } = response;
await lazify(import("../interfaces/fs"), ({ saveFile }) =>
saveFile(attachment.hash, {
saveFile(attachment.metadata.hash, {
key,
iv: attachment.iv,
name: attachment.filename,
@@ -67,7 +73,7 @@ export async function downloadAttachment<
return (await db.attachments.read(hash, type)) as TOutputType;
const blob = await lazify(import("../interfaces/fs"), ({ decryptFile }) =>
decryptFile(attachment.hash, {
decryptFile(attachment.metadata.hash, {
key,
iv: attachment.iv,
name: attachment.filename,

View File

@@ -17,6 +17,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const COLORS = [
{ key: "red", title: "Red" },
{ key: "orange", title: "Orange" },
{ key: "yellow", title: "Yellow" },
{ key: "green", title: "Green" },
{ key: "blue", title: "Blue" },
{ key: "purple", title: "Purple" },
{ key: "gray", title: "Gray" }
] as const;
export const SUBSCRIPTION_STATUS = {
BASIC: 0,
TRIAL: 1,

View File

@@ -57,15 +57,14 @@ async function initializeDatabase(persistence: DatabasePersistence) {
database.setup({
sqliteOptions: {
dialect: (name) => ({
dialect: {
createDriver: () =>
new WaSqliteWorkerDriver({ async: true, dbName: name }),
new WaSqliteWorkerDriver({ async: true, dbName: "test.db" }),
createAdapter: () => new SqliteAdapter(),
createIntrospector: (db) => new SqliteIntrospector(db),
createQueryCompiler: () => new SqliteQueryCompiler()
}),
},
journalMode: "MEMORY",
tempStore: "memory",
synchronous: "normal",
pageSize: 8192,
cacheSize: -16000,

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Dialogs } from "../dialogs";
import { store as notebookStore } from "../stores/notebook-store";
import { store as tagStore } from "../stores/tag-store";
import { store as appStore } from "../stores/app-store";
import { store as editorStore } from "../stores/editor-store";
@@ -25,6 +26,7 @@ import { store as noteStore } from "../stores/note-store";
import { db } from "./db";
import { showToast } from "../utils/toast";
import { Text } from "@theme-ui/components";
import { Topic as TopicIcon } from "../components/icons";
import Config from "../utils/config";
import { AppVersion, getChangelog } from "../utils/version";
import { Period } from "../dialogs/buy-dialog/types";
@@ -34,7 +36,7 @@ import { ConfirmDialogProps } from "../dialogs/confirm";
import { getFormattedDate } from "@notesnook/common";
import { downloadUpdate } from "../utils/updater";
import { ThemeMetadata } from "@notesnook/themes-server";
import { Color, Reminder, Tag } from "@notesnook/core";
import { Reminder } from "@notesnook/core";
import { AuthenticatorType } from "@notesnook/core/dist/api/user-manager";
import { createRoot } from "react-dom/client";
@@ -88,15 +90,6 @@ export function showAddTagsDialog(noteIds: string[]) {
));
}
export function showCreateColorDialog() {
return showDialog<"CreateColorDialog", string | undefined>(
"CreateColorDialog",
(Dialog, perform) => (
<Dialog onClose={() => perform(undefined)} onDone={(id) => perform(id)} />
)
);
}
export function showAddNotebookDialog(parentId?: string) {
return showDialog("AddNotebookDialog", (Dialog, perform) => (
<Dialog
@@ -419,25 +412,21 @@ export function showPasswordDialog(
password?: string;
oldPassword?: string;
newPassword?: string;
deleteAllLockedNotes?: boolean;
}) => boolean | Promise<boolean>
) {
const { title, subtitle, positiveButtonText, checks } = getDialogData(type);
return showDialog<"PasswordDialog", boolean>(
"PasswordDialog",
(Dialog, perform) => (
<Dialog
type={type}
title={title}
subtitle={subtitle}
checks={checks}
positiveButtonText={positiveButtonText}
validate={validate}
onClose={() => perform(false)}
onDone={() => perform(true)}
/>
)
);
return showDialog("PasswordDialog", (Dialog, perform) => (
<Dialog
type={type}
title={title}
subtitle={subtitle}
checks={checks}
positiveButtonText={positiveButtonText}
validate={validate}
onClose={() => perform(false)}
onDone={() => perform(true)}
/>
));
}
export function showRecoveryKeyDialog() {
@@ -469,7 +458,9 @@ export function showCreateTagDialog() {
));
}
export function showEditTagDialog(tag: Tag) {
export function showEditTagDialog(tagId: string) {
const tag = db.tags.tag(tagId);
if (!tag) return;
return showDialog("ItemDialog", (Dialog, perform) => (
<Dialog
title={"Edit tag"}
@@ -479,7 +470,7 @@ export function showEditTagDialog(tag: Tag) {
onClose={() => perform(false)}
onAction={async (title: string) => {
if (!title) return;
await db.tags.add({ id: tag.id, title });
await db.tags.add({ id: tagId, title });
showToast("success", "Tag edited!");
tagStore.refresh();
editorStore.refreshTags();
@@ -491,7 +482,9 @@ export function showEditTagDialog(tag: Tag) {
));
}
export function showRenameColorDialog(color: Color) {
export function showRenameColorDialog(colorId: string) {
const color = db.colors.color(colorId);
if (!color) return;
return showDialog("ItemDialog", (Dialog, perform) => (
<Dialog
title={"Rename color"}
@@ -501,7 +494,7 @@ export function showRenameColorDialog(color: Color) {
onClose={() => perform(false)}
onAction={async (title: string) => {
if (!title) return;
await db.tags.add({ id: color.id, title });
await db.tags.add({ id: colorId, title });
showToast("success", "Color renamed!");
appStore.refreshNavItems();
perform(true);

View File

@@ -39,8 +39,7 @@ import { createWritableStream } from "./desktop-bridge";
import { ZipStream } from "../utils/streams/zip-stream";
import { FeatureKeys } from "../dialogs/feature-dialog";
import { Entry, Reader } from "../utils/zip-reader";
import { User } from "@notesnook/core/dist/api/user-manager";
import { LegacyBackupFile } from "@notesnook/core";
import { IndexedDBKVStore } from "../interfaces/key-value";
export const CREATE_BUTTON_MAP = {
notes: {
@@ -52,6 +51,10 @@ export const CREATE_BUTTON_MAP = {
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 })
@@ -136,9 +139,8 @@ export async function selectBackupFile() {
export async function importBackup() {
const backupFile = await selectBackupFile();
if (!backupFile) return false;
if (!backupFile) return;
await restoreBackupFile(backupFile);
return true;
}
export async function restoreBackupFile(backupFile: File) {
@@ -194,7 +196,7 @@ export async function restoreBackupFile(backupFile: File) {
if (!result) break;
} else await db.backup?.import(backup, cachedPassword);
} else {
await db.backup?.import(backup);
await db.backup?.import(backup, null);
}
report({
@@ -214,7 +216,7 @@ export async function restoreBackupFile(backupFile: File) {
}
async function restoreWithProgress(
backup: LegacyBackupFile,
backup: Record<string, unknown>,
password?: string
) {
return await TaskManager.startTask<Error | void>({
@@ -249,8 +251,8 @@ async function restoreWithProgress(
export async function verifyAccount() {
if (!(await db.user?.getUser())) return true;
return showPasswordDialog("verify_account", async ({ password }) => {
return !!password && (await db.user?.verifyPassword(password));
return showPasswordDialog("verify_account", ({ password }) => {
return db.user?.verifyPassword(password) || false;
});
}
@@ -282,7 +284,7 @@ export async function showUpgradeReminderDialogs() {
}
}
async function restore(backup: LegacyBackupFile, password?: string) {
async function restore(backup: Record<string, unknown>, password?: string) {
try {
await db.backup?.import(backup, password);
showToast("success", "Backup restored!");

View File

@@ -27,6 +27,7 @@ import { showToast } from "../utils/toast";
import Vault from "./vault";
import { TaskManager } from "./task-manager";
import { pluralize } from "@notesnook/common";
import { Reminder } from "@notesnook/core";
async function moveNotesToTrash(ids: string[], confirm = true) {
if (confirm && !(await showMultiDeleteConfirmation(ids.length))) return;
@@ -96,7 +97,7 @@ async function deleteAttachments(ids: string[]) {
action: async (report) => {
for (let i = 0; i < ids.length; ++i) {
const id = ids[i];
const attachment = await db.attachments.attachment(id);
const attachment = await attachmentStore.get().attachments?.item(id);
if (!attachment) continue;
report({
@@ -111,10 +112,10 @@ async function deleteAttachments(ids: string[]) {
showToast("success", `${pluralize(ids.length, "attachment")} deleted`);
}
async function moveRemindersToTrash(ids: string[]) {
const isMultiselect = ids.length > 1;
async function moveRemindersToTrash(reminders: Reminder[]) {
const isMultiselect = reminders.length > 1;
if (isMultiselect) {
if (!(await showMultiDeleteConfirmation(ids.length))) return;
if (!(await showMultiDeleteConfirmation(reminders.length))) return;
}
await TaskManager.startTask({
@@ -122,13 +123,13 @@ async function moveRemindersToTrash(ids: string[]) {
id: "deleteReminders",
action: async (report) => {
report({
text: `Deleting ${pluralize(ids.length, "reminder")}...`
text: `Deleting ${pluralize(reminders.length, "reminder")}...`
});
await reminderStore.delete(...ids);
await reminderStore.delete(...reminders.map((i) => i.id));
}
});
showToast("success", `${pluralize(ids.length, "reminder")} deleted.`);
showToast("success", `${pluralize(reminders.length, "reminder")} deleted.`);
}
export const Multiselect = {

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