Compare commits
2 Commits
fix/export
...
feat-chang
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4173309fd9 | ||
|
|
d33c2a6537 |
94
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.7",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -24,7 +24,7 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.9.3",
|
||||
"electron": "25.8.1",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
@@ -216,7 +216,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz",
|
||||
"integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"env-paths": "^2.2.0",
|
||||
@@ -237,7 +236,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
@@ -251,7 +249,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"dev": true,
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
@@ -260,7 +257,6 @@
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -269,7 +265,6 @@
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
@@ -803,7 +798,6 @@
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -815,7 +809,6 @@
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"defer-to-connect": "^2.0.0"
|
||||
},
|
||||
@@ -860,7 +853,6 @@
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
|
||||
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/http-cache-semantics": "*",
|
||||
"@types/keyv": "^3.1.4",
|
||||
@@ -889,14 +881,12 @@
|
||||
"node_modules/@types/http-cache-semantics": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ=="
|
||||
},
|
||||
"node_modules/@types/keyv": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
|
||||
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -910,8 +900,7 @@
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz",
|
||||
"integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA=="
|
||||
},
|
||||
"node_modules/@types/normalize-package-data": {
|
||||
"version": "2.4.1",
|
||||
@@ -933,7 +922,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
||||
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -963,7 +951,6 @@
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
@@ -1204,7 +1191,6 @@
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
||||
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
@@ -1256,7 +1242,6 @@
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@@ -1331,7 +1316,6 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
|
||||
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.6.0"
|
||||
}
|
||||
@@ -1340,7 +1324,6 @@
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
|
||||
"integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"clone-response": "^1.0.2",
|
||||
"get-stream": "^5.1.0",
|
||||
@@ -1460,7 +1443,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
||||
"integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^1.0.0"
|
||||
},
|
||||
@@ -1604,7 +1586,6 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
@@ -1619,7 +1600,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -1631,7 +1611,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
|
||||
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@@ -1640,7 +1619,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
||||
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
@@ -1666,7 +1644,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/dir-compare": {
|
||||
@@ -1774,10 +1751,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "25.9.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.3.tgz",
|
||||
"integrity": "sha512-dacaHg/PuwVcFRgPDCM5j7UDzqGJWOsbBRdS5wPKLNS/ejPeccIjuNUT1cqcrpvCJKAFW8swHWg9kdizNSEDHQ==",
|
||||
"dev": true,
|
||||
"version": "25.8.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.8.1.tgz",
|
||||
"integrity": "sha512-GtcP1nMrROZfFg0+mhyj1hamrHvukfF6of2B/pcWxmWkd5FVY1NJib0tlhiorFZRzQN5Z+APLPr7aMolt7i2AQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
@@ -1944,7 +1920,6 @@
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
@@ -1953,7 +1928,6 @@
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -1977,7 +1951,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
@@ -2029,7 +2002,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -2055,7 +2027,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.1",
|
||||
"get-stream": "^5.1.0",
|
||||
@@ -2096,7 +2067,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
@@ -2213,7 +2183,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
@@ -2227,7 +2197,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
@@ -2243,7 +2212,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0"
|
||||
},
|
||||
@@ -2312,7 +2280,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
|
||||
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
@@ -2330,7 +2297,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
|
||||
"integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"define-properties": "^1.1.3"
|
||||
@@ -2346,7 +2312,6 @@
|
||||
"version": "11.8.6",
|
||||
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
|
||||
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.0.0",
|
||||
"@szmarczak/http-timer": "^4.0.5",
|
||||
@@ -2376,7 +2341,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
@@ -2397,7 +2362,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
|
||||
"integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.1"
|
||||
@@ -2410,7 +2374,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -2423,7 +2386,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -2447,8 +2409,7 @@
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
@@ -2468,7 +2429,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
|
||||
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"quick-lru": "^5.1.1",
|
||||
"resolve-alpn": "^1.0.0"
|
||||
@@ -2731,8 +2691,7 @@
|
||||
"node_modules/json-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
@@ -2750,7 +2709,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
@@ -2780,7 +2738,6 @@
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
|
||||
"integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
@@ -2816,7 +2773,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -2836,7 +2792,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"escape-string-regexp": "^4.0.0"
|
||||
@@ -2882,7 +2837,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
||||
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -3005,7 +2959,6 @@
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -3017,7 +2970,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -3027,7 +2979,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -3036,7 +2987,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -3116,8 +3066,7 @@
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@@ -3156,7 +3105,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -3178,7 +3126,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
@@ -3197,7 +3144,6 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
|
||||
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -3392,14 +3338,12 @@
|
||||
"node_modules/resolve-alpn": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
|
||||
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="
|
||||
},
|
||||
"node_modules/responselike": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
|
||||
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lowercase-keys": "^2.0.0"
|
||||
},
|
||||
@@ -3435,7 +3379,6 @@
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
|
||||
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
@@ -3515,14 +3458,12 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/serialize-error": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
|
||||
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.13.1"
|
||||
@@ -3538,7 +3479,6 @@
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -3659,7 +3599,6 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/stat-mode": {
|
||||
@@ -3732,7 +3671,6 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
|
||||
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.1.0"
|
||||
},
|
||||
@@ -4002,8 +3940,7 @@
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "15.1.1",
|
||||
@@ -4056,7 +3993,6 @@
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.7",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
@@ -25,7 +25,7 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.9.3",
|
||||
"electron": "25.8.1",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
|
||||
@@ -29,8 +29,6 @@ import { dirname } from "path";
|
||||
import { resolvePath } from "../utils/resolve-path";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { AssetManager } from "../utils/asset-manager";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||
|
||||
const t = initTRPC.create();
|
||||
|
||||
@@ -46,23 +44,12 @@ const NotificationOptions = z.object({
|
||||
});
|
||||
|
||||
export const osIntegrationRouter = t.router({
|
||||
isFlatpak: t.procedure.query(() => isFlatpak()),
|
||||
|
||||
zoomFactor: t.procedure.query(() => config.zoomFactor),
|
||||
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
|
||||
globalThis.window?.webContents.setZoomFactor(factor);
|
||||
config.zoomFactor = factor;
|
||||
}),
|
||||
|
||||
menuBar: t.procedure.query(() => config.menuBar),
|
||||
setMenuBar: t.procedure
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
config.menuBar = enabled;
|
||||
if (!enabled) globalThis.window?.setMenuBarVisibility(false);
|
||||
globalThis.window?.setAutoHideMenuBar(enabled);
|
||||
}),
|
||||
|
||||
privacyMode: t.procedure.query(() => config.privacyMode),
|
||||
setPrivacyMode: t.procedure
|
||||
.input(z.object({ enabled: z.boolean() }))
|
||||
@@ -92,7 +79,6 @@ export const osIntegrationRouter = t.router({
|
||||
AutoLaunch.disable();
|
||||
}
|
||||
config.desktopSettings = settings;
|
||||
setupDesktopIntegration();
|
||||
}),
|
||||
|
||||
selectDirectory: t.procedure
|
||||
|
||||
@@ -24,6 +24,7 @@ import { configureAutoUpdater } from "./utils/autoupdater";
|
||||
import { getBackgroundColor, getTheme, setTheme } from "./utils/theme";
|
||||
import { setupMenu } from "./utils/menu";
|
||||
import { WindowState } from "./utils/window-state";
|
||||
import { AutoLaunch } from "./utils/autolaunch";
|
||||
import { setupJumplist } from "./utils/jumplist";
|
||||
import { setupTray } from "./utils/tray";
|
||||
import { CLIOptions, parseArguments } from "./cli";
|
||||
@@ -34,7 +35,6 @@ import { config } from "./utils/config";
|
||||
import path from "path";
|
||||
import { bringToFront } from "./utils/bring-to-front";
|
||||
import { bridge } from "./api/bridge";
|
||||
import { setupDesktopIntegration } from "./utils/desktop-integration";
|
||||
|
||||
// only run a single instance
|
||||
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
|
||||
@@ -77,7 +77,7 @@ async function createWindow() {
|
||||
darkTheme: getTheme() === "dark",
|
||||
backgroundColor: getBackgroundColor(),
|
||||
opacity: 0,
|
||||
autoHideMenuBar: config.menuBar,
|
||||
autoHideMenuBar: true,
|
||||
icon: AssetManager.appIcon({
|
||||
size: 512,
|
||||
format: process.platform === "win32" ? "ico" : "png"
|
||||
@@ -93,7 +93,6 @@ async function createWindow() {
|
||||
|
||||
createIPCHandler({ router, windows: [mainWindow] });
|
||||
globalThis.window = mainWindow;
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
mainWindowState.manage(mainWindow);
|
||||
|
||||
if (cliOptions.hidden && !config.desktopSettings.minimizeToSystemTray)
|
||||
@@ -177,3 +176,49 @@ function createURL(options: CLIOptions, path = "/") {
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function setupDesktopIntegration() {
|
||||
const desktopIntegration = config.desktopSettings;
|
||||
|
||||
if (
|
||||
desktopIntegration.closeToSystemTray ||
|
||||
desktopIntegration.minimizeToSystemTray
|
||||
) {
|
||||
setupTray();
|
||||
}
|
||||
|
||||
// when close to system tray is enabled, it becomes nigh impossible
|
||||
// to "quit" the app. This is necessary in order to fix that.
|
||||
if (desktopIntegration.closeToSystemTray) {
|
||||
app.on("before-quit", () => app.exit(0));
|
||||
}
|
||||
|
||||
globalThis.window?.on("close", (e) => {
|
||||
if (config.desktopSettings.closeToSystemTray) {
|
||||
e.preventDefault();
|
||||
if (process.platform == "darwin") {
|
||||
// on macOS window cannot be minimized/hidden if it is already fullscreen
|
||||
// so we just close it.
|
||||
if (globalThis.window?.isFullScreen()) app.exit(0);
|
||||
else app.hide();
|
||||
} else {
|
||||
globalThis.window?.minimize();
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.window?.on("minimize", () => {
|
||||
if (config.desktopSettings.minimizeToSystemTray) {
|
||||
if (process.platform == "darwin") {
|
||||
app.hide();
|
||||
} else {
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (desktopIntegration.autoStart) {
|
||||
AutoLaunch.enable(!!desktopIntegration.startMinimized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
|
||||
"autostart"
|
||||
);
|
||||
|
||||
const STARTUP_ARGS = ["--hidden"];
|
||||
|
||||
export class AutoLaunch {
|
||||
static enable(hidden: boolean) {
|
||||
if (process.platform === "linux") {
|
||||
@@ -49,14 +47,10 @@ export class AutoLaunch {
|
||||
LINUX_DESKTOP_ENTRY(hidden)
|
||||
);
|
||||
} else {
|
||||
const loginItemSettings = app.getLoginItemSettings({
|
||||
args: STARTUP_ARGS
|
||||
});
|
||||
if (loginItemSettings.openAtLogin) return;
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
openAsHidden: hidden,
|
||||
args: STARTUP_ARGS
|
||||
args: ["--hidden"]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ export const config = {
|
||||
isSpellCheckerEnabled: true,
|
||||
zoomFactor: 1,
|
||||
theme: nativeTheme.themeSource,
|
||||
automaticUpdates: true,
|
||||
menuBar: true
|
||||
automaticUpdates: true
|
||||
};
|
||||
|
||||
type ConfigKey = keyof typeof config;
|
||||
|
||||
@@ -1,70 +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 { app } from "electron";
|
||||
import { config } from "./config";
|
||||
import { setupTray } from "./tray";
|
||||
import { AutoLaunch } from "./autolaunch";
|
||||
|
||||
export function setupDesktopIntegration() {
|
||||
const desktopIntegration = config.desktopSettings;
|
||||
|
||||
if (
|
||||
desktopIntegration.closeToSystemTray ||
|
||||
desktopIntegration.minimizeToSystemTray
|
||||
) {
|
||||
setupTray();
|
||||
}
|
||||
|
||||
// when close to system tray is enabled, it becomes nigh impossible
|
||||
// to "quit" the app. This is necessary in order to fix that.
|
||||
if (desktopIntegration.closeToSystemTray) {
|
||||
app.on("before-quit", () => app.exit(0));
|
||||
}
|
||||
|
||||
globalThis.window?.on("close", (e) => {
|
||||
if (config.desktopSettings.closeToSystemTray) {
|
||||
e.preventDefault();
|
||||
if (process.platform == "darwin") {
|
||||
// on macOS window cannot be minimized/hidden if it is already fullscreen
|
||||
// so we just close it.
|
||||
if (globalThis.window?.isFullScreen()) app.exit(0);
|
||||
else app.hide();
|
||||
} else {
|
||||
try {
|
||||
globalThis.window?.minimize();
|
||||
globalThis.window?.hide();
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalThis.window?.on("minimize", () => {
|
||||
if (config.desktopSettings.minimizeToSystemTray) {
|
||||
if (process.platform == "darwin") {
|
||||
app.hide();
|
||||
} else {
|
||||
globalThis.window?.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (desktopIntegration.autoStart) {
|
||||
AutoLaunch.enable(!!desktopIntegration.startMinimized);
|
||||
}
|
||||
}
|
||||
@@ -96,22 +96,16 @@ export async function downloadFile(filename, data, cancelToken) {
|
||||
}
|
||||
|
||||
export async function getUploadedFileSize(hash) {
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.user.tokenManager.getAccessToken();
|
||||
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const attachmentInfo = await fetch(url, {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length")
|
||||
);
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
|
||||
return isNaN(contentLength) ? 0 : contentLength;
|
||||
}
|
||||
|
||||
export async function checkAttachment(hash) {
|
||||
|
||||
@@ -91,7 +91,6 @@ const AuthModal = () => {
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
enableAutomaticScroll={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{currentAuthMode !== AuthMode.login ? (
|
||||
|
||||
@@ -69,8 +69,7 @@ export const SessionExpired = () => {
|
||||
eSendEvent(eUserLoggedIn, true);
|
||||
setVisible(false);
|
||||
setFocused(false);
|
||||
},
|
||||
true
|
||||
}
|
||||
);
|
||||
|
||||
const logout = async () => {
|
||||
@@ -143,9 +142,9 @@ export const SessionExpired = () => {
|
||||
passwordInputRef.current?.focus();
|
||||
setFocused(true);
|
||||
}}
|
||||
enableSheetKeyboardHandler={true}
|
||||
visible={true}
|
||||
>
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<View
|
||||
style={{
|
||||
width: focused ? "100%" : "99.9%",
|
||||
@@ -238,8 +237,6 @@ export const SessionExpired = () => {
|
||||
</View>
|
||||
<Toast context="local" />
|
||||
<Dialog context="session_expiry" />
|
||||
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
</BaseDialog>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ export const LoginSteps = {
|
||||
passwordAuth: 3
|
||||
};
|
||||
|
||||
export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
export const useLogin = (onFinishLogin) => {
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const setUser = useUserStore((state) => state.setUser);
|
||||
@@ -69,7 +69,6 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
switch (step) {
|
||||
case LoginSteps.emailAuth: {
|
||||
const mfaInfo = await db.user.authenticateEmail(email.current);
|
||||
console.log("email auth", mfaInfo);
|
||||
if (mfaInfo) {
|
||||
TwoFactorVerification.present(async (mfa, callback) => {
|
||||
try {
|
||||
@@ -102,12 +101,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
break;
|
||||
}
|
||||
case LoginSteps.passwordAuth: {
|
||||
await db.user.authenticatePassword(
|
||||
email.current,
|
||||
password.current,
|
||||
null,
|
||||
sessionExpired
|
||||
);
|
||||
await db.user.authenticatePassword(email.current, password.current);
|
||||
finishLogin();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ const BaseDialog = ({
|
||||
bounce = true,
|
||||
closeOnTouch = true,
|
||||
useSafeArea = true,
|
||||
avoidKeyboardResize = false,
|
||||
enableSheetKeyboardHandler = false
|
||||
avoidKeyboardResize = false
|
||||
}) => {
|
||||
const floating = useIsFloatingKeyboard();
|
||||
const appState = useAppState();
|
||||
@@ -104,9 +103,7 @@ const BaseDialog = ({
|
||||
if (lockEvents.current) return;
|
||||
if (onShow) {
|
||||
onShow();
|
||||
if (!enableSheetKeyboardHandler) {
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
}
|
||||
useSettingStore.getState().setSheetKeyboardHandler(false);
|
||||
}
|
||||
}}
|
||||
animationType={animation}
|
||||
|
||||
@@ -43,9 +43,6 @@ const SelectionWrapper = ({
|
||||
|
||||
const onLongPress = () => {
|
||||
if (!useSelectionStore.getState().selectionMode) {
|
||||
useSelectionStore.setState({
|
||||
selectedItemsList: []
|
||||
});
|
||||
useSelectionStore.getState().setSelectionMode(true);
|
||||
}
|
||||
useSelectionStore.getState().setSelectedItem(item);
|
||||
|
||||
@@ -165,12 +165,7 @@ export const Component = ({ close, promo }) => {
|
||||
}}
|
||||
size={SIZE.md}
|
||||
>
|
||||
(
|
||||
{Platform.OS === "android"
|
||||
? pricing.product?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0].formattedPrice
|
||||
: pricing.product?.localizedPrice}{" "}
|
||||
/ mo)
|
||||
({pricing?.product?.localizedPrice} / mo)
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,27 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { View } from "react-native";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { PressableButton } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import RNIap from "react-native-iap";
|
||||
|
||||
export const PricingItem = ({
|
||||
product,
|
||||
onPress,
|
||||
compact
|
||||
}: {
|
||||
product: {
|
||||
type: "yearly" | "monthly";
|
||||
data?: RNIap.Subscription;
|
||||
info: string;
|
||||
offerType?: "yearly" | "monthly";
|
||||
};
|
||||
onPress?: () => void;
|
||||
compact?: boolean;
|
||||
}) => {
|
||||
export const PricingItem = ({ product, onPress, compact }) => {
|
||||
return (
|
||||
<PressableButton
|
||||
onPress={onPress}
|
||||
@@ -68,13 +54,7 @@ export const PricingItem = ({
|
||||
|
||||
<View>
|
||||
<Paragraph size={SIZE.sm}>
|
||||
<Heading size={SIZE.lg - 2}>
|
||||
{Platform.OS === "android"
|
||||
? (product.data as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0].formattedPrice
|
||||
: (product.data as RNIap.SubscriptionIOS)?.localizedPrice}
|
||||
</Heading>
|
||||
<Heading size={SIZE.lg - 2}>{product?.data?.localizedPrice}/</Heading>
|
||||
{product?.type === "yearly" || product?.offerType === "yearly"
|
||||
? "/year"
|
||||
: "/month"}
|
||||
@@ -47,15 +47,11 @@ import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Walkthrough } from "../walkthroughs";
|
||||
import { PricingItem } from "./pricing-item";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
|
||||
const promoCyclesMonthly = {
|
||||
1: "first month",
|
||||
2: "first 2 months",
|
||||
3: "first 3 months",
|
||||
4: "first 4 months",
|
||||
5: "first 5 months",
|
||||
6: "first 3 months"
|
||||
3: "first 3 months"
|
||||
};
|
||||
|
||||
const promoCyclesYearly = {
|
||||
@@ -69,24 +65,10 @@ export const PricingPlans = ({
|
||||
marginTop,
|
||||
heading = true,
|
||||
compact = false
|
||||
}: {
|
||||
promo?: {
|
||||
promoCode: string;
|
||||
};
|
||||
marginTop?: any;
|
||||
heading?: boolean;
|
||||
compact?: boolean;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const [product, setProduct] = useState<{
|
||||
type: string;
|
||||
offerType: "monthly" | "yearly";
|
||||
data: RNIap.Subscription;
|
||||
cycleText: string;
|
||||
info: string;
|
||||
}>();
|
||||
|
||||
const [product, setProduct] = useState(null);
|
||||
const [buying, setBuying] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const userCanRequestTrial =
|
||||
@@ -108,40 +90,27 @@ export const PricingPlans = ({
|
||||
}
|
||||
}, [promo?.promoCode]);
|
||||
|
||||
const getPromo = async (code: string) => {
|
||||
const getPromo = async (code) => {
|
||||
try {
|
||||
let skuId: string;
|
||||
let productId;
|
||||
if (code.startsWith("com.streetwriters.notesnook")) {
|
||||
skuId = code;
|
||||
productId = code;
|
||||
} else {
|
||||
skuId = await db.offers?.getCode(code.split(":")[0], Platform.OS);
|
||||
productId = await db.offers.getCode(code.split(":")[0], Platform.OS);
|
||||
}
|
||||
|
||||
const products = await PremiumService.getProducts();
|
||||
const product = products.find((p) => p.productId === skuId);
|
||||
let products = await PremiumService.getProducts();
|
||||
let product = products.find((p) => p.productId === productId);
|
||||
if (!product) return false;
|
||||
const isMonthly = product.productId.indexOf(".mo") > -1;
|
||||
|
||||
const cycleText = isMonthly
|
||||
let isMonthly = product.productId.indexOf(".mo") > -1;
|
||||
let cycleText = isMonthly
|
||||
? promoCyclesMonthly[
|
||||
(Platform.OS === "android"
|
||||
? (product as RNIap.SubscriptionAndroid)
|
||||
.subscriptionOfferDetails[0]?.pricingPhases
|
||||
.pricingPhaseList?.[0].billingCycleCount
|
||||
: parseInt(
|
||||
(product as RNIap.SubscriptionIOS)
|
||||
.introductoryPriceNumberOfPeriodsIOS as string
|
||||
)) as keyof typeof promoCyclesMonthly
|
||||
product.introductoryPriceCyclesAndroid ||
|
||||
product.introductoryPriceNumberOfPeriodsIOS
|
||||
]
|
||||
: promoCyclesYearly[
|
||||
(Platform.OS === "android"
|
||||
? (product as RNIap.SubscriptionAndroid)
|
||||
.subscriptionOfferDetails[0]?.pricingPhases
|
||||
.pricingPhaseList?.[0].billingCycleCount
|
||||
: parseInt(
|
||||
(product as RNIap.SubscriptionIOS)
|
||||
.introductoryPriceNumberOfPeriodsIOS as string
|
||||
)) as keyof typeof promoCyclesYearly
|
||||
product.introductoryPriceCyclesAndroid ||
|
||||
product.introductoryPriceNumberOfPeriodsIOS
|
||||
];
|
||||
|
||||
setProduct({
|
||||
@@ -149,7 +118,7 @@ export const PricingPlans = ({
|
||||
offerType: isMonthly ? "monthly" : "yearly",
|
||||
data: product,
|
||||
cycleText: cycleText,
|
||||
info: `Pay ${isMonthly ? "monthly" : "yearly"}, cancel anytime`
|
||||
info: "Pay monthly, cancel anytime"
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
@@ -162,37 +131,22 @@ export const PricingPlans = ({
|
||||
getSkus();
|
||||
}, [getSkus]);
|
||||
|
||||
const buySubscription = async (product: RNIap.Subscription) => {
|
||||
if (buying || !product) return;
|
||||
const buySubscription = async (product) => {
|
||||
if (buying) return;
|
||||
setBuying(true);
|
||||
try {
|
||||
if (!user) {
|
||||
setBuying(false);
|
||||
return;
|
||||
}
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
const androidOfferToken =
|
||||
Platform.OS === "android"
|
||||
? (product as RNIap.SubscriptionAndroid).subscriptionOfferDetails[0]
|
||||
.offerToken
|
||||
: null;
|
||||
|
||||
await RNIap.requestSubscription({
|
||||
sku: product?.productId,
|
||||
obfuscatedAccountIdAndroid: user.id,
|
||||
obfuscatedProfileIdAndroid: user.id,
|
||||
appAccountToken: user.id,
|
||||
andDangerouslyFinishTransactionAutomaticallyIOS: false,
|
||||
subscriptionOffers: androidOfferToken
|
||||
? [
|
||||
{
|
||||
offerToken: androidOfferToken,
|
||||
sku: product?.productId
|
||||
}
|
||||
]
|
||||
: undefined
|
||||
});
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
|
||||
await RNIap.requestSubscription(
|
||||
product?.productId,
|
||||
false,
|
||||
null,
|
||||
-1,
|
||||
user.id,
|
||||
user.id
|
||||
);
|
||||
setBuying(false);
|
||||
eSendEvent(eCloseSheet);
|
||||
eSendEvent(eClosePremiumDialog);
|
||||
@@ -244,14 +198,7 @@ export const PricingPlans = ({
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
>
|
||||
{(Platform.OS === "android"
|
||||
? (monthlyPlan?.product as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0].formattedPrice
|
||||
: (monthlyPlan?.product as RNIap.SubscriptionIOS)
|
||||
.localizedPrice) ||
|
||||
(PremiumService.getMontlySub() as any).localizedPrice}
|
||||
/ mo
|
||||
{PremiumService.getMontlySub().localizedPrice} / mo
|
||||
</Paragraph>
|
||||
<Button
|
||||
onPress={() => {
|
||||
@@ -271,7 +218,7 @@ export const PricingPlans = ({
|
||||
<Button
|
||||
onPress={async () => {
|
||||
try {
|
||||
await db.user?.activateTrial();
|
||||
await db.user.activateTrial();
|
||||
eSendEvent(eClosePremiumDialog);
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
@@ -300,11 +247,7 @@ export const PricingPlans = ({
|
||||
}}
|
||||
size={SIZE.lg - 4}
|
||||
>
|
||||
{Platform.OS === "android"
|
||||
? (product.data as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0].formattedPrice
|
||||
: (product.data as RNIap.SubscriptionIOS)?.introductoryPrice}
|
||||
{product.data.introductoryPrice}
|
||||
<Paragraph
|
||||
style={{
|
||||
textDecorationLine: "line-through",
|
||||
@@ -312,11 +255,7 @@ export const PricingPlans = ({
|
||||
}}
|
||||
size={SIZE.sm}
|
||||
>
|
||||
{Platform.OS === "android"
|
||||
? (product.data as RNIap.SubscriptionAndroid)
|
||||
?.subscriptionOfferDetails[1].pricingPhases
|
||||
.pricingPhaseList?.[1].formattedPrice
|
||||
: (product.data as RNIap.SubscriptionIOS)?.localizedPrice}
|
||||
({product.data.localizedPrice})
|
||||
</Paragraph>{" "}
|
||||
for {product.cycleText}
|
||||
</Heading>
|
||||
@@ -324,9 +263,9 @@ export const PricingPlans = ({
|
||||
|
||||
{user && !product ? (
|
||||
<>
|
||||
{heading || (monthlyPlan?.info?.discount || 0) > 0 ? (
|
||||
{heading || monthlyPlan?.info?.discount > 0 ? (
|
||||
<>
|
||||
{monthlyPlan && (monthlyPlan?.info?.discount || 0) > 0 ? (
|
||||
{monthlyPlan && monthlyPlan?.info?.discount > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
@@ -361,10 +300,7 @@ export const PricingPlans = ({
|
||||
}}
|
||||
>
|
||||
<PricingItem
|
||||
onPress={() => {
|
||||
if (!monthlyPlan?.product) return;
|
||||
buySubscription(monthlyPlan?.product);
|
||||
}}
|
||||
onPress={() => buySubscription(monthlyPlan?.product)}
|
||||
compact={compact}
|
||||
product={{
|
||||
type: "monthly",
|
||||
@@ -383,10 +319,7 @@ export const PricingPlans = ({
|
||||
)}
|
||||
|
||||
<PricingItem
|
||||
onPress={() => {
|
||||
if (!yearlyPlan?.product) return;
|
||||
buySubscription(yearlyPlan?.product);
|
||||
}}
|
||||
onPress={() => buySubscription(yearlyPlan?.product)}
|
||||
compact={compact}
|
||||
product={{
|
||||
type: "yearly",
|
||||
@@ -413,7 +346,7 @@ export const PricingPlans = ({
|
||||
eSendEvent(eCloseSimpleDialog);
|
||||
setBuying(true);
|
||||
try {
|
||||
if (!(await getPromo(value as string)))
|
||||
if (!(await getPromo(value)))
|
||||
throw new Error("Error applying promo code");
|
||||
ToastEvent.show({
|
||||
heading: "Discount applied!",
|
||||
@@ -425,7 +358,7 @@ export const PricingPlans = ({
|
||||
setBuying(false);
|
||||
ToastEvent.show({
|
||||
heading: "Promo code invalid or expired",
|
||||
message: (e as Error).message,
|
||||
message: e.message,
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
@@ -494,10 +427,7 @@ export const PricingPlans = ({
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (!product?.data) return;
|
||||
buySubscription(product.data);
|
||||
}}
|
||||
onPress={() => buySubscription(product.data)}
|
||||
height={40}
|
||||
width="50%"
|
||||
type="accent"
|
||||
@@ -506,7 +436,7 @@ export const PricingPlans = ({
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
setProduct(undefined);
|
||||
setProduct(null);
|
||||
}}
|
||||
style={{
|
||||
marginTop: 5
|
||||
@@ -593,7 +523,7 @@ export const PricingPlans = ({
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/tos")
|
||||
openLinkInBrowser("https://notesnook.com/tos", colors)
|
||||
.catch(() => {})
|
||||
.then(() => {});
|
||||
}}
|
||||
@@ -608,7 +538,7 @@ export const PricingPlans = ({
|
||||
<Paragraph
|
||||
size={SIZE.xs}
|
||||
onPress={() => {
|
||||
openLinkInBrowser("https://notesnook.com/privacy")
|
||||
openLinkInBrowser("https://notesnook.com/privacy", colors)
|
||||
.catch(() => {})
|
||||
.then(() => {});
|
||||
}}
|
||||
@@ -531,7 +531,9 @@ export default function ReminderSheet({
|
||||
androidVariant="nativeAndroid"
|
||||
is24hourSource="locale"
|
||||
locale={
|
||||
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
|
||||
db.settings?.getTimeFormat() === "24-hour"
|
||||
? "en_GB.UTF8"
|
||||
: "en_US.UTF8"
|
||||
}
|
||||
mode={reminderMode === ReminderModes.Repeat ? "time" : "datetime"}
|
||||
/>
|
||||
|
||||
@@ -188,7 +188,7 @@ export const useActions = ({ close = () => null, item }) => {
|
||||
Notifications.displayNotification({
|
||||
title: item.title,
|
||||
message: item.headline || text,
|
||||
subtitle: "",
|
||||
subtitle: item.headline || text,
|
||||
bigText: html,
|
||||
ongoing: true,
|
||||
actions: ["UNPIN"],
|
||||
|
||||
@@ -229,25 +229,12 @@ async function saveEditorState() {
|
||||
MMKV.setString("appState", state);
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.Purchase} subscription
|
||||
*/
|
||||
|
||||
const onSuccessfulSubscription = async (subscription) => {
|
||||
console.log(
|
||||
"Subscription success!",
|
||||
subscription.transactionId,
|
||||
subscription.obfuscatedAccountIdAndroid,
|
||||
subscription.obfuscatedProfileIdAndroid
|
||||
);
|
||||
await PremiumService.subscriptions.set(subscription);
|
||||
await PremiumService.subscriptions.verify(subscription);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.PurchaseError} error
|
||||
*/
|
||||
const onSubscriptionError = async (error) => {
|
||||
ToastEvent.show({
|
||||
heading: "Failed to subscribe",
|
||||
@@ -503,9 +490,9 @@ export const useAppEvents = () => {
|
||||
);
|
||||
|
||||
const subscribeToIAPListeners = useCallback(async () => {
|
||||
RNIap.flushFailedPurchasesCachedAsPendingAndroid()
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
await RNIap.initConnection()
|
||||
.catch(() => null)
|
||||
.then(async () => {
|
||||
refValues.current.subsriptionSuccessListener =
|
||||
RNIap.purchaseUpdatedListener(onSuccessfulSubscription);
|
||||
refValues.current.subsriptionErrorListener =
|
||||
|
||||
@@ -384,16 +384,7 @@ const _TabsHolder = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
function updateSystemBars() {
|
||||
changeNavigationBarColor(colors.primary.background, isDark, true);
|
||||
StatusBar.setBackgroundColor("transparent");
|
||||
StatusBar.setTranslucent(true);
|
||||
StatusBar.setBarStyle(isDark ? "light-content" : "dark-content");
|
||||
}
|
||||
updateSystemBars();
|
||||
setTimeout(() => {
|
||||
updateSystemBars();
|
||||
}, 1000);
|
||||
changeNavigationBarColor(colors.primary.background, isDark, true);
|
||||
}, [colors.primary.background, isDark]);
|
||||
|
||||
return (
|
||||
@@ -415,7 +406,11 @@ const _TabsHolder = () => {
|
||||
: 0
|
||||
}}
|
||||
>
|
||||
<StatusBar translucent={true} backgroundColor="transparent" />
|
||||
<StatusBar
|
||||
barStyle={isDark ? "light-content" : "dark-content"}
|
||||
translucent={true}
|
||||
backgroundColor="transparent"
|
||||
/>
|
||||
|
||||
{!introCompleted ? (
|
||||
<NavigationStack />
|
||||
|
||||
@@ -115,7 +115,7 @@ const Editor = React.memo(
|
||||
} else {
|
||||
editor.commands.updateImage({
|
||||
hash: hash,
|
||||
dataurl: src
|
||||
src: src
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -167,7 +167,7 @@ const Editor = React.memo(
|
||||
globalThis.noToolbar=${noToolbar};
|
||||
globalThis.noHeader=${noHeader};
|
||||
`}
|
||||
useSharedProcessPool={false}
|
||||
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
onContentProcessDidTerminate={onError}
|
||||
|
||||
@@ -113,7 +113,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
};
|
||||
|
||||
setSessionId = async (id: string | null) =>
|
||||
await this.doAsync(`globalThis.sessionId = "${id}";`);
|
||||
await this.doAsync(`globalThis.sessionId = "${id}"`);
|
||||
|
||||
setStatus = async (date: string | undefined, saved: string) =>
|
||||
await this.doAsync(
|
||||
@@ -211,20 +211,9 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
);
|
||||
};
|
||||
|
||||
insertImage = async (
|
||||
image: Omit<ImageAttributes, "bloburl"> & {
|
||||
dataurl: string;
|
||||
}
|
||||
) => {
|
||||
insertImage = async (image: ImageAttributes) => {
|
||||
await this.doAsync(
|
||||
`const image = toBlobURL("${image.dataurl}", "${image.hash}");
|
||||
editor && editor.commands.insertImage({
|
||||
...${JSON.stringify({
|
||||
...image,
|
||||
dataurl: undefined
|
||||
})},
|
||||
bloburl: image
|
||||
})`
|
||||
`editor && editor.commands.insertImage(${JSON.stringify(image)})`
|
||||
);
|
||||
};
|
||||
|
||||
@@ -236,20 +225,11 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
);
|
||||
};
|
||||
|
||||
updateImage = async ({
|
||||
hash,
|
||||
dataurl
|
||||
}: Partial<Omit<ImageAttributes, "bloburl">> & {
|
||||
dataurl: string;
|
||||
}) => {
|
||||
updateImage = async ({ src, hash }: Partial<ImageAttributes>) => {
|
||||
await this.doAsync(
|
||||
`const image = toBlobURL("${dataurl}", "${hash}");
|
||||
editor && editor.commands.updateImage(${JSON.stringify({
|
||||
`editor && editor.commands.updateImage(${JSON.stringify({
|
||||
hash
|
||||
})}, {
|
||||
...${JSON.stringify({ hash, preventUpdate: true })},
|
||||
bloburl: image
|
||||
})`
|
||||
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { editorController, editorState } from "./utils";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
|
||||
const showEncryptionSheet = (file) => {
|
||||
presentSheet({
|
||||
@@ -231,16 +230,6 @@ const handleImageResponse = async (response, options) => {
|
||||
});
|
||||
|
||||
let fileName = image.originalFileName || image.fileName;
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
});
|
||||
|
||||
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
|
||||
const isPng = /(png)/g.test(image.type);
|
||||
const isJpeg = /(jpeg|jpg)/g.test(image.type);
|
||||
@@ -254,6 +243,15 @@ const handleImageResponse = async (response, options) => {
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
title: fileName,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
});
|
||||
};
|
||||
|
||||
export async function attachFile(uri, hash, type, filename, options) {
|
||||
@@ -271,10 +269,6 @@ export async function attachFile(uri, hash, type, filename, options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.reupload && exists) {
|
||||
options.reupload = (await filesystem.getUploadedFileSize(hash)) <= 0;
|
||||
}
|
||||
|
||||
if (!exists || options?.reupload) {
|
||||
let key = await db.attachments.generateKey();
|
||||
encryptionInfo = await Sodium.encryptFile(key, {
|
||||
|
||||
@@ -205,6 +205,7 @@ export const useEditorEvents = (
|
||||
fullscreen,
|
||||
isPremium,
|
||||
readonly,
|
||||
editor.sessionId,
|
||||
editor.loading,
|
||||
deviceMode,
|
||||
tools,
|
||||
@@ -228,8 +229,6 @@ export const useEditorEvents = (
|
||||
logger.info("editor handled back event", editorHandledBack);
|
||||
return;
|
||||
}
|
||||
editorState().currentlyEditing = false;
|
||||
editor.reset();
|
||||
setTimeout(async () => {
|
||||
if (deviceMode !== "mobile" && fullscreen) {
|
||||
if (fullscreen) {
|
||||
@@ -242,10 +241,14 @@ export const useEditorEvents = (
|
||||
editorState().movedAway = true;
|
||||
tabBarRef.current?.goToPage(0);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 500);
|
||||
setImmediate(() => {
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 500);
|
||||
});
|
||||
editorState().currentlyEditing = false;
|
||||
editor.reset();
|
||||
}, 1);
|
||||
}, [editor, deviceMode, fullscreen]);
|
||||
|
||||
@@ -341,12 +344,8 @@ export const useEditorEvents = (
|
||||
});
|
||||
}
|
||||
|
||||
if (editorMessage.type === EventTypes.back) {
|
||||
return onBackPress();
|
||||
}
|
||||
|
||||
if (
|
||||
editorMessage.sessionId !== editor.sessionId.current &&
|
||||
editorMessage.sessionId !== editor.sessionId &&
|
||||
editorMessage.type !== EditorEvents.status
|
||||
) {
|
||||
return;
|
||||
@@ -432,6 +431,9 @@ export const useEditorEvents = (
|
||||
editorState().isFullscreen = true;
|
||||
eSendEvent(eOpenFullscreenEditor);
|
||||
break;
|
||||
case EventTypes.back:
|
||||
onBackPress();
|
||||
break;
|
||||
case EventTypes.link:
|
||||
openLinkInBrowser(editorMessage.value as string);
|
||||
break;
|
||||
|
||||
@@ -63,7 +63,8 @@ export const useEditor = (
|
||||
const theme = useThemeEngineStore((state) => state.theme);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const sessionIdRef = useRef(makeSessionId());
|
||||
const [sessionId, setSessionId] = useState<string>(makeSessionId());
|
||||
const sessionIdRef = useRef(sessionId);
|
||||
const editorRef = useRef<WebView>(null);
|
||||
const currentNote = useRef<NoteType | null>();
|
||||
const currentContent = useRef<Content | null>();
|
||||
@@ -98,6 +99,10 @@ export const useEditor = (
|
||||
postMessage(EditorEvents.theme, theme);
|
||||
}, [theme, postMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
sessionIdRef.current = sessionId;
|
||||
}, [sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
commands.setTags(currentNote.current);
|
||||
}, [commands, tags]);
|
||||
@@ -137,7 +142,6 @@ export const useEditor = (
|
||||
clearTimeout(timers.current["loading-images"]);
|
||||
sessionHistoryId.current = undefined;
|
||||
saveCount.current = 0;
|
||||
loadingState.current = undefined;
|
||||
lock.current = false;
|
||||
useEditorStore.getState().setReadonly(false);
|
||||
resetContent && postMessage(EditorEvents.title, "");
|
||||
@@ -147,8 +151,8 @@ export const useEditor = (
|
||||
|
||||
if (resetState) {
|
||||
const newSessionId = makeSessionId();
|
||||
sessionIdRef.current = newSessionId;
|
||||
await commands.setSessionId(newSessionId);
|
||||
setSessionId(newSessionId);
|
||||
|
||||
isDefaultEditor &&
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
@@ -359,6 +363,7 @@ export const useEditor = (
|
||||
if (item && item.type === "new") {
|
||||
currentNote.current && (await reset());
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
setSessionId(nextSessionId);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
await commands.setSessionId(nextSessionId);
|
||||
@@ -400,10 +405,12 @@ export const useEditor = (
|
||||
}
|
||||
lastContentChangeTime.current = item.dateEdited;
|
||||
const nextSessionId = makeSessionId(item as NoteType);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
lockedSessionId.current = nextSessionId;
|
||||
sessionHistoryId.current = Date.now();
|
||||
await commands.setSessionId(nextSessionId);
|
||||
setSessionId(nextSessionId);
|
||||
|
||||
commands.setSessionId(nextSessionId);
|
||||
sessionIdRef.current = nextSessionId;
|
||||
currentNote.current = item as NoteType;
|
||||
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
@@ -631,7 +638,7 @@ export const useEditor = (
|
||||
|
||||
useEffect(() => {
|
||||
state.current.saveCount = 0;
|
||||
}, [loading]);
|
||||
}, [sessionId, loading]);
|
||||
|
||||
const onReady = useCallback(async () => {
|
||||
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
|
||||
@@ -683,7 +690,8 @@ export const useEditor = (
|
||||
loading,
|
||||
setLoading,
|
||||
state,
|
||||
sessionId: sessionIdRef,
|
||||
sessionId,
|
||||
setSessionId,
|
||||
note: currentNote,
|
||||
onReady,
|
||||
saveContent,
|
||||
|
||||
@@ -88,13 +88,6 @@ export const Subscription = () => {
|
||||
}
|
||||
};
|
||||
|
||||
function getPrice() {
|
||||
return Platform.OS === "android"
|
||||
? monthlyPlan?.product?.subscriptionOfferDetails[0].pricingPhases
|
||||
.pricingPhaseList?.[0].formattedPrice
|
||||
: monthlyPlan?.product?.localizedPrice;
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{isNotPro ? (
|
||||
@@ -121,8 +114,12 @@ export const Subscription = () => {
|
||||
Config.GITHUB_RELEASE !== "true"
|
||||
? "Resubscribe from Google Playstore"
|
||||
: user.subscription?.type === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED
|
||||
? `Resubscribe to Pro (${getPrice() || "$4.49"} / mo)`
|
||||
: `Get Pro (${getPrice() || "$4.49"} / mo)`
|
||||
? `Resubscribe to Pro (${
|
||||
monthlyPlan?.product?.localizedPrice || "$4.49"
|
||||
} / mo)`
|
||||
: `Get Pro (${
|
||||
monthlyPlan?.product?.localizedPrice || "$4.49"
|
||||
} / mo)`
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -39,10 +39,6 @@ import { eSendEvent, presentSheet, ToastEvent } from "./event-manager";
|
||||
|
||||
import SettingsService from "./settings";
|
||||
let premiumStatus = 0;
|
||||
|
||||
/**
|
||||
* @type {RNIap.Subscription[]}
|
||||
*/
|
||||
let products = [];
|
||||
let user = null;
|
||||
|
||||
@@ -72,9 +68,7 @@ async function setPremiumStatus() {
|
||||
}
|
||||
try {
|
||||
await RNIap.initConnection();
|
||||
products = await RNIap.getSubscriptions({
|
||||
skus: itemSkus
|
||||
});
|
||||
products = await RNIap.getSubscriptions(itemSkus);
|
||||
} catch (e) {
|
||||
console.log("subscriptions: ", e);
|
||||
}
|
||||
@@ -229,21 +223,12 @@ const showVerifyEmailDialog = () => {
|
||||
};
|
||||
|
||||
const subscriptions = {
|
||||
/**
|
||||
*
|
||||
* @returns {RNIap.Purchase} subscription
|
||||
*/
|
||||
get: async () => {
|
||||
if (Platform.OS === "android") return;
|
||||
let _subscriptions = MMKV.getString("subscriptionsIOS");
|
||||
if (!_subscriptions) return [];
|
||||
return JSON.parse(_subscriptions);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.Purchase} subscription
|
||||
* @returns
|
||||
*/
|
||||
set: async (subscription) => {
|
||||
if (Platform.OS === "android") return;
|
||||
let _subscriptions = MMKV.getString("subscriptionsIOS");
|
||||
@@ -278,10 +263,6 @@ const subscriptions = {
|
||||
MMKV.setString("subscriptionsIOS", JSON.stringify(_subscriptions));
|
||||
}
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {RNIap.Purchase} subscription
|
||||
*/
|
||||
verify: async (subscription) => {
|
||||
if (Platform.OS === "android") return;
|
||||
|
||||
|
||||
@@ -67,10 +67,15 @@ export const deleteItems = async (item, context) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
item &&
|
||||
item.id &&
|
||||
useSelectionStore.getState().selectedItemsList.length === 0
|
||||
) {
|
||||
useSelectionStore.getState().setSelectedItem(item);
|
||||
}
|
||||
|
||||
const selectedItemsList = item
|
||||
? [item]
|
||||
: useSelectionStore.getState().selectedItemsList;
|
||||
const { selectedItemsList } = useSelectionStore.getState();
|
||||
|
||||
let notes = selectedItemsList.filter((i) => i.type === "note");
|
||||
let notebooks = selectedItemsList.filter((i) => i.type === "notebook");
|
||||
@@ -174,9 +179,7 @@ export const deleteItems = async (item, context) => {
|
||||
});
|
||||
}
|
||||
Navigation.queueRoutesForUpdate();
|
||||
if (!item) {
|
||||
useSelectionStore.getState().clearSelection();
|
||||
}
|
||||
useSelectionStore.getState().clearSelection();
|
||||
useMenuStore.getState().setMenuPins();
|
||||
useMenuStore.getState().setColorNotes();
|
||||
SearchService.updateAndSearch();
|
||||
|
||||
@@ -66,37 +66,17 @@ async function createNotes(bundle) {
|
||||
const sessionId = bundle.note.sessionId;
|
||||
const id = await db.notes.add(bundle.note);
|
||||
|
||||
if (!bundle.notebooks || !bundle.notebooks.length) {
|
||||
const defaultNotebook = db.settings?.getDefaultNotebook();
|
||||
if (defaultNotebook) {
|
||||
if (!defaultNotebook.topic) {
|
||||
db.relations.add(
|
||||
{ type: "notebook", id: defaultNotebook.id },
|
||||
{ id, type: "note" }
|
||||
);
|
||||
} else {
|
||||
db.notes.addToNotebook(
|
||||
{
|
||||
id: defaultNotebook?.id,
|
||||
topic: defaultNotebook.topic
|
||||
},
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const item of bundle.notebooks) {
|
||||
if (item.type === "notebook") {
|
||||
db.relations.add(item, { id, type: "note" });
|
||||
} else {
|
||||
db.notes.addToNotebook(
|
||||
{
|
||||
id: item.notebookId,
|
||||
topic: item.id
|
||||
},
|
||||
id
|
||||
);
|
||||
}
|
||||
for (const item of bundle.notebooks) {
|
||||
if (item.type === "notebook") {
|
||||
db.relations.add(item, { id, type: "note" });
|
||||
} else {
|
||||
db.notes.addToNotebook(
|
||||
{
|
||||
id: item.notebookId,
|
||||
topic: item.id
|
||||
},
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 2068
|
||||
versionCode 2065
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -8,10 +8,6 @@ import com.ammarahmed.mmkv.MMKV;
|
||||
|
||||
public class OnClearFromRecentService extends Service {
|
||||
|
||||
static {
|
||||
System.loadLibrary("rnmmkv");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
@@ -19,6 +15,7 @@ public class OnClearFromRecentService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@@ -34,10 +31,8 @@ public class OnClearFromRecentService extends Service {
|
||||
MMKV mmkv = MMKV.mmkvWithID("default",MMKV.SINGLE_PROCESS_MODE);
|
||||
mmkv.removeValueForKey("appState");
|
||||
stopSelf();
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
|
||||
} catch (Exception e) {
|
||||
|
||||
|
||||
}
|
||||
//System.exit(0);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
ext.kotlinVersion = '1.8.0'
|
||||
ext.kotlinVersion = '1.6.10'
|
||||
androidXAnnotation = "1.1.0"
|
||||
androidXCoreVersion = "1.7.0"
|
||||
androidXCore = "1.7.0"
|
||||
|
||||
@@ -997,7 +997,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2060;
|
||||
CURRENT_PROJECT_VERSION = 2058;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1071,7 +1071,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.9;
|
||||
MARKETING_VERSION = 2.6.7;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1102,7 +1102,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2060;
|
||||
CURRENT_PROJECT_VERSION = 2058;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1176,7 +1176,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.9;
|
||||
MARKETING_VERSION = 2.6.7;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1335,7 +1335,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2060;
|
||||
CURRENT_PROJECT_VERSION = 2058;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1347,7 +1347,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.9;
|
||||
MARKETING_VERSION = 2.6.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1378,7 +1378,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2060;
|
||||
CURRENT_PROJECT_VERSION = 2058;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1391,7 +1391,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.9;
|
||||
MARKETING_VERSION = 2.6.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1421,7 +1421,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2060;
|
||||
CURRENT_PROJECT_VERSION = 2058;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1495,7 +1495,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.9;
|
||||
MARKETING_VERSION = 2.6.7;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1526,7 +1526,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2060;
|
||||
CURRENT_PROJECT_VERSION = 2058;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1601,7 +1601,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.9;
|
||||
MARKETING_VERSION = 2.6.7;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -477,7 +477,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNGestureHandler (2.12.0):
|
||||
- React-Core
|
||||
- RNIap (12.11.0):
|
||||
- RNIap (7.5.6):
|
||||
- React-Core
|
||||
- RNKeychain (4.0.5):
|
||||
- React
|
||||
@@ -911,7 +911,7 @@ SPEC CHECKSUMS:
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a
|
||||
RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5
|
||||
RNIap: fc9af04ee706894a80c9d8f979bae930b0dee191
|
||||
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
|
||||
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"react-native-get-random-values": "^1.7.0",
|
||||
"react-native-gzip": "1.1.0",
|
||||
"react-native-html-to-pdf-lite": "^0.9.1",
|
||||
"react-native-iap": "12.11.0",
|
||||
"react-native-iap": "7.5.6",
|
||||
"react-native-image-picker": "4.1.2",
|
||||
"react-native-in-app-review": "4.3.3",
|
||||
"react-native-keychain": "4.0.5",
|
||||
|
||||
892
apps/mobile/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.6.9",
|
||||
"version": "2.6.7",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -41,7 +41,6 @@
|
||||
"@trpc/react-query": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.0",
|
||||
"react-native-iap": "12.11.0"
|
||||
"react-native": "0.72.0"
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import ShareExtension from "@ammarahmed/react-native-share-extension";
|
||||
import { getPreviewData } from "@flyerhq/react-native-link-preview";
|
||||
import { formatBytes } from "@notesnook/common";
|
||||
import { isImage } from "@notesnook/core/dist/utils/filename";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { parseHTML } from "@notesnook/core/dist/utils/html-parser";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -53,9 +53,10 @@ import { getElevationStyle } from "../app/utils/elevation";
|
||||
import { eOnLoadNote } from "../app/utils/events";
|
||||
import { NoteBundle } from "../app/utils/note-bundle";
|
||||
import { Editor } from "./editor";
|
||||
import { HtmlLoadingWebViewAgent, fetchHandle } from "./fetch-webview";
|
||||
import { Search } from "./search";
|
||||
import { initDatabase, useShareStore } from "./store";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { HtmlLoadingWebViewAgent, fetchHandle } from "./fetch-webview";
|
||||
|
||||
const getLinkPreview = (url) => {
|
||||
return getPreviewData(url, 5000);
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
"@notesnook/theme": ["../../packages/theme"],
|
||||
"@types/react": ["./node_modules/@types/react"]
|
||||
},
|
||||
"incremental": true,
|
||||
"maxNodeModuleJsDepth": 5,
|
||||
"downlevelIteration": true
|
||||
"incremental": true
|
||||
},
|
||||
"exclude": ["native"]
|
||||
}
|
||||
|
||||
21
apps/theme-builder/package-lock.json
generated
@@ -1377,7 +1377,7 @@
|
||||
},
|
||||
"../web": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.5",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@aws-sdk/util-base64-browser": "^3.208.0",
|
||||
@@ -1446,7 +1446,6 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@swc/core": "1.3.61",
|
||||
"@trpc/server": "10.38.3",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
@@ -1457,8 +1456,7 @@
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/react-modal": "3.13.1",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/wicg-file-system-access": "^2020.9.6",
|
||||
"@vitejs/plugin-react-swc": "3.3.2",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
@@ -1472,14 +1470,14 @@
|
||||
"otplib": "^12.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"rollup": "^3.29.4",
|
||||
"rollup": "^3.27.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"swc-plugin-react-remove-properties": "^0.1.4",
|
||||
"vite": "^4.5.0",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-env-compatible": "^1.1.1",
|
||||
"vite-plugin-pwa": "^0.16.3",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.34.6",
|
||||
"vitest": "^0.32.0",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
@@ -2408,6 +2406,15 @@
|
||||
"@trpc/server": "10.38.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@trpc/server": {
|
||||
"version": "10.38.3",
|
||||
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.38.3.tgz",
|
||||
"integrity": "sha512-9s8/kwo2IDB5hwB2SKZZrfevRhdb1f9fdXtIYd3lbQuf2jQaC/LyQuHaIQjDQoUx9updBfsHXcFFPiCP1DL6pg==",
|
||||
"funding": [
|
||||
"https://trpc.io/sponsor"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.1",
|
||||
"dev": true,
|
||||
|
||||
38829
apps/web/package-lock.json
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.7",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -74,7 +74,6 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@swc/core": "1.3.61",
|
||||
"@trpc/server": "10.38.3",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
@@ -85,8 +84,7 @@
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/react-modal": "3.13.1",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"@types/wicg-file-system-access": "^2020.9.6",
|
||||
"@vitejs/plugin-react-swc": "3.3.2",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
@@ -100,14 +98,14 @@
|
||||
"otplib": "^12.0.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"rollup": "^3.29.4",
|
||||
"rollup": "^3.27.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"swc-plugin-react-remove-properties": "^0.1.4",
|
||||
"vite": "^4.5.0",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-env-compatible": "^1.1.1",
|
||||
"vite-plugin-pwa": "^0.16.3",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.34.6",
|
||||
"vitest": "^0.32.0",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -129,6 +129,7 @@ export default function AppEffects({ setShow }: AppEffectsProps) {
|
||||
) {
|
||||
const [key, status] = getProcessingStatusFromType(type);
|
||||
|
||||
console.log("handleDownloadUploadProgresss", key, status, current, total);
|
||||
if (current === total) {
|
||||
removeStatus(key);
|
||||
} else {
|
||||
|
||||
@@ -24,7 +24,6 @@ import Config from "./utils/config";
|
||||
|
||||
import { initalizeLogger, logger } from "./utils/logger";
|
||||
import { AuthProps } from "./views/auth";
|
||||
import { initializeFeatureChecks } from "./utils/feature-check";
|
||||
|
||||
type Route<TProps = null> = {
|
||||
component: () => Promise<{
|
||||
@@ -143,8 +142,6 @@ function isSessionExpired(path: Routes): RouteWithPath<AuthProps> | null {
|
||||
|
||||
export async function init() {
|
||||
await initalizeLogger();
|
||||
await initializeFeatureChecks();
|
||||
|
||||
const { path, route } = getRoute();
|
||||
return { ...route, path };
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { lazify } from "../utils/lazify";
|
||||
import FS from "../interfaces/fs";
|
||||
import { db } from "./db";
|
||||
|
||||
async function download(hash: string) {
|
||||
@@ -42,15 +42,13 @@ export async function saveAttachment(hash: string) {
|
||||
if (!response) return;
|
||||
|
||||
const { attachment, key } = response;
|
||||
await lazify(import("../interfaces/fs"), ({ default: FS }) =>
|
||||
FS.saveFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
})
|
||||
);
|
||||
await FS.saveFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
});
|
||||
}
|
||||
|
||||
type OutputTypeToReturnType = {
|
||||
@@ -69,15 +67,13 @@ export async function downloadAttachment<
|
||||
if (type === "base64" || type === "text")
|
||||
return (await db.attachments?.read(hash, type)) as TOutputType;
|
||||
|
||||
const blob = await lazify(import("../interfaces/fs"), ({ default: FS }) =>
|
||||
FS.decryptFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
})
|
||||
);
|
||||
const blob = await FS.decryptFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
});
|
||||
|
||||
if (!blob) return;
|
||||
return blob as TOutputType;
|
||||
@@ -88,9 +84,7 @@ export async function checkAttachment(hash: string) {
|
||||
if (!attachment) return { failed: "Attachment not found." };
|
||||
|
||||
try {
|
||||
const size = await lazify(import("../interfaces/fs"), ({ default: FS }) =>
|
||||
FS.getUploadedFileSize(hash)
|
||||
);
|
||||
const size = await FS.getUploadedFileSize(hash);
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
} catch (e) {
|
||||
return { failed: e instanceof Error ? e.message : "Unknown error." };
|
||||
|
||||
@@ -21,7 +21,6 @@ import { EventSourcePolyfill as EventSource } from "event-source-polyfill";
|
||||
import { DatabasePersistence, NNStorage } from "../interfaces/storage";
|
||||
import { logger } from "../utils/logger";
|
||||
import type Database from "@notesnook/core/dist/api";
|
||||
import { showMigrationDialog } from "./dialog-controller";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
@@ -71,6 +70,7 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
logger.measure("Database initialization");
|
||||
|
||||
if (db.migrations?.required()) {
|
||||
const { showMigrationDialog } = await import("./dialog-controller");
|
||||
await showMigrationDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,21 +41,12 @@ export async function exportToPDF(
|
||||
iframe.style.width = "0";
|
||||
iframe.style.height = "0";
|
||||
iframe.style.border = "0";
|
||||
iframe.loading = "eager";
|
||||
|
||||
iframe.onload = async () => {
|
||||
iframe.onload = () => {
|
||||
if (!iframe.contentWindow) return;
|
||||
if (iframe.contentDocument) iframe.contentDocument.title = title;
|
||||
iframe.contentWindow.onbeforeunload = () => closePrint(false);
|
||||
iframe.contentWindow.onafterprint = () => closePrint(true);
|
||||
|
||||
if (
|
||||
iframe.contentDocument &&
|
||||
!!iframe.contentDocument.querySelector(".math-block,.math-inline")
|
||||
) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
iframe.contentWindow.print();
|
||||
};
|
||||
|
||||
@@ -128,11 +119,7 @@ export async function exportNotes(
|
||||
return await exportToPDF(note.title, exported);
|
||||
}
|
||||
|
||||
files.push({
|
||||
filename: note.title,
|
||||
content: exported,
|
||||
date: note.dateEdited
|
||||
});
|
||||
files.push({ filename: note.title, content: exported });
|
||||
}
|
||||
|
||||
if (!files.length) return false;
|
||||
|
||||
@@ -290,6 +290,7 @@ const AttachmentMenuItems: (
|
||||
key: "download",
|
||||
title: status?.type === "download" ? "Cancel download" : "Download",
|
||||
icon: Download.path,
|
||||
isDisabled: !attachment.dateUploaded,
|
||||
onClick: async () => {
|
||||
const isDownloading = status?.type === "download";
|
||||
if (isDownloading) {
|
||||
|
||||
@@ -39,7 +39,7 @@ function Header(props: HeaderProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{id && (
|
||||
{!readonly && id && (
|
||||
<Flex
|
||||
sx={{ lineHeight: 2.5, alignItems: "center", flexWrap: "wrap" }}
|
||||
data-test-id="tags"
|
||||
@@ -55,21 +55,19 @@ function Header(props: HeaderProps) {
|
||||
styles={{ container: { mr: 1 }, text: { fontSize: "body" } }}
|
||||
/>
|
||||
))}
|
||||
{!readonly && (
|
||||
<Autosuggest
|
||||
sessionId={id}
|
||||
filter={(query) =>
|
||||
db.lookup?.tags(filterableTags, query).slice(0, 10) || []
|
||||
}
|
||||
onAdd={(value) => setTag(value)}
|
||||
onSelect={(item) => setTag(item.title)}
|
||||
onRemove={() => {
|
||||
if (tags.length <= 0) return;
|
||||
setTag(tags[tags.length - 1]);
|
||||
}}
|
||||
defaultItems={filterableTags?.slice(0, 10) || []}
|
||||
/>
|
||||
)}
|
||||
<Autosuggest
|
||||
sessionId={id}
|
||||
filter={(query) =>
|
||||
db.lookup?.tags(filterableTags, query).slice(0, 10) || []
|
||||
}
|
||||
onAdd={(value) => setTag(value)}
|
||||
onSelect={(item) => setTag(item.title)}
|
||||
onRemove={() => {
|
||||
if (tags.length <= 0) return;
|
||||
setTag(tags[tags.length - 1]);
|
||||
}}
|
||||
defaultItems={filterableTags?.slice(0, 10) || []}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</>
|
||||
@@ -88,7 +86,6 @@ type AutosuggestProps = {
|
||||
export function Autosuggest(props: AutosuggestProps) {
|
||||
const { sessionId, filter, onRemove, onSelect, onAdd, defaultItems } = props;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const arrowDown = useRef<boolean>();
|
||||
const filteredItems = useRef<any[]>([]);
|
||||
const { openMenu, closeMenu, isOpen } = useMenuTrigger();
|
||||
const clearInput = useCallback(() => {
|
||||
@@ -181,13 +178,8 @@ export function Autosuggest(props: AutosuggestProps) {
|
||||
data-test-id="editor-tag-input"
|
||||
onFocus={() => {
|
||||
const text = getInputValue();
|
||||
console.log(defaultItems);
|
||||
if (!text) onOpenMenu(defaultItems.slice());
|
||||
else onOpenMenu([]);
|
||||
}}
|
||||
onClick={() => {
|
||||
const text = getInputValue();
|
||||
if (!text) onOpenMenu(defaultItems.slice());
|
||||
else onOpenMenu([]);
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
@@ -199,17 +191,22 @@ export function Autosuggest(props: AutosuggestProps) {
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
const text = getInputValue();
|
||||
if (e.key === "Enter" && !!text && isOpen && !arrowDown.current) {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
!!text &&
|
||||
isOpen &&
|
||||
filteredItems.current.length <= 0
|
||||
) {
|
||||
onAction("add", text);
|
||||
} else if (e.key === "Enter" && !!text && isOpen) {
|
||||
onAction("select", filteredItems.current[0]);
|
||||
} else if (!text && e.key === "Backspace") {
|
||||
onRemove();
|
||||
closeMenu();
|
||||
} else if (e.key === "Escape") {
|
||||
arrowDown.current = false;
|
||||
closeMenu();
|
||||
e.stopPropagation();
|
||||
} else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
||||
arrowDown.current = true;
|
||||
if (e.key === "ArrowDown" && !text) onOpenMenu(defaultItems.slice());
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
@@ -23,6 +23,7 @@ import { db } from "../../common/db";
|
||||
import { showBuyDialog } from "../../common/dialog-controller";
|
||||
import { TaskManager } from "../../common/task-manager";
|
||||
import { isUserPremium } from "../../hooks/use-is-user-premium";
|
||||
import fs from "../../interfaces/fs";
|
||||
import { showToast } from "../../utils/toast";
|
||||
import { showFilePicker } from "../../utils/file-picker";
|
||||
|
||||
@@ -150,29 +151,25 @@ async function addAttachment(
|
||||
dataurl: string | undefined,
|
||||
options: AddAttachmentOptions = {}
|
||||
): Promise<Attachment> {
|
||||
const { default: FS } = await import("../../interfaces/fs");
|
||||
const { expectedFileHash, showProgress = true } = options;
|
||||
let forceWrite = options.forceWrite;
|
||||
const { expectedFileHash, forceWrite, showProgress = true } = options;
|
||||
|
||||
const action = async () => {
|
||||
const reader = file.stream().getReader();
|
||||
const { hash, type: hashType } = await FS.hashStream(reader);
|
||||
const reader: ReadableStreamReader<Uint8Array> = (
|
||||
file.stream() as unknown as ReadableStream<Uint8Array>
|
||||
).getReader();
|
||||
|
||||
const { hash, type: hashType } = await fs.hashStream(reader);
|
||||
reader.releaseLock();
|
||||
|
||||
if (expectedFileHash && hash !== expectedFileHash)
|
||||
throw new Error(
|
||||
`Please select the same file for reuploading. Expected hash ${expectedFileHash} but got ${hash}.`
|
||||
);
|
||||
|
||||
const exists = db.attachments?.exists(hash);
|
||||
if (!forceWrite && exists) {
|
||||
forceWrite = (await FS.getUploadedFileSize(hash)) <= 0;
|
||||
}
|
||||
|
||||
if (forceWrite || !exists) {
|
||||
const key: SerializedKey = await getEncryptionKey();
|
||||
|
||||
const output = await FS.writeEncryptedFile(file, key, hash);
|
||||
const output = await fs.writeEncryptedFile(file, key, hash);
|
||||
if (!output) throw new Error("Could not encrypt file.");
|
||||
|
||||
if (forceWrite && exists) await db.attachments?.reset(hash);
|
||||
|
||||
@@ -57,7 +57,6 @@ import { store as editorstore } from "../../stores/editor-store";
|
||||
import { ScopedThemeProvider } from "../theme-provider";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import { toBlobURL } from "@notesnook/editor/dist/utils/downloader";
|
||||
|
||||
type OnChangeHandler = (
|
||||
id: string | undefined,
|
||||
@@ -447,7 +446,7 @@ function toIEditor(editor: Editor): IEditor {
|
||||
if (file.dataurl) {
|
||||
editor.current?.commands.insertImage({
|
||||
...file,
|
||||
bloburl: toBlobURL(file.dataurl, file.hash)
|
||||
dataurl: file.dataurl
|
||||
});
|
||||
} else editor.current?.commands.insertAttachment(file);
|
||||
},
|
||||
@@ -456,7 +455,7 @@ function toIEditor(editor: Editor): IEditor {
|
||||
loadImage: (hash, dataurl) =>
|
||||
editor.current?.commands.updateImage(
|
||||
{ hash },
|
||||
{ hash, bloburl: toBlobURL(dataurl, hash), preventUpdate: true }
|
||||
{ hash, dataurl, preventUpdate: true }
|
||||
),
|
||||
sendAttachmentProgress: (hash, type, progress) =>
|
||||
editor.current?.commands.setAttachmentProgress({
|
||||
|
||||
@@ -26,8 +26,6 @@ import useTablet from "../../hooks/use-tablet";
|
||||
import { useEditorConfig } from "./context";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
import { AppEventManager, AppEvents } from "../../common/app-events";
|
||||
import { replaceDateTime } from "@notesnook/editor/dist/extensions/date-time";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
|
||||
type TitleBoxProps = {
|
||||
readonly: boolean;
|
||||
@@ -40,9 +38,6 @@ function TitleBox(props: TitleBoxProps) {
|
||||
const isMobile = useMobile();
|
||||
const isTablet = useTablet();
|
||||
const { editorConfig } = useEditorConfig();
|
||||
const dateFormat = useSettingsStore((store) => store.dateFormat);
|
||||
const timeFormat = useSettingsStore((store) => store.timeFormat);
|
||||
|
||||
const fontFamily = useMemo(
|
||||
() => getFontById(editorConfig.fontFamily)?.font || "heading",
|
||||
[editorConfig.fontFamily]
|
||||
@@ -116,11 +111,6 @@ function TitleBox(props: TitleBoxProps) {
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const { sessionId, id } = store.get().session;
|
||||
e.target.value = replaceDateTime(
|
||||
e.target.value,
|
||||
dateFormat,
|
||||
timeFormat
|
||||
);
|
||||
debouncedOnTitleChange(sessionId, id, e.target.value);
|
||||
updateFontSize(e.target.value.length);
|
||||
}}
|
||||
|
||||
@@ -110,11 +110,10 @@ function Properties(props) {
|
||||
"reminder"
|
||||
);
|
||||
const allNotebooks = useMemo(
|
||||
() =>
|
||||
[
|
||||
...notebooks.map((ref) => db.notebooks.notebook(ref.id)?.data),
|
||||
...db.relations.to({ id: sessionId, type: "note" }, "notebook")
|
||||
].filter(Boolean),
|
||||
() => [
|
||||
...notebooks.map((ref) => db.notebooks.notebook(ref.id)?.data),
|
||||
...db.relations.to({ id: sessionId, type: "note" }, "notebook")
|
||||
],
|
||||
[sessionId, notebooks]
|
||||
);
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ function StatusBar() {
|
||||
const isLoggedIn = useUserStore((state) => state.isLoggedIn);
|
||||
const statuses = useStatus();
|
||||
const updateStatus = useAutoUpdater();
|
||||
const isFocusMode = useAppStore((state) => state.isFocusMode);
|
||||
|
||||
return (
|
||||
<ScopedThemeProvider
|
||||
@@ -64,130 +63,124 @@ function StatusBar() {
|
||||
}}
|
||||
px={2}
|
||||
>
|
||||
{isFocusMode ? (
|
||||
<Flex />
|
||||
) : (
|
||||
<Flex>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() =>
|
||||
user?.isEmailConfirmed
|
||||
? navigate("/settings")
|
||||
: hashNavigate("/email/verify")
|
||||
}
|
||||
variant="statusitem"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
}}
|
||||
>
|
||||
<Circle
|
||||
size={7}
|
||||
color={
|
||||
user?.isEmailConfirmed
|
||||
? "var(--icon-success)"
|
||||
: "var(--icon-error)"
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
className="selectable"
|
||||
variant="subBody"
|
||||
ml={1}
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
{user?.email}
|
||||
{user?.isEmailConfirmed ? "" : " (not verified)"}
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<SyncStatus />
|
||||
</>
|
||||
) : isLoggedIn === false ? (
|
||||
<Flex>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() =>
|
||||
user?.isEmailConfirmed
|
||||
? navigate("/settings")
|
||||
: hashNavigate("/email/verify")
|
||||
}
|
||||
variant="statusitem"
|
||||
onClick={() => hardNavigate("/login")}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
}}
|
||||
data-test-id="not-logged-in"
|
||||
>
|
||||
<Circle size={7} color="var(--icon-error)" />
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
Not logged in
|
||||
<Circle
|
||||
size={7}
|
||||
color={
|
||||
user?.isEmailConfirmed
|
||||
? "var(--icon-success)"
|
||||
: "var(--icon-error)"
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
className="selectable"
|
||||
variant="subBody"
|
||||
ml={1}
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
{user?.email}
|
||||
{user?.isEmailConfirmed ? "" : " (not verified)"}
|
||||
</Text>
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
<SyncStatus />
|
||||
</>
|
||||
) : isLoggedIn === false ? (
|
||||
<Button
|
||||
variant="statusitem"
|
||||
onClick={() => showIssueDialog()}
|
||||
onClick={() => hardNavigate("/login")}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
}}
|
||||
title="Facing an issue? Click here to create a bug report."
|
||||
data-test-id="not-logged-in"
|
||||
>
|
||||
<Issue size={12} />
|
||||
<Circle size={7} color="var(--icon-error)" />
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
Report an issue
|
||||
Not logged in
|
||||
</Text>
|
||||
</Button>
|
||||
{statuses?.map((status) => {
|
||||
const { key, icon: Icon } = status;
|
||||
return (
|
||||
<Flex
|
||||
key={key}
|
||||
ml={1}
|
||||
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
{Icon ? <Icon size={12} /> : <Loading size={12} />}
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
{statusToString(status)}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
|
||||
{updateStatus && updateStatus.type !== "updated" && (
|
||||
<Button
|
||||
variant="statusitem"
|
||||
onClick={async () => {
|
||||
if (updateStatus.type === "available") {
|
||||
await showUpdateAvailableNotice(updateStatus);
|
||||
} else if (updateStatus.type === "completed") {
|
||||
installUpdate();
|
||||
} else {
|
||||
checkForUpdate();
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
ml: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
}}
|
||||
) : null}
|
||||
<Button
|
||||
variant="statusitem"
|
||||
onClick={() => showIssueDialog()}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
}}
|
||||
title="Facing an issue? Click here to create a bug report."
|
||||
>
|
||||
<Issue size={12} />
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
Report an issue
|
||||
</Text>
|
||||
</Button>
|
||||
{statuses?.map((status) => {
|
||||
const { key, icon: Icon } = status;
|
||||
return (
|
||||
<Flex
|
||||
key={key}
|
||||
ml={1}
|
||||
sx={{ alignItems: "center", justifyContent: "center" }}
|
||||
>
|
||||
<Update
|
||||
rotate={
|
||||
updateStatus.type !== "completed" &&
|
||||
updateStatus.type !== "available"
|
||||
}
|
||||
color={
|
||||
updateStatus.type === "available" ? "accent" : "paragraph"
|
||||
}
|
||||
size={12}
|
||||
/>
|
||||
{Icon ? <Icon size={12} /> : <Loading size={12} />}
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
{statusToInfoText(updateStatus)}
|
||||
{statusToString(status)}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
|
||||
{updateStatus && updateStatus.type !== "updated" && (
|
||||
<Button
|
||||
variant="statusitem"
|
||||
onClick={async () => {
|
||||
if (updateStatus.type === "available") {
|
||||
await showUpdateAvailableNotice(updateStatus);
|
||||
} else if (updateStatus.type === "completed") {
|
||||
installUpdate();
|
||||
} else {
|
||||
checkForUpdate();
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
ml: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
display: "flex"
|
||||
}}
|
||||
>
|
||||
<Update
|
||||
rotate={
|
||||
updateStatus.type !== "completed" &&
|
||||
updateStatus.type !== "available"
|
||||
}
|
||||
color={updateStatus.type === "available" ? "accent" : "paragraph"}
|
||||
size={12}
|
||||
/>
|
||||
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>
|
||||
{statusToInfoText(updateStatus)}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<EditorFooter />
|
||||
</ScopedThemeProvider>
|
||||
);
|
||||
|
||||
@@ -59,16 +59,6 @@ export function BaseThemeProvider(
|
||||
|
||||
useEffect(() => {
|
||||
useThemeEngineStore.getState().setTheme(theme);
|
||||
|
||||
const themeColorElement = document.head.querySelector(
|
||||
"meta[name='theme-color']"
|
||||
);
|
||||
if (themeColorElement) {
|
||||
themeColorElement.setAttribute(
|
||||
"content",
|
||||
theme.scopes.base.primary.background
|
||||
);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,6 +25,7 @@ import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { db } from "../common/db";
|
||||
import { useStore } from "../stores/reminder-store";
|
||||
import { useStore as useSettingStore } from "../stores/setting-store";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { useIsUserPremium } from "../hooks/use-is-user-premium";
|
||||
import { Pro } from "../components/icons";
|
||||
@@ -56,7 +57,9 @@ const RecurringModes = {
|
||||
DAY: "day"
|
||||
} as const;
|
||||
|
||||
const WEEK_DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
let WEEK_DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const WEEK_DAYS_MON = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||
|
||||
const modes = [
|
||||
{
|
||||
id: Modes.ONCE,
|
||||
@@ -116,6 +119,7 @@ export default function AddReminderDialog(props: AddReminderDialogProps) {
|
||||
const [title, setTitle] = useState<string>();
|
||||
const [description, setDescription] = useState<string>();
|
||||
const refresh = useStore((state) => state.refresh);
|
||||
const weekFromat = useSettingStore((state) => state.weekFormat);
|
||||
const isUserPremium = useIsUserPremium();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -142,6 +146,10 @@ export default function AddReminderDialog(props: AddReminderDialogProps) {
|
||||
setDescription(note.headline);
|
||||
}, [noteId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (weekFromat === "Mon") WEEK_DAYS = WEEK_DAYS_MON;
|
||||
}, [weekFromat]);
|
||||
|
||||
const repeatsDaily =
|
||||
(selectedDays.length === 7 && recurringMode === RecurringModes.WEEK) ||
|
||||
(selectedDays.length === 31 && recurringMode === RecurringModes.MONTH) ||
|
||||
|
||||
@@ -105,11 +105,7 @@ function PasswordDialog(props) {
|
||||
<Label
|
||||
key={check.key}
|
||||
mt={2}
|
||||
sx={{
|
||||
fontSize: "body",
|
||||
fontFamily: "body",
|
||||
alignItems: "center"
|
||||
}}
|
||||
sx={{ fontSize: "title", alignItems: "center" }}
|
||||
>
|
||||
<Checkbox id={check.key} name={check.key} />
|
||||
{check.title}
|
||||
|
||||
@@ -22,7 +22,6 @@ import { SettingsGroup } from "./types";
|
||||
import { useStore as useSettingStore } from "../../stores/setting-store";
|
||||
import dayjs from "dayjs";
|
||||
import { isUserPremium } from "../../hooks/use-is-user-premium";
|
||||
import { isMac } from "../../utils/platform";
|
||||
|
||||
export const BehaviourSettings: SettingsGroup[] = [
|
||||
{
|
||||
@@ -100,6 +99,27 @@ export const BehaviourSettings: SettingsGroup[] = [
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "week-format",
|
||||
title: "Week format",
|
||||
description:
|
||||
"This will set first day of the week for weekly reminders.",
|
||||
keywords: [],
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe((s) => s.weekFormat, listener),
|
||||
components: [
|
||||
{
|
||||
type: "dropdown",
|
||||
onSelectionChanged: (value) =>
|
||||
useSettingStore.getState().setWeekFormat(value),
|
||||
selectedOption: () => useSettingStore.getState().weekFormat,
|
||||
options: [
|
||||
{ value: "Sun", title: "Sunday" },
|
||||
{ value: "Mon", title: "Monday" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -137,10 +157,9 @@ export const BehaviourSettings: SettingsGroup[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "desktop-app",
|
||||
key: "updates",
|
||||
section: "behaviour",
|
||||
header: "Desktop app",
|
||||
isHidden: () => !IS_DESKTOP_APP,
|
||||
header: "Updates",
|
||||
settings: [
|
||||
{
|
||||
key: "auto-updates",
|
||||
@@ -149,7 +168,6 @@ export const BehaviourSettings: SettingsGroup[] = [
|
||||
"Automatically download & install updates in the background without prompting first.",
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe((s) => s.autoUpdates, listener),
|
||||
isHidden: () => useSettingStore.getState().isFlatpak,
|
||||
components: [
|
||||
{
|
||||
type: "toggle",
|
||||
@@ -157,20 +175,6 @@ export const BehaviourSettings: SettingsGroup[] = [
|
||||
toggle: () => useSettingStore.getState().toggleAutoUpdates()
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "menu-bar",
|
||||
title: "Disable menu bar",
|
||||
description:
|
||||
"Disable menu bar so it remains hidden even when Alt key is pressed.",
|
||||
isHidden: () => isMac(),
|
||||
components: [
|
||||
{
|
||||
type: "toggle",
|
||||
isToggled: () => !useSettingStore.getState().menuBar,
|
||||
toggle: () => useSettingStore.getState().toggleMenuBar()
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -72,24 +72,6 @@ export const AboutSettings: SettingsGroup[] = [
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "source-code",
|
||||
title: "Source code",
|
||||
description: "All the source code for Notesnook is available & open for everyone on GitHub.",
|
||||
components: [
|
||||
{
|
||||
type: "button",
|
||||
action: () => {
|
||||
window.open(
|
||||
"https://github.com/streetwriters/notesnook",
|
||||
"_blank"
|
||||
);
|
||||
},
|
||||
title: "View source code",
|
||||
variant: "secondary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "roadmap",
|
||||
title: "Roadmap",
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { SettingsGroup } from "./types";
|
||||
import { useStore as useSettingStore } from "../../stores/setting-store";
|
||||
import { useStore as useUserStore } from "../../stores/user-store";
|
||||
import { getPlatform } from "../../utils/platform";
|
||||
import { getPlatform, isDesktop } from "../../utils/platform";
|
||||
import { db } from "../../common/db";
|
||||
import { showPromptDialog } from "../../common/dialog-controller";
|
||||
import Config from "../../utils/config";
|
||||
@@ -74,20 +74,6 @@ What data is collected & when?`,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "hide-note-title",
|
||||
title: "Hide note title",
|
||||
description: "Prevent note title from appearing in tab/window title.",
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe((s) => s.hideNoteTitle, listener),
|
||||
components: [
|
||||
{
|
||||
type: "toggle",
|
||||
isToggled: () => useSettingStore.getState().hideNoteTitle,
|
||||
toggle: () => useSettingStore.getState().toggleHideTitle()
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "privacy-mode",
|
||||
title: "Privacy mode",
|
||||
|
||||
@@ -58,8 +58,6 @@ export const VaultSettings: SettingsGroup[] = [
|
||||
title: "Change vault password",
|
||||
description: "Set a new password for your vault",
|
||||
isHidden: () => !useAppStore.getState().isVaultCreated,
|
||||
onStateChange: (listener) =>
|
||||
useAppStore.subscribe((s) => s.isVaultCreated, listener),
|
||||
components: [
|
||||
{
|
||||
type: "button",
|
||||
@@ -74,8 +72,6 @@ export const VaultSettings: SettingsGroup[] = [
|
||||
title: "Clear vault",
|
||||
description: "Unlock all locked notes and clear vault.",
|
||||
isHidden: () => !useAppStore.getState().isVaultCreated,
|
||||
onStateChange: (listener) =>
|
||||
useAppStore.subscribe((s) => s.isVaultCreated, listener),
|
||||
components: [
|
||||
{
|
||||
type: "button",
|
||||
@@ -95,8 +91,6 @@ export const VaultSettings: SettingsGroup[] = [
|
||||
title: "Delete vault",
|
||||
description: "Delete vault including all locked notes.",
|
||||
isHidden: () => !useAppStore.getState().isVaultCreated,
|
||||
onStateChange: (listener) =>
|
||||
useAppStore.subscribe((s) => s.isVaultCreated, listener),
|
||||
components: [
|
||||
{
|
||||
type: "button",
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAHUUExURUxpcf////////////////////////////////////////////////////////////////////////////////////39/f39/f////////////////////////////////z8/O3s7P7+/vTz8wAAAPz7++/v7/Py8u/u7vDv7+7t7dfX1/79/ezr66Kiovv6+vf29vX09OTj4w0NDfv7++Ti4vr6+vT09Ovq6vn5+f39/fb19dnX1/b29tvb29/f3xMTE/j4+Pn4+O7s7MnJyerq6uno6M/Pz8jIyN3d3dnZ2d7e3kZGRvj39/38/Pf39+zs7PDw8OPi4ubl5cvLy+rp6czMzPLy8vPz887Ozujo6MrKyuDf3+fn59PT0+bm5uXl5c3NzfX19evr6wsLC+/t7ePj4/Ty8u3r6/Lx8eXk5OPh4dDQ0Nza2tjX19vZ2fHw8Nva2tLR0djW1tbW1tra2tLS0uHh4evp6eLi4tXV1djY2L+/vysrK7q6utHR0aGhoa6urrm5uQICAigoKCEhIZCQkFxcXBgYGFRUVBISEm1tbQ4ODklJSbu7uyAgID09PZqamkVFRXh4eLGxsTg4OOTk5FFRUQEBAQoKCnx8fBcXF7/+rP8AAAAfdFJOUwBOqO2h6KT9Mtyr5+w3Str+6TTZOjXb31M24O8BOd0PapSsAAABxElEQVQ4y2NgYOBgYWWXxwLYBdmEGICAjx9Z1NJJVQfB42QE6hdG0RaoaaCqhKSCl4EF1VxNVAXyTAzcaArUUBXwMLBjKDBBViDAII+uwBZFgTy6AjU1awIKNKz1aa0g1VBfB58CDS08CuIb40AKzLAqsJKXN2tXacChwKS+rTtB3tJHRU9ewxmbFZ6tKiqTrSAKdIO1LDEUaPeoqMzsUwAp0NIt8Uj0ijJXQFVgnDJVZcK0XrACXcXY0sjoHM8AfyUkBbOn9KvMmgRSYFEW4hUVqJagHeaShqTAwX7ijJQusAkW5VXRHu6eruFqaigKkqarqAAVKFbYxWiYq1XGedijuMHBWz6xo7m2Rl7RwtvdJaw6NjzCFl2BVVNdshJQgbqBsqtdpIu7nasjXIFXS2cyRL2CW6i6JTCgFCLitUPs4QrM7cDmKZi7FRYVm6o5QeLbDGICLNEqJAVk59rk2djY5Me4FThawQOKFSzrrxmaZWpqBAamQUGmwRaakNTNzMAm7+ekrKmlqOjsrIgAGcAA1ci0NVPiYhAV8zVURgbp6nBg7isuwsDAKKfkp4AEVOHASklKApS9JZmY5bECaS5ZGQYA3vqpy6NoYh0AAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII="
|
||||
/>
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<!-- <link rel="manifest" href="/site.webmanifest" /> -->
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#01c352" />
|
||||
<meta name="msapplication-TileColor" content="#01c352" />
|
||||
<meta name="theme-color" content="#01c352" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
@@ -24,7 +24,6 @@ import { loadDatabase } from "./hooks/use-database";
|
||||
import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { BaseThemeProvider } from "./components/theme-provider";
|
||||
import { register } from "./utils/stream-saver/mitm";
|
||||
import { getServiceWorkerVersion } from "./utils/version";
|
||||
|
||||
renderApp();
|
||||
|
||||
@@ -61,6 +60,7 @@ async function initializeServiceWorker() {
|
||||
serviceWorker.register({
|
||||
onUpdate: async (registration: ServiceWorkerRegistration) => {
|
||||
if (!registration.waiting) return;
|
||||
const { getServiceWorkerVersion } = await import("./utils/version");
|
||||
const { formatted } = await getServiceWorkerVersion(
|
||||
registration.waiting
|
||||
);
|
||||
|
||||
@@ -1,161 +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 { IFileStorage } from "@notesnook/streamable-fs/dist/src/interfaces";
|
||||
import { File } from "@notesnook/streamable-fs/dist/src/types";
|
||||
import { IndexedDBKVStore } from "./key-value";
|
||||
import OriginPrivateFileStoreWorker from "./opfs.worker?worker";
|
||||
import { OriginPrivateFileStoreWorkerType } from "./opfs.worker";
|
||||
import { transfer, wrap } from "comlink";
|
||||
|
||||
export class IndexedDBFileStore implements IFileStorage {
|
||||
storage: IndexedDBKVStore;
|
||||
constructor(name: string) {
|
||||
this.storage = new IndexedDBKVStore(name, name);
|
||||
}
|
||||
|
||||
clear(): Promise<void> {
|
||||
return this.storage.clear();
|
||||
}
|
||||
setMetadata(filename: string, metadata: File): Promise<void> {
|
||||
return this.storage.set(filename, metadata);
|
||||
}
|
||||
getMetadata(filename: string): Promise<File | undefined> {
|
||||
return this.storage.get(filename);
|
||||
}
|
||||
deleteMetadata(filename: string): Promise<void> {
|
||||
return this.storage.delete(filename);
|
||||
}
|
||||
writeChunk(chunkName: string, data: Uint8Array): Promise<void> {
|
||||
return this.storage.set(chunkName, data);
|
||||
}
|
||||
deleteChunk(chunkName: string): Promise<void> {
|
||||
return this.storage.delete(chunkName);
|
||||
}
|
||||
readChunk(chunkName: string): Promise<Uint8Array | undefined> {
|
||||
return this.storage.get(chunkName);
|
||||
}
|
||||
}
|
||||
|
||||
export class CacheStorageFileStore implements IFileStorage {
|
||||
storage: IndexedDBKVStore;
|
||||
constructor(private readonly name: string) {
|
||||
this.storage = new IndexedDBKVStore(name, name);
|
||||
console.log("USING CACHE FILE STORE!");
|
||||
}
|
||||
|
||||
private getCache() {
|
||||
return window.caches.open(this.name);
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
const cache = await this.getCache();
|
||||
for (const req of await cache.keys()) {
|
||||
await cache.delete(req);
|
||||
}
|
||||
return this.storage.clear();
|
||||
}
|
||||
|
||||
setMetadata(filename: string, metadata: File): Promise<void> {
|
||||
return this.storage.set(filename, metadata);
|
||||
}
|
||||
|
||||
getMetadata(filename: string): Promise<File | undefined> {
|
||||
return this.storage.get(filename);
|
||||
}
|
||||
|
||||
deleteMetadata(filename: string): Promise<void> {
|
||||
return this.storage.delete(filename);
|
||||
}
|
||||
|
||||
async writeChunk(chunkName: string, data: Uint8Array): Promise<void> {
|
||||
const cache = await this.getCache();
|
||||
await cache.put(
|
||||
this.toURL(chunkName),
|
||||
new Response(data, {
|
||||
headers: new Headers({
|
||||
"Content-Length": data.length.toString(),
|
||||
"Content-Type": "application/encrypted-octet-stream"
|
||||
})
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async deleteChunk(chunkName: string): Promise<void> {
|
||||
const cache = await this.getCache();
|
||||
await cache.delete(this.toURL(chunkName));
|
||||
}
|
||||
|
||||
async readChunk(chunkName: string): Promise<Uint8Array | undefined> {
|
||||
const cache = await this.getCache();
|
||||
const response = await cache.match(this.toURL(chunkName));
|
||||
return response ? new Uint8Array(await response.arrayBuffer()) : undefined;
|
||||
}
|
||||
|
||||
private toURL(chunkName: string) {
|
||||
return `/${chunkName}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class OriginPrivateFileSystem implements IFileStorage {
|
||||
private readonly worker = wrap<OriginPrivateFileStoreWorkerType>(
|
||||
new OriginPrivateFileStoreWorker()
|
||||
);
|
||||
private created = false;
|
||||
constructor(private readonly name: string) {
|
||||
console.log("using origin private file store");
|
||||
}
|
||||
private async create() {
|
||||
if (this.created) return;
|
||||
await this.worker.create(this.name, this.name);
|
||||
this.created = true;
|
||||
}
|
||||
async clear(): Promise<void> {
|
||||
await this.create();
|
||||
await this.worker.clear(this.name);
|
||||
}
|
||||
async setMetadata(filename: string, metadata: File): Promise<void> {
|
||||
await this.create();
|
||||
await this.worker.setMetadata(this.name, filename, metadata);
|
||||
}
|
||||
async getMetadata(filename: string): Promise<File | undefined> {
|
||||
await this.create();
|
||||
return this.worker.getMetadata(this.name, filename);
|
||||
}
|
||||
async deleteMetadata(filename: string): Promise<void> {
|
||||
await this.create();
|
||||
return this.worker.deleteMetadata(this.name, filename);
|
||||
}
|
||||
async writeChunk(chunkName: string, data: Uint8Array): Promise<void> {
|
||||
await this.create();
|
||||
return this.worker.writeChunk(
|
||||
this.name,
|
||||
chunkName,
|
||||
transfer(data.buffer, [data.buffer])
|
||||
);
|
||||
}
|
||||
async deleteChunk(chunkName: string): Promise<void> {
|
||||
await this.create();
|
||||
return this.worker.deleteChunk(this.name, chunkName);
|
||||
}
|
||||
async readChunk(chunkName: string): Promise<Uint8Array | undefined> {
|
||||
await this.create();
|
||||
return this.worker.readChunk(this.name, chunkName);
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,6 @@ import { Cipher, DataFormat, SerializedKey } from "@notesnook/crypto";
|
||||
import { IDataType } from "hash-wasm/dist/lib/util";
|
||||
import { IndexedDBKVStore } from "./key-value";
|
||||
import FileHandle from "@notesnook/streamable-fs/dist/src/filehandle";
|
||||
import {
|
||||
CacheStorageFileStore,
|
||||
IndexedDBFileStore,
|
||||
OriginPrivateFileSystem
|
||||
} from "./file-store";
|
||||
import { isFeatureSupported } from "../utils/feature-check";
|
||||
|
||||
const ABYTES = 17;
|
||||
const CHUNK_SIZE = 512 * 1024;
|
||||
@@ -51,13 +45,7 @@ const UPLOAD_PART_REQUIRED_CHUNKS = Math.ceil(
|
||||
(5 * 1024 * 1024) / ENCRYPTED_CHUNK_SIZE
|
||||
);
|
||||
const MINIMUM_MULTIPART_FILE_SIZE = 25 * 1024 * 1024;
|
||||
const streamablefs = new StreamableFS(
|
||||
isFeatureSupported("opfs")
|
||||
? new OriginPrivateFileSystem("streamable-fs")
|
||||
: isFeatureSupported("cache")
|
||||
? new CacheStorageFileStore("streamable-fs")
|
||||
: new IndexedDBFileStore("streamable-fs")
|
||||
);
|
||||
const streamablefs = new StreamableFS("streamable-fs");
|
||||
|
||||
async function writeEncryptedFile(
|
||||
file: File,
|
||||
@@ -81,12 +69,7 @@ async function writeEncryptedFile(
|
||||
const { iv, stream } = await NNCrypto.createEncryptionStream(key);
|
||||
await file
|
||||
.stream()
|
||||
.pipeThrough(
|
||||
new ChunkedStream(
|
||||
CHUNK_SIZE,
|
||||
isFeatureSupported("opfs") ? "copy" : "nocopy"
|
||||
)
|
||||
)
|
||||
.pipeThrough(new ChunkedStream(CHUNK_SIZE))
|
||||
.pipeThrough(new IntoChunks(file.size))
|
||||
.pipeThrough(stream)
|
||||
.pipeThrough(
|
||||
@@ -517,17 +500,11 @@ async function downloadFile(filename: string, requestOptions: RequestOptions) {
|
||||
);
|
||||
})
|
||||
)
|
||||
.pipeThrough(
|
||||
new ChunkedStream(
|
||||
chunkSize + ABYTES,
|
||||
isFeatureSupported("opfs") ? "copy" : "nocopy"
|
||||
)
|
||||
)
|
||||
.pipeThrough(new ChunkedStream(chunkSize + ABYTES))
|
||||
.pipeTo(fileHandle.writeable);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showError(toS3Error(e), "Could not download file");
|
||||
reportProgress(undefined, { type: "download", hash: filename });
|
||||
return false;
|
||||
@@ -565,6 +542,8 @@ export async function decryptFile(
|
||||
}
|
||||
|
||||
async function saveFile(filename: string, fileMetadata: FileMetadata) {
|
||||
if (!fileMetadata) return false;
|
||||
|
||||
const { name, type, isUploaded } = fileMetadata;
|
||||
|
||||
const decrypted = await decryptFile(filename, fileMetadata);
|
||||
|
||||
@@ -21,7 +21,4 @@ import { INNCrypto } from "@notesnook/crypto";
|
||||
import CryptoWorker from "./nncrypto.worker?worker";
|
||||
import { wrap } from "comlink";
|
||||
|
||||
export const NNCrypto =
|
||||
IS_DESKTOP_APP && window.NativeNNCrypto
|
||||
? new window.NativeNNCrypto()
|
||||
: (wrap<INNCrypto>(new CryptoWorker()) as INNCrypto);
|
||||
export const NNCrypto = wrap<INNCrypto>(new CryptoWorker()) as INNCrypto;
|
||||
|
||||
@@ -1,119 +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 { IFileStorage } from "@notesnook/streamable-fs/dist/src/interfaces";
|
||||
import { File } from "@notesnook/streamable-fs/dist/src/types";
|
||||
import { IndexedDBKVStore } from "./key-value";
|
||||
import { expose, transfer } from "comlink";
|
||||
|
||||
class OriginPrivateFileStore implements IFileStorage {
|
||||
private storage: IndexedDBKVStore;
|
||||
constructor(
|
||||
name: string,
|
||||
private readonly directory: FileSystemDirectoryHandle
|
||||
) {
|
||||
this.storage = new IndexedDBKVStore(name, name);
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
for await (const [name] of this.directory) {
|
||||
await this.directory.removeEntry(name, { recursive: true });
|
||||
}
|
||||
await this.storage.clear();
|
||||
}
|
||||
setMetadata(filename: string, metadata: File): Promise<void> {
|
||||
return this.storage.set(filename, metadata);
|
||||
}
|
||||
getMetadata(filename: string): Promise<File | undefined> {
|
||||
return this.storage.get(filename);
|
||||
}
|
||||
deleteMetadata(filename: string): Promise<void> {
|
||||
return this.storage.delete(filename);
|
||||
}
|
||||
async writeChunk(chunkName: string, data: Uint8Array): Promise<void> {
|
||||
const file = await this.directory.getFileHandle(chunkName, {
|
||||
create: true
|
||||
});
|
||||
const syncHandle = await file.createSyncAccessHandle();
|
||||
syncHandle.write(data);
|
||||
syncHandle.close();
|
||||
}
|
||||
async deleteChunk(chunkName: string) {
|
||||
try {
|
||||
await this.directory.removeEntry(chunkName);
|
||||
} catch (e) {
|
||||
console.error("Failed to delete chunk", e);
|
||||
}
|
||||
}
|
||||
async readChunk(chunkName: string): Promise<Uint8Array | undefined> {
|
||||
try {
|
||||
const file = await this.directory.getFileHandle(chunkName);
|
||||
const syncHandle = await file.createSyncAccessHandle();
|
||||
const buffer = new Uint8Array(syncHandle.getSize());
|
||||
syncHandle.read(buffer);
|
||||
syncHandle.close();
|
||||
return buffer;
|
||||
} catch (e) {
|
||||
console.error("Failed to read chunk", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fileStores: Map<string, OriginPrivateFileStore> = new Map();
|
||||
const workerModule = {
|
||||
async create(name: string, directoryName: string) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const directoryHandle = await root.getDirectoryHandle(directoryName, {
|
||||
create: true
|
||||
});
|
||||
fileStores.set(
|
||||
directoryName,
|
||||
new OriginPrivateFileStore(name, directoryHandle)
|
||||
);
|
||||
},
|
||||
clear(directoryName: string) {
|
||||
return fileStores.get(directoryName)?.clear();
|
||||
},
|
||||
setMetadata(directoryName: string, filename: string, metadata: File) {
|
||||
return fileStores.get(directoryName)?.setMetadata(filename, metadata);
|
||||
},
|
||||
getMetadata(directoryName: string, filename: string) {
|
||||
return fileStores.get(directoryName)?.getMetadata(filename);
|
||||
},
|
||||
deleteMetadata(directoryName: string, filename: string) {
|
||||
return fileStores.get(directoryName)?.deleteMetadata(filename);
|
||||
},
|
||||
writeChunk(directoryName: string, chunkName: string, data: ArrayBuffer) {
|
||||
return fileStores
|
||||
.get(directoryName)
|
||||
?.writeChunk(chunkName, new Uint8Array(data));
|
||||
},
|
||||
deleteChunk(directoryName: string, chunkName: string) {
|
||||
return fileStores.get(directoryName)?.deleteChunk(chunkName);
|
||||
},
|
||||
async readChunk(directoryName: string, chunkName: string) {
|
||||
const chunk = await fileStores.get(directoryName)?.readChunk(chunkName);
|
||||
return chunk ? transfer(chunk, [chunk.buffer]) : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
expose(workerModule);
|
||||
|
||||
export type OriginPrivateFileStoreWorkerType = typeof workerModule;
|
||||
@@ -22,7 +22,6 @@ import { store as noteStore } from "./note-store";
|
||||
import { store as attachmentStore } from "./attachment-store";
|
||||
import { store as appStore } from "./app-store";
|
||||
import { store as tagStore } from "./tag-store";
|
||||
import { store as settingStore } from "./setting-store";
|
||||
import { db } from "../common/db";
|
||||
import BaseStore from ".";
|
||||
import { EV, EVENTS } from "@notesnook/core/dist/common";
|
||||
@@ -152,7 +151,7 @@ class EditorStore extends BaseStore {
|
||||
if (!note) return;
|
||||
|
||||
noteStore.setSelectedNote(note.id);
|
||||
setDocumentTitle(settingStore.get().hideNoteTitle ? undefined : note.title);
|
||||
setDocumentTitle(note.title);
|
||||
|
||||
if (note.locked)
|
||||
return hashNavigate(`/notes/${noteId}/unlock`, { replace: true });
|
||||
@@ -248,9 +247,7 @@ class EditorStore extends BaseStore {
|
||||
state.session.dateEdited = note.dateEdited;
|
||||
state.session.attachmentsLength = attachments.length;
|
||||
});
|
||||
setDocumentTitle(
|
||||
settingStore.get().hideNoteTitle ? undefined : note.title
|
||||
);
|
||||
setDocumentTitle(note.title);
|
||||
|
||||
this.setSaveState(1);
|
||||
} catch (err) {
|
||||
|
||||
@@ -23,9 +23,7 @@ import { desktop } from "../common/desktop-bridge";
|
||||
import createStore from "../common/store";
|
||||
import Config from "../utils/config";
|
||||
import BaseStore from "./index";
|
||||
import { store as editorStore } from "./editor-store";
|
||||
import { isTelemetryEnabled, setTelemetry } from "../utils/telemetry";
|
||||
import { setDocumentTitle } from "../utils/dom";
|
||||
|
||||
/**
|
||||
* @extends {BaseStore<SettingStore>}
|
||||
@@ -42,7 +40,6 @@ class SettingStore extends BaseStore {
|
||||
|
||||
zoomFactor = 1.0;
|
||||
privacyMode = false;
|
||||
hideNoteTitle = Config.get("hideNoteTitle", false);
|
||||
telemetry = isTelemetryEnabled();
|
||||
/** @type {string} */
|
||||
dateFormat = null;
|
||||
@@ -52,27 +49,26 @@ class SettingStore extends BaseStore {
|
||||
/** @type {number} */
|
||||
trashCleanupInterval = 7;
|
||||
homepage = Config.get("homepage", 0);
|
||||
/**@type {"Sun" | "Mon"} */
|
||||
weekFormat = null;
|
||||
/**
|
||||
* @type {DesktopIntegrationSettings | undefined}
|
||||
*/
|
||||
desktopIntegrationSettings = undefined;
|
||||
autoUpdates = true;
|
||||
isFlatpak = false;
|
||||
menuBar = true;
|
||||
|
||||
refresh = async () => {
|
||||
this.set({
|
||||
dateFormat: db.settings.getDateFormat(),
|
||||
timeFormat: db.settings.getTimeFormat(),
|
||||
weekFormat: db.settings.getWeekFormat(),
|
||||
titleFormat: db.settings.getTitleFormat(),
|
||||
trashCleanupInterval: db.settings.getTrashCleanupInterval(),
|
||||
isFlatpak: await desktop?.integration.isFlatpak.query(),
|
||||
desktopIntegrationSettings:
|
||||
await desktop?.integration.desktopIntegration.query(),
|
||||
privacyMode: await desktop?.integration.privacyMode.query(),
|
||||
zoomFactor: await desktop?.integration.zoomFactor.query(),
|
||||
autoUpdates: await desktop?.updater.autoUpdates.query(),
|
||||
menuBar: await desktop?.integration.menuBar.query()
|
||||
autoUpdates: await desktop?.updater.autoUpdates.query()
|
||||
});
|
||||
};
|
||||
|
||||
@@ -86,6 +82,11 @@ class SettingStore extends BaseStore {
|
||||
this.set({ timeFormat });
|
||||
};
|
||||
|
||||
setWeekFormat = async (weekFormat) => {
|
||||
await db.settings.setWeekFormat(weekFormat);
|
||||
this.set({ weekFormat });
|
||||
};
|
||||
|
||||
setTitleFormat = async (titleFormat) => {
|
||||
await db.settings.setTitleFormat(titleFormat);
|
||||
this.set({ titleFormat });
|
||||
@@ -170,28 +171,11 @@ class SettingStore extends BaseStore {
|
||||
await desktop?.integration.setPrivacyMode.mutate({ enabled: !privacyMode });
|
||||
};
|
||||
|
||||
toggleHideTitle = async () => {
|
||||
const { hideNoteTitle } = this.get();
|
||||
this.set({ hideNoteTitle: !hideNoteTitle });
|
||||
Config.set("hideNoteTitle", !hideNoteTitle);
|
||||
setDocumentTitle(
|
||||
!hideNoteTitle ? undefined : editorStore.get().session.title
|
||||
);
|
||||
};
|
||||
|
||||
toggleAutoUpdates = async () => {
|
||||
const autoUpdates = this.get().autoUpdates;
|
||||
this.set({ autoUpdates: !autoUpdates });
|
||||
await desktop?.updater.toggleAutoUpdates.mutate({ enabled: !autoUpdates });
|
||||
};
|
||||
|
||||
toggleMenuBar = async () => {
|
||||
const menuBar = this.get().menuBar;
|
||||
this.set({ menuBar: !menuBar });
|
||||
await desktop?.integration.setMenuBar.mutate({
|
||||
enabled: !menuBar
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const [useStore, store] = createStore(SettingStore);
|
||||
|
||||
@@ -112,7 +112,7 @@ class UserStore extends BaseStore {
|
||||
});
|
||||
};
|
||||
|
||||
login = async (form, skipInit = false, sessionExpired = false) => {
|
||||
login = async (form, skipInit = false) => {
|
||||
this.set((state) => (state.isLoggingIn = true));
|
||||
const { email, password, code, method } = form;
|
||||
|
||||
@@ -120,12 +120,7 @@ class UserStore extends BaseStore {
|
||||
if (code) {
|
||||
return await db.user.authenticateMultiFactorCode(code, method);
|
||||
} else if (password) {
|
||||
await db.user.authenticatePassword(
|
||||
email,
|
||||
password,
|
||||
null,
|
||||
sessionExpired
|
||||
);
|
||||
await db.user.authenticatePassword(email, password, null);
|
||||
Config.set("encryptBackups", true);
|
||||
|
||||
if (skipInit) return true;
|
||||
|
||||
@@ -1,60 +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/>.
|
||||
*/
|
||||
|
||||
const FEATURE_CHECKS = {
|
||||
opfs: false,
|
||||
cache: false
|
||||
};
|
||||
|
||||
async function isOPFSSupported() {
|
||||
const hasGetDirectory =
|
||||
"getDirectory" in window.navigator.storage &&
|
||||
typeof window.navigator.storage.getDirectory === "function";
|
||||
return (
|
||||
hasGetDirectory &&
|
||||
window.navigator.storage
|
||||
.getDirectory()
|
||||
.then(() => (FEATURE_CHECKS.opfs = true))
|
||||
.catch(() => (FEATURE_CHECKS.opfs = false))
|
||||
);
|
||||
}
|
||||
|
||||
async function isCacheSupported() {
|
||||
const hasCacheStorage =
|
||||
"CacheStorage" in window &&
|
||||
"caches" in window &&
|
||||
window.caches instanceof CacheStorage;
|
||||
return (
|
||||
hasCacheStorage &&
|
||||
window.caches
|
||||
.has("something")
|
||||
.then((f) => (FEATURE_CHECKS.cache = true))
|
||||
.catch((a) => (FEATURE_CHECKS.cache = false))
|
||||
);
|
||||
}
|
||||
|
||||
export async function initializeFeatureChecks() {
|
||||
await Promise.allSettled([isOPFSSupported(), isCacheSupported()]);
|
||||
}
|
||||
|
||||
function isFeatureSupported(key: keyof typeof FEATURE_CHECKS) {
|
||||
return FEATURE_CHECKS[key];
|
||||
}
|
||||
|
||||
export { isFeatureSupported };
|
||||
@@ -18,6 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { db } from "../common/db";
|
||||
import FS from "../interfaces/fs";
|
||||
|
||||
import type { Note, Notebook } from "@notesnook-importer/core";
|
||||
import {
|
||||
ATTACHMENTS_DIRECTORY_NAME,
|
||||
@@ -28,7 +30,6 @@ import { Reader, Entry } from "./zip-reader";
|
||||
import { path } from "@notesnook-importer/core/dist/src/utils/path";
|
||||
|
||||
export async function* importFiles(zipFiles: File[]) {
|
||||
const { default: FS } = await import("../interfaces/fs");
|
||||
for (const zip of zipFiles) {
|
||||
let count = 0;
|
||||
let filesRead = 0;
|
||||
|
||||
@@ -1,26 +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/>.
|
||||
*/
|
||||
|
||||
export async function lazify<T, R>(
|
||||
loader: Promise<T>,
|
||||
action: (module: T) => R | Promise<R>
|
||||
): Promise<R> {
|
||||
const module = await loader;
|
||||
return await action(module);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import { expose } from "comlink";
|
||||
|
||||
const module = {
|
||||
async waitForInternet() {
|
||||
let retries = 3;
|
||||
let retries = 10;
|
||||
while (retries-- > 0) {
|
||||
try {
|
||||
const response = await fetch("https://api.notesnook.com/health");
|
||||
|
||||
@@ -129,5 +129,5 @@ export function isMac() {
|
||||
}
|
||||
|
||||
export function isMacStoreApp() {
|
||||
return window.os ? window.os() === "mas" : false;
|
||||
return window.os && window.os() === "mas";
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ 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 { decryptFile } from "../../interfaces/fs";
|
||||
import { db } from "../../common/db";
|
||||
import { lazify } from "../lazify";
|
||||
import { ZipFile } from "./zip-stream";
|
||||
|
||||
export const METADATA_FILENAME = "metadata.json";
|
||||
@@ -29,13 +29,12 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
|
||||
signal?: AbortSignal,
|
||||
onProgress?: (current: number) => void
|
||||
) {
|
||||
let index = 0;
|
||||
const counters: Record<string, number> = {};
|
||||
if (signal)
|
||||
signal.onabort = async () => {
|
||||
await db.fs?.cancel(GROUP_ID, "download");
|
||||
};
|
||||
|
||||
let index = 0;
|
||||
super({
|
||||
start() {},
|
||||
async pull(controller) {
|
||||
@@ -55,22 +54,18 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
|
||||
);
|
||||
|
||||
const key = await db.attachments?.decryptKey(attachment.key);
|
||||
const file = await lazify(
|
||||
import("../../interfaces/fs"),
|
||||
({ decryptFile }) =>
|
||||
decryptFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
})
|
||||
);
|
||||
const file = await decryptFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
});
|
||||
|
||||
if (file) {
|
||||
const filePath: string = attachment.metadata.filename;
|
||||
const filePath = attachment.metadata.filename;
|
||||
controller.enqueue({
|
||||
path: makeUniqueFilename(filePath, counters),
|
||||
path: filePath,
|
||||
data: new Uint8Array(await file.arrayBuffer())
|
||||
});
|
||||
} else {
|
||||
@@ -84,17 +79,3 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function makeUniqueFilename(
|
||||
filePath: string,
|
||||
counters: Record<string, number>
|
||||
) {
|
||||
filePath = filePath.toLowerCase();
|
||||
counters[filePath] = (counters[filePath] || 0) + 1;
|
||||
if (counters[filePath] === 1) return filePath;
|
||||
|
||||
const parts = filePath.split(".");
|
||||
return `${parts.slice(0, -1).join(".")}-${counters[filePath]}.${
|
||||
parts[parts.length - 1]
|
||||
}`;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Chunk } from "@notesnook/crypto";
|
||||
|
||||
export class ChunkedStream extends TransformStream<Uint8Array, Uint8Array> {
|
||||
constructor(chunkSize: number, mode: "nocopy" | "copy") {
|
||||
constructor(chunkSize: number) {
|
||||
let backBuffer: Uint8Array | null = null;
|
||||
super({
|
||||
start() {},
|
||||
@@ -38,13 +38,7 @@ export class ChunkedStream extends TransformStream<Uint8Array, Uint8Array> {
|
||||
const start = backBuffer.length - remainingBytes;
|
||||
const end = start + chunkSize;
|
||||
|
||||
// TODO: find a way to support sending the chunked
|
||||
// buffer to web workers without copying.
|
||||
controller.enqueue(
|
||||
mode === "copy"
|
||||
? new Uint8Array(backBuffer.buffer.slice(start, end))
|
||||
: backBuffer.subarray(start, end)
|
||||
);
|
||||
controller.enqueue(backBuffer.subarray(start, end));
|
||||
remainingBytes -= chunkSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,25 +17,23 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AsyncZippable, zip as zipAsync } from "fflate";
|
||||
import { Unzipped } from "fflate";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
type File = { filename: string; content: string; date: number };
|
||||
type File = { filename: string; content: string };
|
||||
async function zip(files: File[], format: string): Promise<Uint8Array> {
|
||||
const obj: AsyncZippable = Object.create(null);
|
||||
const obj: Unzipped = Object.create(null);
|
||||
files.forEach((file) => {
|
||||
const name = sanitizeFilename(file.filename, { replacement: "-" });
|
||||
let counter = 0;
|
||||
while (obj[makeFilename(name, format, counter)]) ++counter;
|
||||
|
||||
obj[makeFilename(name, format, counter)] = [
|
||||
textEncoder.encode(file.content),
|
||||
{ mtime: file.date }
|
||||
];
|
||||
obj[makeFilename(name, format, counter)] = textEncoder.encode(file.content);
|
||||
});
|
||||
const { zip } = await import("fflate");
|
||||
return new Promise((resolve, reject) =>
|
||||
zipAsync(obj, (err, data) => (err ? reject(err) : resolve(data)))
|
||||
zip(obj, (err, data) => (err ? reject(err) : resolve(data)))
|
||||
);
|
||||
}
|
||||
export { zip };
|
||||
|
||||
@@ -278,14 +278,10 @@ function LoginPassword(props: BaseAuthComponentProps<"login:password">) {
|
||||
subtitle: "Please wait while you are authenticated."
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
await userstore.login(
|
||||
{
|
||||
password: form.password,
|
||||
email: formData.email
|
||||
},
|
||||
false,
|
||||
Config.get("sessionExpired", false)
|
||||
);
|
||||
await userstore.login({
|
||||
password: form.password,
|
||||
email: formData.email
|
||||
});
|
||||
Config.set("sessionExpired", false);
|
||||
openURL("/");
|
||||
}}
|
||||
|
||||
@@ -329,14 +329,23 @@ function RecoveryMethods(props: BaseRecoveryComponentProps<"methods">) {
|
||||
|
||||
function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
|
||||
const { navigate } = props;
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [progress, setProgress] = useState("0");
|
||||
|
||||
useEffect(() => {
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncProgress,
|
||||
({ type, current }: { type: string; current: number }) => {
|
||||
({
|
||||
type,
|
||||
total,
|
||||
current
|
||||
}: {
|
||||
type: string;
|
||||
total: number;
|
||||
current: number;
|
||||
}) => {
|
||||
if (total === current) return;
|
||||
if (type === "download") {
|
||||
setProgress(current);
|
||||
setProgress(((current / total) * 100).toFixed());
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -349,11 +358,11 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
|
||||
title="Recover your account"
|
||||
subtitle={"Use a data recovery key to reset your account password."}
|
||||
loading={{
|
||||
title: `Downloading your data (${progress})`,
|
||||
title: `Downloading your data (${progress}%)`,
|
||||
subtitle: "Please wait while your data is downloaded & decrypted."
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
setProgress(0);
|
||||
setProgress("0");
|
||||
|
||||
const user = await db.user?.getUser();
|
||||
if (!user) throw new Error("User not authenticated");
|
||||
@@ -470,13 +479,14 @@ function BackupData(props: BaseRecoveryComponentProps<"backup">) {
|
||||
|
||||
function NewPassword(props: BaseRecoveryComponentProps<"new">) {
|
||||
const { navigate, formData } = props;
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [progress, setProgress] = useState("0");
|
||||
|
||||
useEffect(() => {
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncProgress,
|
||||
({ current }: { current: number }) => {
|
||||
setProgress(current);
|
||||
({ total, current }: { total: number; current: number }) => {
|
||||
if (total === current) return;
|
||||
setProgress(((current / total) * 100).toFixed());
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
@@ -490,11 +500,11 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
|
||||
"Notesnook is E2E encrypted — your password never leaves this device."
|
||||
}
|
||||
loading={{
|
||||
title: `Resetting account password (${progress})`,
|
||||
title: `Resetting account password (${progress}%)`,
|
||||
subtitle: "Please wait while we reset your account password."
|
||||
}}
|
||||
onSubmit={async (form) => {
|
||||
setProgress(0);
|
||||
setProgress("0");
|
||||
|
||||
if (form.password !== form.confirmPassword)
|
||||
throw new Error("Passwords do not match.");
|
||||
|
||||
@@ -8,6 +8,6 @@ Unfortunately, we cannot find what you are looking for.
|
||||
|
||||
Here are some other pages you might be interested in:
|
||||
|
||||
1. [Installing Notesnook](organizing-notes/organize-notes-using-notebooks)
|
||||
1. [Installing Notesnook](installation)
|
||||
2. [Creating your first note](create-a-note-in-notesnook)
|
||||
3. [How is my data encrypted?](how-is-my-data-encrypted)
|
||||
|
||||
@@ -11,6 +11,4 @@ Notesnook is our **proof** that privacy does _not_ (always) have to come at the
|
||||
|
||||
And with that convenience in mind, we believe that it is equally important to put together a simple & useful help website about everything Notesnook. Our help is a complete walkthrough of the Notesnook app and it's features.
|
||||
|
||||
Before we get started, let's [download & install Notesnook](https://notesnook.com/downloads) on all your devices.
|
||||
|
||||
Once that's done, let's learn how to [create your first note](/create-a-note-in-notesnook) in Notesnook.
|
||||
So let's get started with [installing Notesnook](/installation) on all your devices.
|
||||
|
||||
|
Before Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 57 KiB |
@@ -163,10 +163,6 @@ nav li {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
li.nested {
|
||||
min-height: 38px !important;
|
||||
}
|
||||
|
||||
nav li li:not(li.nested):hover {
|
||||
background-color: var(--hover-bg);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 411 KiB |
|
Before Width: | Height: | Size: 74 KiB |
@@ -5,58 +5,41 @@ description: Notesnook allows you to backup all your notes data to a single back
|
||||
|
||||
# Backup your notes
|
||||
|
||||
It is always a good practice to take regular backups of your data so you can easily recover your data in case of data corruption or losing access to your account. All backups are stored locally encrypted (unless you turn off backup encryption) on your device.
|
||||
It is always a good practice to take a backup of your data so in case you lose access to your account, you don't lose your notes. Currently backups are local only.
|
||||
|
||||
> error Store your password & recovery key safely
|
||||
> warning
|
||||
>
|
||||
> Since all your data is end-to-end encrypted, we have no way to restore your account data if you forget your account password and lose your account recovery key. That's why we recommend that you store your password & recovery key in a password manager or some other safe place.
|
||||
> Since all your data is end-to-end encrypted, we do not have the access to restore your account if you forgot your account password and recovery key. Therefore we recommend that you store your password safely and make sure that you have saved your account recovery.
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Scroll down in the Settings navigation menu and click on `Backup & export` section
|
||||
3. Click on `Create backup` under `Backup now` heading to create a new `.nnbackupz` file
|
||||
|
||||

|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Backup & Restore` section
|
||||
3. Click on `Backup Data` to download the `.nnbackup` file
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Backup and Restore`
|
||||
3. Tap on `Backups`
|
||||
4. Press on `Backup now` to create a new `.nnbackupz` file
|
||||
3. Go to `Backups`
|
||||
4. Press on `Backup now` to save `.nnbackup` file
|
||||
|
||||
> info
|
||||
>
|
||||
> On **Android** when you take a backup for the first time, you will be asked to select a folder where you want to store all your backup files. You can always change your backup files location from `Backups > Select backup directory`.
|
||||
> On **Android** when you take a backup first time, you will be asked to select a folder on your phone where you would like to store backup files. You can always change your backup files location from `Backups > Change backup location`.
|
||||
>
|
||||
> Regardless of the folder you select, Notesnook will create a folder "Notesnook/backups" inside it and store all backup files there.
|
||||
> Regardless of the folder you select, Notesnook will create a folder "Notesnook/backups" inside it and store backup files there.
|
||||
|
||||
---
|
||||
|
||||
## Automatic Backups
|
||||
|
||||
For maximum safety against potential data loss, you can enable daily, weekly or monthly backups of your notes. Enabling automatic backups will ensure that all your data is safely backed up locally after a regular interval.
|
||||
On mobile and desktop apps you can enable automatic daily, weekly or monthly backups of your notes. Your data will backup automatically and backup file will be stored locally.
|
||||
|
||||
# [Desktop](#/tab/desktop)
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Scroll down in the Settings navigation menu and click on `Backup & export` section
|
||||
3. Select the Automatic backups interval from the dropdown
|
||||
|
||||

|
||||
|
||||
# [Web](#/tab/web)
|
||||
|
||||
> info
|
||||
>
|
||||
> On the **web** app there is no way to automatically save backups to a folder, that is why Notesnook only reminds the users when it's time to create a new backup.
|
||||
|
||||
1. Go to Settings
|
||||
2. Scroll down in the Settings navigation menu and click on `Backup & export` section
|
||||
3. Select the Backup reminders interval from the dropdown
|
||||
|
||||

|
||||
1. Go to Settings > Backup & Restore
|
||||
2. Select automatic backup frequency to enable automatic backups
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
@@ -68,44 +51,41 @@ For maximum safety against potential data loss, you can enable daily, weekly or
|
||||
|
||||
## Encrypted Backups (Recommended)
|
||||
|
||||
To keep your backups secure & private, it is recommended that you enable encryption on your backup files instead of storing them as plaintext data. **Starting from v2.6.0, encrypted backups are enabled by default for all users, free or Pro.**
|
||||
To keep your backups secure & private, it is recommended that you enable encryption on your backup files instead of storing them as plain json data.
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Scroll down in the Settings navigation menu and click on `Backup & export` section
|
||||
3. Click on the toggle next to `Backup encryption` to enable/disable encrypted backups
|
||||
1. Go to Settings > Backup & Restore
|
||||
2. Enable encrypted backups option
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to `Settings` > `Backup & Restore`
|
||||
2. Tap on `Backups`
|
||||
3. Tap on the toggle next to `Backup encryption` to enable/disable encrypted backups
|
||||
1. Go to Settings > Backup & Restore
|
||||
2. Press Backups
|
||||
3. Enable encrypted backups option
|
||||
|
||||
---
|
||||
|
||||
> info
|
||||
>
|
||||
> Backups are always encrypted with your account password.
|
||||
> Backups are encrypted with your account password.
|
||||
|
||||
# Restore a backup
|
||||
|
||||
At any point in time, you can restore a backup to recover lost data. However, **to restore a backup, you must be logged in to your Notesnook account.** Backups created on one account can be restored on another Notesnook account.
|
||||
To restore a backup, you must be logged in to your Notesnook account.
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Scroll down in the Settings navigation menu and click on `Backup & export` section
|
||||
3. Click on `Restore` button next to `Restore backup` heading
|
||||
4. Select the `.nnbackupz` or `.nnbackup` file from your PC that you want to restore.
|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Backup & Restore` section
|
||||
3. Click on `Restore backup`
|
||||
4. Select the `.nnbackup` file from your PC that you want to restore.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Backup & Restore` section
|
||||
3. Tap on `Restore backup`
|
||||
4. From `Restore backup` sheet, select the backup you want to restore. If your backup file is located in some other location, click on `Restore from files` on top right corner of the sheet then select the backup file.
|
||||
|
||||
<img src="/restore-backup-mobile.png" height="700px">
|
||||
3. Press on `Restore backup`
|
||||
4. From `Restore backup` sheet, select the backup you want to restore. If your backup file is located in some other location, click on `Open file manager` on top right corner of the sheet then select the backup file.
|
||||
|
||||
---
|
||||
|
||||
@@ -5,34 +5,33 @@ description: Notesnook let's you create unlimited notes for free. Learn how you
|
||||
|
||||
# Creating your first note
|
||||
|
||||
You are in a note taking app, the first thing you'd want to do is create a note.
|
||||
You are in a notes app, the first thing you want to do is make a note.
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Click on the `+` button on top right corner or click anywhere inside the editor to focus it.
|
||||
2. Start typing in the editor and a new note will be automatically created.
|
||||
2. Start typing in the editor and your note will be saved.
|
||||
3. As you type, your note is saved automatically whenever you stop for a few seconds.
|
||||
|
||||

|
||||
|
||||
> info
|
||||
>
|
||||
> The bottom right corner of the app will show the number of words of current note & the last saved time.
|
||||
> The bottom right corner of the app will show the no. of words of current note & the last saved time.
|
||||
>
|
||||
> 
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. On mobile regardless of what screen you are on, you can swipe from right to left to open the editor.
|
||||
2. You can also press the `+` button on bottom right corner to open the editor (note: the `+` button is visible only on some screens).
|
||||
3. Start typing in the editor and a new note will be automatically created.
|
||||
4. As you type, your note is saved automatically whenever you stop for a few seconds.
|
||||
1. On mobile, just like desktop, regardless of what screen you have opened, you can always swipe from right to left and open the editor.
|
||||
2. You can also press the `+` button on bottom right corner to open the editor when you are in notes, a topic or tag.
|
||||
3. As you type, your note is saved automatically whenever you stop for a few seconds.
|
||||
|
||||

|
||||
|
||||
> info
|
||||
>
|
||||
> Below the note title is the editor status bar. It shows you the number of words in the note and last saved time.
|
||||
> Below the note title is the editor status bar. It shows you the no. of words in the note and last saved time.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Notesnook allows you to delete your accounts and your data without any questions
|
||||
1. Open Notesnook web or desktop app
|
||||
2. Make sure you are logged in
|
||||
3. Go to Settings
|
||||
4. Click on `Delete account` button next to **Account removal** heading
|
||||
4. Scroll to the very end & click on `Delete account`
|
||||
|
||||
## [Mobile](#/tab/mobile)
|
||||
|
||||
@@ -20,7 +20,7 @@ Notesnook allows you to delete your accounts and your data without any questions
|
||||
2. Make sure you are logged in
|
||||
3. Go to Settings
|
||||
4. Go to `Account Settings`
|
||||
5. Tap on `Delete account`
|
||||
5. Click on `Delete account`
|
||||
|
||||
---
|
||||
|
||||
@@ -32,4 +32,4 @@ All your notes, notebooks, attachments, and any other data that you created with
|
||||
|
||||
### Is there any way to recover deleted data?
|
||||
|
||||
No. Once you delete your account, it's gone for good and there is no way for us or anyone else to recover it. If your data is important to you, make sure to take a [backup](/backup-and-restore-notes-in-notesnook) or [export your notes](/export-notes-from-notesnook) before deleting your account.
|
||||
No. Once you delete your account, it's gone for good and there is no way for us or anyone else to recover it. If your data is important to you, make sure to take a [backup](/backup-and-restore-notes-in-notesnook) before deleting your account or [exporting your notes](/export-notes-from-notesnook).
|
||||
|
||||
@@ -4,8 +4,6 @@ Notesnook supports quick actions from the "jumplist" menu (dock menu on macOS):
|
||||
|
||||
## [Windows](#/tab/windows)
|
||||
|
||||

|
||||
|
||||
## [Linux](#/tab/linux)
|
||||
|
||||

|
||||
@@ -18,8 +16,9 @@ Notesnook supports quick actions from the "jumplist" menu (dock menu on macOS):
|
||||
|
||||
## How it works?
|
||||
|
||||
Jumplist menu items are responsible for opening Notesnook at the specified page. For example:
|
||||
The jumplist menu doesn't work if you already have Notesnook opened. They are responsible for opening Notesnook at the specified page. For example:
|
||||
|
||||
1. Right click on the Notesnook icon in your taskbar/dock
|
||||
2. Click on "New notebook"
|
||||
3. Notice how the new notebook dialog is opened after the Notesnook app is focused
|
||||
1. Close Notesnook if you already have it opened
|
||||
2. Right click on the Notesnook icon in your taskbar/dock
|
||||
3. Click on "New notebook"
|
||||
4. Notice how the new notebook dialog is already opened after the app starts.
|
||||
|
||||
@@ -5,23 +5,23 @@ description: Notesnook is zero lock-in. You can always export and backup your no
|
||||
|
||||
# Exporting notes
|
||||
|
||||
You can export some or all your notes as PDF, HTML, Markdown and Plain text files on all Notesnook apps.
|
||||
You can export your notes as PDF, HTML, Markdown and Plain text files on desktop and mobile apps.
|
||||
|
||||
## Exporting a single note
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Right click on a note
|
||||
2. Click on `Export as`
|
||||
3. Select the desired format
|
||||
2. Click on 'Export'
|
||||
3. Select the desired format to export your note
|
||||
4. Wait a few moments while your note is exported
|
||||
5. Save the note at your desired location
|
||||
5. Save the note in desired location
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Tap the  button on a note
|
||||
2. Tap on `Export`
|
||||
3. Select the desired format
|
||||
1. Press the  button on a note
|
||||
2. Press on 'Export'
|
||||
3. Select the desired format to export your note
|
||||
4. Wait a few moments while your note is exported
|
||||
5. Exported notes are stored in `Notesnook/exported` folder.
|
||||
|
||||
@@ -31,19 +31,20 @@ You can export some or all your notes as PDF, HTML, Markdown and Plain text file
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Hold `Ctrl` & click on all the notes you want to export
|
||||
2. Right click & click on `Export as`
|
||||
3. Select your desired export format
|
||||
1. Hold `Ctrl` & Click on a note to enter multi-selection mode
|
||||
2. Select the notes you want to export then right click
|
||||
3. Click on "Export as" & select your desired export format
|
||||
4. Wait a few moments while your notes are exported
|
||||
5. Save the `.zip` file at your desired location
|
||||
5. Save the note in desired location
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Long press on a note to enter multi selection mode
|
||||
2. Tap on all the notes you want to export
|
||||
3. Press on the Export button on top right corner
|
||||
4. Select the desired format
|
||||
5. Exported notes are stored in `Notesnook/exported` folder
|
||||
1. Long press on a note item
|
||||
2. Press on "Select" on the action bar to enter selection mode
|
||||
3. Select the notes you want to export
|
||||
4. Press on the export icon on top right corner
|
||||
5. Select the desired format to export your note
|
||||
6. Exported notes are stored in `Notesnook/exported` folder
|
||||
|
||||
---
|
||||
|
||||
@@ -51,19 +52,14 @@ You can export some or all your notes as PDF, HTML, Markdown and Plain text file
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Scroll down to `Import & export` section
|
||||
3. Click on `Backup & export`
|
||||
4. Click on `Select format` dropdown next to `Export all notes` heading
|
||||
5. Select the desired format
|
||||
6. Enter account password for authentication
|
||||
7. Save the `.zip` file at your desired location
|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Backup & Restore` section
|
||||
3. Click on `Backup Data` to download the `.nnbackup` file
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Backup and Restore`
|
||||
3. Tap on `Export all notes`
|
||||
4. Select the desired format
|
||||
5. Enter account password for authentication
|
||||
6. Exported notes are stored in `Notesnook/exported` folder as a single .zip file
|
||||
3. Select "Export all notes"
|
||||
4. Select the desired format to export your notes
|
||||
5. Exported notes are stored in `Notesnook/exported` folder as a single .zip file
|
||||
|
||||
130
docs/help/contents/installation.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Be productive and keep your notes private with Notesnook. Download Notesnook on Windows, Mac, Linux, iOS, android or even access your notes on the web directly.
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
Notesnook is fully cross platform & downloadable on all your devices.
|
||||
|
||||
## Mac OS
|
||||
|
||||
# [Installing via dmg](#/tab/dmg)
|
||||
|
||||
Download the latest `.dmg` file from the links below.
|
||||
|
||||
- [For Intel Mac users](https://notesnook.com/releases/macos/notesnook_mac_x64.dmg)
|
||||
- [For M1 Macs users](https://notesnook.com/releases/macos/notesnook_mac_arm64.dmg)
|
||||
|
||||
Once you have the dmg downloaded,
|
||||
|
||||
1. Double click to open the .dmg file
|
||||
2. Move the Notesnook.app to `Applications` folder.
|
||||

|
||||
|
||||
Notesnook is now installed on your Mac. Start notesnook from the Launchpad.
|
||||
|
||||
# [Installing via terminal](#/tab/terminal)
|
||||
|
||||
If you have [homebrew](https://brew.sh/) installed, you can quickly install Notesnook via terminal:
|
||||
|
||||
```bash
|
||||
brew install notesnook
|
||||
```
|
||||
|
||||
Once installation completes, you can start Notesnook app from the Launchpad.
|
||||
|
||||
---
|
||||
|
||||
## Windows
|
||||
|
||||
# [Installing via .exe files](#/tab/exe)
|
||||
|
||||
Download the latest **.exe** file from the links below:
|
||||
|
||||
- [Notesnook x64 (Setup)](https://notesnook.com/releases/windows/notesnook_win_x64.exe)
|
||||
- [Notesnook x64 (Portable)](https://notesnook.com/releases/windows/notesnook_win_x64_portable.exe)
|
||||
|
||||
Once you have the **.exe** downloaded:
|
||||
|
||||
1. Double click to open the **.exe** file
|
||||
2. Follow the installation instructions to install the app.
|
||||
|
||||
# [Installing via PowerShell](#/tab/terminal)
|
||||
|
||||
To install Notesnook, run the following command from the command line or from PowerShell:
|
||||
|
||||
#### [Chocolatey](https://community.chocolatey.org/packages/notesnook)
|
||||
|
||||
```bash
|
||||
choco install notesnook
|
||||
```
|
||||
|
||||
#### [Winget](https://winget.run/pkg/Streetwriters/Notesnook)
|
||||
|
||||
```bash
|
||||
winget install -e --id Streetwriters.Notesnook
|
||||
```
|
||||
|
||||
#### [Scoop](https://scoop.sh/#/apps?q=notesnook&s=0&d=1&o=true)
|
||||
|
||||
```bash
|
||||
scoop bucket add extras && scoop install notesnook
|
||||
```
|
||||
|
||||
Start Notesnook from Windows Start Menu.
|
||||
|
||||
---
|
||||
|
||||
## Linux
|
||||
|
||||
# [AppImage (Recommended)](#/tab/appimage)
|
||||
|
||||
You can install Notesnook on any linux distro with AppImage.
|
||||
|
||||
[Download AppImage](https://notesnook.com/releases/linux/notesnook_linux_x86_64.AppImage)
|
||||
|
||||
# [.deb](#/tab/deb)
|
||||
|
||||
If you are on Ubuntu or debian based distro, you can install Notesnook via .deb package.
|
||||
|
||||
[Download .deb package](https://notesnook.com/releases/linux/notesnook_linux_amd64.deb)
|
||||
|
||||
# [.rpm](#/tab/rpm)
|
||||
|
||||
If you are on Fedora based distro, you can install Notesnook via .rpm package.
|
||||
|
||||
[Download .rpm package](https://notesnook.com/releases/linux/notesnook_linux_x86_64.rpm)
|
||||
|
||||
# [Snapcraft](#/tab/snap)
|
||||
|
||||
Notesnook can be downloaded on any linux distro via Snapcraft.
|
||||
|
||||
[Go to Snapcraft](https://snapcraft.io/notesnook)
|
||||
|
||||
---
|
||||
|
||||
## Android
|
||||
|
||||
# [Google Playstore](#/tab/android)
|
||||
|
||||
If you have Google play services installed on your Android phone, you can get Notesnook from Google Play store
|
||||
|
||||
[Download from playstore](https://play.google.com/store/apps/details?id=com.streetwriters.notesnook)
|
||||
|
||||
# [Github](#/tab/android-github)
|
||||
|
||||
You you want to download Notesnook app that is free of any google play services bloat or if you use a custom android rom, you can download Notesnook via Github.
|
||||
|
||||
- [Arm64-v8a.apk](https://notesnook.com/releases/android/notesnook-arm64-v8a.apk)
|
||||
- [Armeabi v7a.apk](https://notesnook.com/releases/android/notesnook-armeabi-v7a.apk)
|
||||
- [x86.apk](https://notesnook.com/releases/android/notesnook-x86.apk)
|
||||
- [x86_64.apk](https://notesnook.com/releases/android/notesnook-x86_64.apk)
|
||||
|
||||
---
|
||||
|
||||
## iOS
|
||||
|
||||
iOS users can get Notesnook from the AppStore.
|
||||
|
||||
[AppStore](https://apps.apple.com/us/app/notesnook-take-private-notes/id1544027013)
|
||||
@@ -13,19 +13,20 @@ Adding notes to private vault is useful when you do not want anyone to read your
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Go to `Vault` in `Security & privacy` section
|
||||
3. Click on `Create` button
|
||||
4. Enter the password for your vault (this password will be used to open all locked notes)
|
||||
5. Click on `Create` in the dialog to create the vault.
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Click on `Create a vault` in `Vault` section.
|
||||
3. Enter the password for the vault in dialog.
|
||||
4. Click on create to create the vault.
|
||||
5. Now you will be able to lock your notes with the password for vault.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar
|
||||
2. Scroll down to `Privacy and Security` section
|
||||
3. Tap on `Create vault`
|
||||
4. Enter password for the vault (this password will be used to open all locked notes)
|
||||
5. Tap on `Create` button to create the vault.
|
||||
3. Press on `Create vault`
|
||||
4. Enter password for the vault and reenter to confirm.
|
||||
5. Press on `Create` button to create the vault.
|
||||
6. Now you will be able to lock your notes with the password for vault.
|
||||
|
||||
---
|
||||
|
||||
@@ -34,14 +35,14 @@ Adding notes to private vault is useful when you do not want anyone to read your
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Right click on any note
|
||||
2. Select `Lock` from the context menu
|
||||
3. Enter the password for the vault
|
||||
4. Press `Enter` key to lock the note
|
||||
2. Select `Lock` from the `Note properties` menu
|
||||
3. Enter the password for the vault in dialog.
|
||||
4. Click on Lock to lock the note.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Tap the  button on a note
|
||||
2. Tap on `Lock` button in the note properties
|
||||
1. Press on the `three-dot menu` button
|
||||
2. Press on `Add to vault` button in `Note properties` menu
|
||||
3. Enter password for the vault
|
||||
4. Press on `Lock` to add note to vault.
|
||||
|
||||
@@ -56,16 +57,16 @@ To open, edit or delete a locked note, you must provide the password for the vau
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Right click on any note
|
||||
2. Select `Unlock` from the context menu
|
||||
2. Select `Unlock` from the `Note properties` menu
|
||||
3. Enter the password for the vault in dialog.
|
||||
4. Click on Unlock to remove note from vault
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Tap the  button on a note
|
||||
2. Tap on `Unlock` button in the note properties
|
||||
1. Press on the `three-dot menu` button
|
||||
2. Press on `Remove from vault` button in `Note properties` menu
|
||||
3. Enter password for the vault
|
||||
4. Tap on `Unlock` to remove note from vault.
|
||||
4. Press on `Unlock` to remove note from vault.
|
||||
|
||||
---
|
||||
|
||||
@@ -73,20 +74,19 @@ To open, edit or delete a locked note, you must provide the password for the vau
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Go to `Vault` in `Security & privacy` section
|
||||
3. Click on `Change` button next to `Change vault password` heading
|
||||
4. Enter the old and new password for the vault
|
||||
5. Click on `Change password` to update the password
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Click on `Change vault password` in `Vault` section.
|
||||
3. Enter the old and new password for the vault in dialog.
|
||||
4. Click on Change to update password
|
||||
5. All your locked notes will be encrypted with the new password.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Scroll down to `Privacy and Security` section
|
||||
3. Tap on `Vault`
|
||||
4. Press on `Change vault password`
|
||||
5. Enter the old and new password for the vault
|
||||
6. Click on Change to update password
|
||||
3. Press on `Change vault password`
|
||||
4. Click on Change to update password
|
||||
5. All your locked notes will be encrypted with the new password.
|
||||
|
||||
---
|
||||
|
||||
@@ -94,38 +94,34 @@ To open, edit or delete a locked note, you must provide the password for the vau
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Go to `Vault` in `Security & privacy` section
|
||||
3. Click on `Clear` button next to `Clear vault` heading
|
||||
4. Enter your vault password and click on clear vault. All notes in the vault will be deleted.
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Go to `Privacy and Security` section and Click on `Clear vault`.
|
||||
3. Enter your vault password and click on clear vault. All notes in the vault will be deleted.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Scroll down to `Privacy and Security` section
|
||||
3. Tap on `Vault`
|
||||
4. Tap on `Clear vault`
|
||||
5. Enter your vault password and tap on `Clear`. All notes in the vault will be deleted.
|
||||
2. Scroll down to `Privacy and Security` and go to `Vault`
|
||||
3. Tap on `Clear vault`
|
||||
4. Enter your vault password and tap on `Clear`. All notes in the vault will be deleted.
|
||||
|
||||
---
|
||||
|
||||
## Delete vault
|
||||
|
||||
In the event that you have forgotten your vault password, you can delete the vault and (optionally) delete all the notes in it.
|
||||
In the event that you have forgotten your vault password, you can delete the vault and optionally delete all the notes in it.
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to Settings
|
||||
2. Go to `Vault` in `Security & privacy` section
|
||||
3. Click on `Delete` button next to `Delete vault` heading
|
||||
4. Enter your account password and click on `Delete vault` to delete the vault
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Go to `Privacy and Security` section and Click on `Delete vault`.
|
||||
3. Enter your account password and click on clear vault. All notes in the vault will be deleted.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to Settings from Sidebar.
|
||||
2. Scroll down to `Privacy and Security` section
|
||||
3. Tap on `Vault`
|
||||
4. Tap on `Delete vault`
|
||||
5. Enter your account password and tap on `Delete` to delete the vault
|
||||
2. Scroll down to `Privacy and Security` and go to `Vault`
|
||||
3. Tap on `Clear vault`
|
||||
4. Enter your account password and tap on `Delete`. The vault will be deleted.
|
||||
|
||||
---
|
||||
|
||||
@@ -4,13 +4,7 @@ title: Notebooks
|
||||
|
||||
# Organizing notes with notebooks
|
||||
|
||||
Notebooks are a quick and easy way for multi-level notes organization. Starting from v2.4.7, topics are now optional in Notebooks and notes can be put directly under a notebook.
|
||||
|
||||
If you are coming from Evernote, you can think of Notebooks as "Evernote Stacks" & topics as "Evernote Notebooks". However, keep in mind, that there is a key difference:
|
||||
|
||||
> "Evernote Stacks" do not allow putting notes directly under them i.e., a Stack is just a collection of Notebooks and nothing else.
|
||||
|
||||
In Notesnook, one note can belong to multiple notebooks & topics. This allows for very flexible organization structures. For example, an author can "link" the character list to all the chapter topics instead of duplicating it for each chapter. Similarly, a user can link common notes between multiple topics without the need for any duplication.
|
||||
Notebooks consist of topics where you can add/create and edit your notes.
|
||||
|
||||
## Creating a notebook
|
||||
|
||||
@@ -104,78 +98,87 @@ In Notesnook, one note can belong to multiple notebooks & topics. This allows fo
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Go to `Notebooks` and open a notebook
|
||||
2. Click on the  button on top right corner
|
||||
3. Start writing in editor to create a note in the notebook
|
||||
2. Open the topic in which you want to add a note
|
||||
3. Click on the  button on top right corner
|
||||
4. Start writing in editor to create a note in the topic
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to `Notebooks` and open a notebook
|
||||
2. Click on the  button on bottom right corner to open the editor or just **swipe from right to left** to open editor
|
||||
3. Start writing in editor to create a note in the notebook
|
||||
2. Open the topic in which you want to add a note
|
||||
3. Click on the  button on bottom right corner to open the editor or just **swipe from right to left** to open editor
|
||||
4. Start writing in editor to create a note in the topic
|
||||
|
||||
---
|
||||
|
||||
> info
|
||||
>
|
||||
> Once a note is added to a notebook, you will see its path on top of the note in the list. Clicking on it will take you to the respective notebook/topic.
|
||||
> Once a note is added to a notebook, it's references will show on top of the note in the list. Clicking on it will take you to the topic.
|
||||
>
|
||||
> 
|
||||
|
||||
## Linking an existing note to a notebook/topic
|
||||
## Adding a note to a topic
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Right click on a note
|
||||
2. Click on `Notebooks` > `Link to...`
|
||||
3. Select the notebook or topic you want to link the note to (you can select multiple)
|
||||
4. Click `Done` to save your changes.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Tap the  button
|
||||
2. Tap on `Link Notebooks` button.
|
||||
3. Select the notebook or topic you want to link the note to (you can select multiple)
|
||||
4. Click the `Save` button at the top right corner to save your changes.
|
||||
|
||||
---
|
||||
|
||||
> info
|
||||
>
|
||||
> In Notesnook a single note can exist in multiple Notebooks and topics. However, a note will show only one reference on top.
|
||||
|
||||
## Linking multiple notes to a notebook/topic
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Hold `Ctrl` key and click on all the notes you want to link
|
||||
2. Right click on selected notes
|
||||
3. Click on `Notebooks` > `Link to...`
|
||||
4. Select the notebook or topic you want to link the note to (you can select multiple)
|
||||
5. Click `Done` to save your changes.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Long press on a note to enter multi selection mode.
|
||||
2. Tap on all the notes you want to link to select them
|
||||
3. Tap the `+` button in top header
|
||||
4. Select the notebook or topic you want to link the note to (you can select multiple)
|
||||
5. Click the `Save` button at the top right corner to save your changes.
|
||||
|
||||
---
|
||||
|
||||
## Remove note from a notebook/topic
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Right click on a note
|
||||
2. Click on `Notebooks` in the context menu
|
||||
3. Click on the notebook/topic you want to remove the note from
|
||||
2. Select `Add to Notebook(s)`
|
||||
3. Select the notebook and topic in which you want to add the note
|
||||
4. Click "Finish" to add the notes to the topic.
|
||||
5. You should now see the notebook/topic below the note title. You can click on it to navigate to the topic directly.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Press the  button
|
||||
2. Click on `See all linked notebooks`
|
||||
3. Long press on the notebook or topic you want to removve the note from
|
||||
2. Press on `Add to notebook` button.
|
||||
3. Select the notebook and topic in which you want to add the note.
|
||||
4. Once done, swipe down to close the sheet.
|
||||
5. You should now see the notebook/topic on the top left side of the note. You can click on it to navigate to the topic directly.
|
||||
|
||||
---
|
||||
|
||||
> info
|
||||
>
|
||||
> In Notesnook a single note can exist in multiple Notebooks and topics. However a note shows at most only one reference on top.
|
||||
|
||||
## Adding multiple notes to a topic
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Hold `Ctrl` key and click on the notes you want to select
|
||||
2. Right click on selected notes
|
||||
3. Click on `Add to notebook(s)`.
|
||||
4. Select the notebook and topic in which you want to add the notes
|
||||
5. Click "Finish" to add the notes to the topic.
|
||||
6. You should now see the notebook/topic below the note title. You can click on it to navigate to the topic directly.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Long press on a note.
|
||||
2. Click on `Select` from the `Action bar`.
|
||||
3. Select notes & press the  button in top header
|
||||
4. Select the notebook and topic in which you want to add the notes
|
||||
5. Once done, swipe down to close the sheet.
|
||||
|
||||
---
|
||||
|
||||
## Remove note from a topic
|
||||
|
||||
# [Desktop/Web](#/tab/web)
|
||||
|
||||
1. Right click on a note
|
||||
2. Select `Add to Notebook(s)`
|
||||
3. The notebooks in which the note exists will appear green. Click on the notebook to expand topics
|
||||
4. Click on the topics in notebook from which you want to remove th note
|
||||
5. Click "Finish" to save changes
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Press the  button
|
||||
2. Press on `Add to notebooks`
|
||||
3. The notebook(s) in which the note exists will appear green. Press on a notebook to expand topics
|
||||
4. Tap on topics you want to remove the note from
|
||||
5. Once done, swipe down to close `Add to notebook(s)` sheet
|
||||
|
||||
---
|
||||
|
||||
@@ -186,14 +189,14 @@ In Notesnook, one note can belong to multiple notebooks & topics. This allows fo
|
||||
1. Go to `Notebooks` and open the notebook from which you want to delete a topic.
|
||||
2. Right click on the topic
|
||||
3. Click on `Delete` to delete the topic.
|
||||
4. (Optional) Select whether you also want to delete the notes inside this topic.
|
||||
4. Select whether you also want to delete the notes inside this topic.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to `Notebooks` from side menu and open the notebook from which you want to delete a topic.
|
||||
2. Press the  button to open `Topic properties` sheet
|
||||
3. Press `Delete topic`
|
||||
4. (Optional) Select whether you also want to delete the notes inside this topic.
|
||||
4. Select whether you also want to delete the notes inside this topic.
|
||||
|
||||
---
|
||||
|
||||
@@ -205,6 +208,7 @@ In Notesnook, one note can belong to multiple notebooks & topics. This allows fo
|
||||
2. Right click on the notebook
|
||||
3. Click on `Move to trash`.
|
||||
4. Select whether you also want to delete the notes inside this notebook.
|
||||
5. Notebook will be kept in trash for 7 days after which it will be permanently removed.
|
||||
|
||||
# [Mobile](#/tab/mobile)
|
||||
|
||||
@@ -212,5 +216,6 @@ In Notesnook, one note can belong to multiple notebooks & topics. This allows fo
|
||||
2. Press the  button.
|
||||
3. Press `Move to trash` to delete the topic.
|
||||
4. Select whether you also want to delete the notes inside this notebook.
|
||||
5. Notebook will be kept in trash for 7 days after which it will be permanently removed.
|
||||
|
||||
---
|
||||
|
||||
@@ -16,14 +16,13 @@ Privacy mode enables some OS specific settings to enhance your privacy while wor
|
||||
|
||||
## [Desktop](#/tab/desktop)
|
||||
|
||||
1. Go to `Settings`
|
||||
2. Scroll down to `Security & privacy` section
|
||||
3. Click on `Privacy`
|
||||
4. Click on toggle next to `Privacy mode` to enable/disable privacy mode.
|
||||
1. Open `Settings`
|
||||
2. Click on `Privacy & security`
|
||||
3. Click on `Privacy mode` to enable/disable it.
|
||||
|
||||
## [Mobile](#/tab/mobile)
|
||||
|
||||
1. Go to `Settings`
|
||||
1. Open `Settings`
|
||||
2. Scroll down to `Privacy and Security`
|
||||
3. Tap on `Privacy mode` to enable/disable it
|
||||
|
||||
|
||||