mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-30 10:39:07 +02:00
Compare commits
52 Commits
2.6.12-and
...
fix/loadin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5294131370 | ||
|
|
a3464255ec | ||
|
|
088c6670a9 | ||
|
|
27840b9bd6 | ||
|
|
ae819504f0 | ||
|
|
af792e394c | ||
|
|
76a4d5c2a5 | ||
|
|
4e23acd608 | ||
|
|
3c65dcdd8b | ||
|
|
efea6e3972 | ||
|
|
185cca7880 | ||
|
|
fbded93405 | ||
|
|
674881b873 | ||
|
|
248fb6dd2d | ||
|
|
3211e7a149 | ||
|
|
08496c699e | ||
|
|
060fa25814 | ||
|
|
a6b1effc77 | ||
|
|
5cf9c4808e | ||
|
|
d5a44067fd | ||
|
|
0de463526f | ||
|
|
5fd79875ef | ||
|
|
bdb19ad325 | ||
|
|
7a12efc823 | ||
|
|
2f3bddf860 | ||
|
|
6127eb97af | ||
|
|
86386e14f3 | ||
|
|
9cc17c29f6 | ||
|
|
a31e863c7b | ||
|
|
15a32ac83a | ||
|
|
fa5b84ec0d | ||
|
|
7905584eac | ||
|
|
afc9b96a11 | ||
|
|
4e6466fc18 | ||
|
|
233baf78ce | ||
|
|
42d0bee37f | ||
|
|
ccadab6a10 | ||
|
|
bfe5a5cb96 | ||
|
|
88fc24c4cf | ||
|
|
8be4fea7a3 | ||
|
|
a3d39d8b6d | ||
|
|
ff36f0259a | ||
|
|
d13654088a | ||
|
|
8a6b97c757 | ||
|
|
d71e0c109b | ||
|
|
7e345303fe | ||
|
|
d881bdb77c | ||
|
|
4961102d8f | ||
|
|
4f2ab4f1da | ||
|
|
7540e34366 | ||
|
|
4d7a8b9ea0 | ||
|
|
812ecf93c8 |
2
.github/workflows/android.publish.yml
vendored
2
.github/workflows/android.publish.yml
vendored
@@ -40,6 +40,7 @@ jobs:
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
|
||||
- name: Publish to Playstore
|
||||
id: deploy
|
||||
@@ -64,6 +65,7 @@ jobs:
|
||||
alias: ${{ secrets.PUBLIC_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
|
||||
- name: Rename apk files
|
||||
run: |
|
||||
|
||||
176
apps/desktop/package-lock.json
generated
176
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.11",
|
||||
"version": "2.6.14",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.6.11",
|
||||
"version": "2.6.14",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -24,8 +24,8 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.9.3",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
"tree-kill": "^1.2.2",
|
||||
@@ -173,12 +173,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/asar": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.4.tgz",
|
||||
"integrity": "sha512-lykfY3TJRRWFeTxccEKdf1I6BLl2Plw81H0bbp4Fc5iEc67foDCa5pjJQULVgo0wF+Dli75f3xVcdb/67FFZ/g==",
|
||||
"version": "3.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.8.tgz",
|
||||
"integrity": "sha512-cmskk5M06ewHMZAplSiF4AlME3IrnnZhKnWbtwKVLRkdJkKyUVjMLhDIiPIx/+6zQWVlKX/LtmK9xDme7540Sg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"commander": "^5.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"minimatch": "^3.0.4"
|
||||
@@ -869,9 +868,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
|
||||
"integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==",
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/ms": "*"
|
||||
@@ -902,9 +901,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
|
||||
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==",
|
||||
"version": "0.7.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
@@ -970,9 +969,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/7zip-bin": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz",
|
||||
"integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
|
||||
"integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
@@ -1054,9 +1053,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/app-builder-lib": {
|
||||
"version": "24.6.4",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.6.4.tgz",
|
||||
"integrity": "sha512-m9931WXb83teb32N0rKg+ulbn6+Hl8NV5SUpVDOVz9MWOXfhV6AQtTdftf51zJJvCQnQugGtSqoLvgw6mdF/Rg==",
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.9.1.tgz",
|
||||
"integrity": "sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@develar/schema-utils": "~2.6.5",
|
||||
@@ -1065,15 +1064,15 @@
|
||||
"@electron/universal": "1.4.1",
|
||||
"@malept/flatpak-bundler": "^0.4.0",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"7zip-bin": "~5.1.1",
|
||||
"7zip-bin": "~5.2.0",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
"debug": "^4.3.4",
|
||||
"ejs": "^3.1.8",
|
||||
"electron-publish": "24.5.0",
|
||||
"electron-publish": "24.8.1",
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"hosted-git-info": "^4.1.0",
|
||||
@@ -1092,6 +1091,19 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/app-builder-lib/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -1116,9 +1128,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/async-exit-hook": {
|
||||
@@ -1280,16 +1292,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/builder-util": {
|
||||
"version": "24.5.0",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.5.0.tgz",
|
||||
"integrity": "sha512-STnBmZN/M5vGcv01u/K8l+H+kplTaq4PAIn3yeuufUKSpcdro0DhJWxPI81k5XcNfC//bjM3+n9nr8F9uV4uAQ==",
|
||||
"version": "24.8.1",
|
||||
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.8.1.tgz",
|
||||
"integrity": "sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/debug": "^4.1.6",
|
||||
"7zip-bin": "~5.1.1",
|
||||
"7zip-bin": "~5.2.0",
|
||||
"app-builder-bin": "4.0.0",
|
||||
"bluebird-lst": "^1.0.9",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"debug": "^4.3.4",
|
||||
@@ -1315,6 +1327,19 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/builder-util/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -1413,9 +1438,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
|
||||
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1702,14 +1727,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-builder": {
|
||||
"version": "24.6.4",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.6.4.tgz",
|
||||
"integrity": "sha512-BNcHRc9CWEuI9qt0E655bUBU/j/3wUCYBVKGu1kVpbN5lcUdEJJJeiO0NHK3dgKmra6LUUZlo+mWqc+OCbi0zw==",
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.9.1.tgz",
|
||||
"integrity": "sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "24.6.4",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"app-builder-lib": "24.9.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"fs-extra": "^10.1.0",
|
||||
"iconv-lite": "^0.6.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
@@ -1718,6 +1743,19 @@
|
||||
"dmg-license": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-builder/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dmg-license": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz",
|
||||
@@ -1774,9 +1812,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "25.9.3",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.3.tgz",
|
||||
"integrity": "sha512-dacaHg/PuwVcFRgPDCM5j7UDzqGJWOsbBRdS5wPKLNS/ejPeccIjuNUT1cqcrpvCJKAFW8swHWg9kdizNSEDHQ==",
|
||||
"version": "25.9.8",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
|
||||
"integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
@@ -1792,16 +1830,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-builder": {
|
||||
"version": "24.6.4",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.6.4.tgz",
|
||||
"integrity": "sha512-uNWQoU7pE7qOaIQ6CJHpBi44RJFVG8OHRBIadUxrsDJVwLLo8Nma3K/EEtx5/UyWAQYdcK4nVPYKoRqBb20hbA==",
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.9.1.tgz",
|
||||
"integrity": "sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"app-builder-lib": "24.6.4",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"app-builder-lib": "24.9.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"dmg-builder": "24.6.4",
|
||||
"dmg-builder": "24.9.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"is-ci": "^3.0.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
@@ -1863,6 +1901,19 @@
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-builder/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-notarize": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.2.2.tgz",
|
||||
@@ -1893,20 +1944,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish": {
|
||||
"version": "24.5.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.5.0.tgz",
|
||||
"integrity": "sha512-zwo70suH15L15B4ZWNDoEg27HIYoPsGJUF7xevLJLSI7JUPC8l2yLBdLGwqueJ5XkDL7ucYyRZzxJVR8ElV9BA==",
|
||||
"version": "24.8.1",
|
||||
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.8.1.tgz",
|
||||
"integrity": "sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
"builder-util": "24.5.0",
|
||||
"builder-util-runtime": "9.2.1",
|
||||
"builder-util": "24.8.1",
|
||||
"builder-util-runtime": "9.2.3",
|
||||
"chalk": "^4.1.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
"mime": "^2.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-publish/node_modules/builder-util-runtime": {
|
||||
"version": "9.2.3",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz",
|
||||
"integrity": "sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-trpc": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-trpc/-/electron-trpc-0.5.2.tgz",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.6.11",
|
||||
"version": "2.6.14",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
@@ -25,8 +25,8 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "25.9.3",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron-builder-notarize": "^1.5.1",
|
||||
"esbuild": "^0.17.19",
|
||||
"tree-kill": "^1.2.2",
|
||||
|
||||
@@ -17,14 +17,16 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseLoginDialog, eOpenLoginDialog } from "../../utils/events";
|
||||
import { sleep } from "../../utils/time";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
@@ -34,7 +36,6 @@ import { IconButton } from "../ui/icon-button";
|
||||
import { hideAuth, initialAuthMode } from "./common";
|
||||
import { Login } from "./login";
|
||||
import { Signup } from "./signup";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
|
||||
export const AuthMode = {
|
||||
login: 0,
|
||||
@@ -68,6 +69,9 @@ const AuthModal = () => {
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
actionSheetRef.current?.hide();
|
||||
setCurrentAuthMode(AuthMode.login);
|
||||
setVisible(false);
|
||||
@@ -77,6 +81,11 @@ const AuthModal = () => {
|
||||
<BaseDialog
|
||||
overlayOpacity={0}
|
||||
statusBarTranslucent={false}
|
||||
onShow={() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
}}
|
||||
onRequestClose={currentAuthMode !== AuthMode.welcomeSignup && close}
|
||||
visible={true}
|
||||
onClose={close}
|
||||
@@ -86,6 +95,7 @@ const AuthModal = () => {
|
||||
transparent={false}
|
||||
animated={false}
|
||||
centered={false}
|
||||
enableSheetKeyboardHandler
|
||||
>
|
||||
<KeyboardAwareScrollView
|
||||
style={{
|
||||
|
||||
@@ -66,6 +66,7 @@ const BaseDialog = ({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (useUserStore.getState().disableAppLockRequests) return;
|
||||
if (SettingsService.get().appLockMode === "background") {
|
||||
if (appState === "background") {
|
||||
setIntervalVisible(false);
|
||||
|
||||
@@ -99,8 +99,8 @@ const NoteItem = ({
|
||||
dateBy = "dateCreated",
|
||||
noOpen = false
|
||||
}) => {
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
const isEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote === item.id
|
||||
);
|
||||
const { colors } = useThemeColors();
|
||||
const compactMode = useIsCompactModeEnabled(item);
|
||||
@@ -112,8 +112,7 @@ const NoteItem = ({
|
||||
const reminder = getUpcomingReminder(reminders);
|
||||
const noteColor = ColorValues[item.color?.toLowerCase()];
|
||||
const tags = getTags(item);
|
||||
const primaryColors =
|
||||
currentEditingNote === item.id ? colors.selected : colors.primary;
|
||||
const primaryColors = isEditingNote ? colors.selected : colors.primary;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -25,13 +25,13 @@ import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
|
||||
export const Filler = ({ item }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
const isEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote === item.id
|
||||
);
|
||||
|
||||
const [selected] = useIsSelected(item);
|
||||
|
||||
return currentEditingNote === item.id || selected ? (
|
||||
return isEditingNote || selected ? (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -39,12 +39,11 @@ export const Filler = ({ item }) => {
|
||||
height: "150%",
|
||||
backgroundColor: colors.selected.background,
|
||||
borderLeftWidth: 5,
|
||||
borderLeftColor:
|
||||
currentEditingNote === item.id
|
||||
? item.color
|
||||
? colors.static[item.color]
|
||||
: colors.selected.accent
|
||||
: "transparent"
|
||||
borderLeftColor: isEditingNote
|
||||
? item.color
|
||||
? colors.static[item.color]
|
||||
: colors.selected.accent
|
||||
: "transparent"
|
||||
}}
|
||||
collapsable={false}
|
||||
/>
|
||||
|
||||
@@ -85,6 +85,7 @@ const SheetWrapper = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (useUserStore.getState().disableAppLockRequests) return;
|
||||
if (SettingsService.get().appLockMode === "background") {
|
||||
if (appState === "background") {
|
||||
const ref = fwdRef || localRef;
|
||||
|
||||
@@ -1,82 +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 { useEffect, useState } from "react";
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
||||
import { useEditorStore } from "../stores/use-editor-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { db } from "../common/database";
|
||||
import { NoteType } from "app/utils/types";
|
||||
import { useCallback } from "react";
|
||||
|
||||
/**
|
||||
* A hook that injects/removes tags from tags bar in editor
|
||||
*/
|
||||
const useEditorTags = () => {
|
||||
const currentEditingNote = useEditorStore(
|
||||
(state) => state.currentEditingNote
|
||||
);
|
||||
const tags = useTagStore((state) => state.tags);
|
||||
const [note, setNote] = useState<NoteType | null>(null);
|
||||
const [noteTags, setNoteTags] = useState<string[]>([]);
|
||||
|
||||
const refreshNote = useCallback(() => {
|
||||
const current = useEditorStore.getState().currentEditingNote;
|
||||
if (!current) {
|
||||
setNote(null);
|
||||
setNoteTags([]);
|
||||
return;
|
||||
}
|
||||
const note = db.notes?.note(current)?.data as NoteType;
|
||||
setNote(note ? { ...note } : null);
|
||||
getTags(note);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshNote();
|
||||
}, [currentEditingNote, refreshNote, tags]);
|
||||
|
||||
const load = useCallback(() => {
|
||||
if (!note) return;
|
||||
// tiny.call(EditorWebView, renderTags(noteTags));
|
||||
}, [note]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("updateTags", load);
|
||||
return () => {
|
||||
eUnSubscribeEvent("updateTags", load);
|
||||
};
|
||||
}, [load, noteTags]);
|
||||
|
||||
function getTags(note: NoteType) {
|
||||
if (!note || !note.tags) return [];
|
||||
const tags = note.tags
|
||||
.map((t) => (db.tags?.tag(t) ? { ...db.tags.tag(t) } : null))
|
||||
.filter((t) => t !== null);
|
||||
setNoteTags(tags);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, [load, noteTags]);
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export default useEditorTags;
|
||||
@@ -189,7 +189,7 @@ export const useEditorEvents = (
|
||||
readonly: readonly || editorPropReadonly,
|
||||
tools: tools || getDefaultPresets().default,
|
||||
noHeader: noHeader,
|
||||
noToolbar: readonly || editorPropReadonly || noToolbar,
|
||||
noToolbar: noToolbar,
|
||||
doubleSpacedLines: doubleSpacedLines,
|
||||
corsProxy: corsProxy,
|
||||
fontSize:
|
||||
|
||||
@@ -408,13 +408,13 @@ export const useEditor = (
|
||||
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
loadingState.current = currentContent.current?.data;
|
||||
if (currentContent.current?.data) {
|
||||
await postMessage(
|
||||
EditorEvents.html,
|
||||
currentContent.current?.data,
|
||||
10000
|
||||
);
|
||||
}
|
||||
|
||||
await postMessage(
|
||||
EditorEvents.html,
|
||||
currentContent.current?.data || "",
|
||||
10000
|
||||
);
|
||||
|
||||
loadingState.current = undefined;
|
||||
useEditorStore.getState().setReadonly(item.readonly);
|
||||
await commands.setTags(currentNote.current);
|
||||
|
||||
@@ -303,7 +303,9 @@ const subscriptions = {
|
||||
console.log("Subscription.verify", requestData);
|
||||
try {
|
||||
let result = await fetch(
|
||||
"http://192.168.43.5:4264/apple/verify",
|
||||
__DEV__
|
||||
? "http://192.168.43.5:4264/apple/verify"
|
||||
: "https://payments.streetwriters.co/apple/verify",
|
||||
requestData
|
||||
);
|
||||
console.log("Subscribed", result);
|
||||
|
||||
@@ -111,7 +111,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 2072
|
||||
versionCode 2074
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
- Added support for markdown with frontmatter in exports
|
||||
- Fixed a bug that would randomly cause the app to freeze on launch after using the app for some days.
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -997,7 +997,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2063;
|
||||
CURRENT_PROJECT_VERSION = 2066;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1071,7 +1071,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.12;
|
||||
MARKETING_VERSION = 2.6.15;
|
||||
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 = 2063;
|
||||
CURRENT_PROJECT_VERSION = 2066;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1176,7 +1176,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.12;
|
||||
MARKETING_VERSION = 2.6.15;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1186,7 +1186,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
|
||||
PRODUCT_NAME = Notesnook;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Distribution 2023";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2024";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1335,7 +1335,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2063;
|
||||
CURRENT_PROJECT_VERSION = 2066;
|
||||
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.12;
|
||||
MARKETING_VERSION = 2.6.15;
|
||||
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 = 2063;
|
||||
CURRENT_PROJECT_VERSION = 2066;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1391,12 +1391,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.12;
|
||||
MARKETING_VERSION = 2.6.15;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2023";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
@@ -1421,7 +1421,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2063;
|
||||
CURRENT_PROJECT_VERSION = 2066;
|
||||
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.12;
|
||||
MARKETING_VERSION = 2.6.15;
|
||||
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 = 2063;
|
||||
CURRENT_PROJECT_VERSION = 2066;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1601,12 +1601,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.6.12;
|
||||
MARKETING_VERSION = 2.6.15;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distrubution 2023";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2024";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -931,4 +931,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: b0cadd188aa428b98d1eaa0b84b3b6208e714119
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.14.2
|
||||
|
||||
File diff suppressed because one or more lines are too long
8
apps/mobile/package-lock.json
generated
8
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.6.11",
|
||||
"version": "2.6.14",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.6.11",
|
||||
"version": "2.6.14",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -62,7 +62,7 @@
|
||||
"@microsoft/signalr": "^7.0.10",
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"async-mutex": "^0.3.2",
|
||||
"dayjs": "1.11.9",
|
||||
"entities": "^4.3.1",
|
||||
@@ -26831,7 +26831,7 @@
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"@types/html-to-text": "^9.0.0",
|
||||
"@types/katex": "^0.16.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.6.12",
|
||||
"version": "2.6.15",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
5
apps/theme-builder/package-lock.json
generated
5
apps/theme-builder/package-lock.json
generated
@@ -1377,7 +1377,7 @@
|
||||
},
|
||||
"../web": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.14",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@aws-sdk/util-base64-browser": "^3.208.0",
|
||||
@@ -1388,7 +1388,7 @@
|
||||
"@emotion/react": "11.11.1",
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@notesnook-importer/core": "^1.7.1",
|
||||
"@notesnook-importer/core": "^2.0.0",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
@@ -1408,6 +1408,7 @@
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/react-query": "10.38.3",
|
||||
"@zip.js/zip.js": "^2.7.32",
|
||||
"allotment": "^1.19.0",
|
||||
"axios": "^1.3.4",
|
||||
"clipboard-polyfill": "4.0.0",
|
||||
|
||||
@@ -171,7 +171,7 @@ export class EditorModel {
|
||||
await this.tagInput.fill(tag);
|
||||
await this.tagInput.press("Enter");
|
||||
await this.tags
|
||||
.locator(":scope", { hasText: new RegExp(`^${tag}$`) })
|
||||
.locator("span", { hasText: new RegExp(`^${tag}$`) })
|
||||
.waitFor();
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ export class EditorModel {
|
||||
const count = await this.tags.count();
|
||||
for (let i = 0; i < count; ++i) {
|
||||
const item = this.tags.nth(i);
|
||||
const tag = await item.textContent();
|
||||
const tag = await item.locator("span").textContent();
|
||||
if (tag) tags.push(tag);
|
||||
}
|
||||
return tags;
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
content="This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1"
|
||||
/>
|
||||
<title>Test 1 - Notesnook</title>
|
||||
<meta name="created-on" content="xxx" />
|
||||
<meta name="last-edited-on" content="xxx" />
|
||||
<meta name="created-at" content="22-12-2023 11:33 AM" />
|
||||
<meta name="updated-at" content="22-12-2023 11:33 AM" />
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://app.notesnook.com/assets/editor-styles.css?d=0">
|
||||
|
||||
@@ -25,6 +28,11 @@
|
||||
color: #202124;
|
||||
font-family: "Open Sans", "Noto Sans", Frutiger, Calibri, Myriad, Arial, Ubuntu, Helvetica, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
.math-block {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
|
||||
@@ -2,6 +2,3 @@
|
||||
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
1737
apps/web/package-lock.json
generated
1737
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.6.11",
|
||||
"version": "2.6.14",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@emotion/react": "11.11.1",
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@notesnook-importer/core": "^1.7.1",
|
||||
"@notesnook-importer/core": "^2.0.0",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
@@ -36,6 +36,7 @@
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/react-query": "10.38.3",
|
||||
"@zip.js/zip.js": "^2.7.32",
|
||||
"allotment": "^1.19.0",
|
||||
"axios": "^1.3.4",
|
||||
"clipboard-polyfill": "4.0.0",
|
||||
|
||||
@@ -17,21 +17,10 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { db } from "./db";
|
||||
import { TaskManager } from "./task-manager";
|
||||
import { zip } from "../utils/zip";
|
||||
import { saveAs } from "file-saver";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
import Vault from "./vault";
|
||||
|
||||
const FORMAT_TO_EXT = {
|
||||
pdf: "pdf",
|
||||
md: "md",
|
||||
txt: "txt",
|
||||
html: "html",
|
||||
"md-frontmatter": "md"
|
||||
} as const;
|
||||
import { ExportStream } from "../utils/streams/export-stream";
|
||||
import { createZipStream } from "../utils/streams/zip-stream";
|
||||
import { createWriteStream } from "../utils/stream-saver";
|
||||
|
||||
export async function exportToPDF(
|
||||
title: string,
|
||||
@@ -85,75 +74,11 @@ export async function exportNotes(
|
||||
title: "Exporting notes",
|
||||
subtitle: "Please wait while your notes are exported.",
|
||||
action: async (report) => {
|
||||
let vaultUnlocked = false;
|
||||
|
||||
if (noteIds.length === 1 && db.notes?.note(noteIds[0])?.data.locked) {
|
||||
vaultUnlocked = await Vault.unlockVault();
|
||||
if (!vaultUnlocked) return false;
|
||||
} else if (noteIds.length > 1 && (await db.vault?.exists())) {
|
||||
vaultUnlocked = await Vault.unlockVault();
|
||||
if (!vaultUnlocked)
|
||||
showToast(
|
||||
"error",
|
||||
"Failed to unlock vault. Locked notes will be skipped."
|
||||
);
|
||||
}
|
||||
|
||||
const files = [];
|
||||
let index = 0;
|
||||
for (const noteId of noteIds) {
|
||||
const note = db.notes?.note(noteId);
|
||||
if (!note) continue;
|
||||
if (!vaultUnlocked && note.data.locked) continue;
|
||||
|
||||
report({
|
||||
current: ++index,
|
||||
total: noteIds.length,
|
||||
text: `Exporting "${note.title}"...`
|
||||
});
|
||||
|
||||
const rawContent = await db.content?.raw(note.data.contentId);
|
||||
const content = note.data.locked
|
||||
? await db.vault?.decryptContent(rawContent)
|
||||
: rawContent;
|
||||
|
||||
const exported = await note
|
||||
.export(format === "pdf" ? "html" : format, content)
|
||||
.catch((e: Error) => {
|
||||
console.error(note.data, e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to export note "${note.title}": ${e.message}`
|
||||
);
|
||||
});
|
||||
|
||||
if (typeof exported !== "string") {
|
||||
showToast("error", `Failed to export note "${note.title}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format === "pdf") {
|
||||
return await exportToPDF(note.title, exported);
|
||||
}
|
||||
|
||||
files.push({
|
||||
filename: note.title,
|
||||
content: exported,
|
||||
date: note.dateEdited
|
||||
});
|
||||
}
|
||||
|
||||
if (!files.length) return false;
|
||||
if (files.length === 1) {
|
||||
saveAs(
|
||||
new Blob([Buffer.from(files[0].content, "utf-8")]),
|
||||
`${sanitizeFilename(files[0].filename)}.${FORMAT_TO_EXT[format]}`
|
||||
);
|
||||
} else {
|
||||
const zipped = await zip(files, FORMAT_TO_EXT[format]);
|
||||
saveAs(new Blob([zipped.buffer]), "notes.zip");
|
||||
}
|
||||
|
||||
await new ExportStream(noteIds, format, undefined, (c, text) =>
|
||||
report({ total: noteIds.length, current: c, text })
|
||||
)
|
||||
.pipeThrough(createZipStream())
|
||||
.pipeTo(await createWriteStream("notes.zip"));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -36,9 +36,9 @@ import { TaskManager } from "./task-manager";
|
||||
import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { createWritableStream } from "./desktop-bridge";
|
||||
import { ZipStream } from "../utils/streams/zip-stream";
|
||||
import { createZipStream } from "../utils/streams/zip-stream";
|
||||
import { FeatureKeys } from "../dialogs/feature-dialog";
|
||||
import { Entry, Reader } from "../utils/zip-reader";
|
||||
import { ZipEntry, createUnzipIterator } from "../utils/streams/unzip-stream";
|
||||
|
||||
export const CREATE_BUTTON_MAP = {
|
||||
notes: {
|
||||
@@ -113,7 +113,7 @@ export async function createBackup() {
|
||||
controller.close();
|
||||
}
|
||||
})
|
||||
.pipeThrough(new ZipStream())
|
||||
.pipeThrough(createZipStream())
|
||||
.pipeTo(writeStream);
|
||||
}
|
||||
});
|
||||
@@ -138,8 +138,9 @@ export async function selectBackupFile() {
|
||||
|
||||
export async function importBackup() {
|
||||
const backupFile = await selectBackupFile();
|
||||
if (!backupFile) return;
|
||||
if (!backupFile) return false;
|
||||
await restoreBackupFile(backupFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function restoreBackupFile(backupFile: File) {
|
||||
@@ -165,12 +166,12 @@ export async function restoreBackupFile(backupFile: File) {
|
||||
type: "modal",
|
||||
action: async (report) => {
|
||||
let cachedPassword: string | undefined = undefined;
|
||||
const { read, totalFiles } = await Reader(backupFile);
|
||||
const entries: Entry[] = [];
|
||||
// const { read, totalFiles } = await Reader(backupFile);
|
||||
const entries: ZipEntry[] = [];
|
||||
let filesProcessed = 0;
|
||||
|
||||
let isValid = false;
|
||||
for await (const entry of read()) {
|
||||
for await (const entry of createUnzipIterator(backupFile)) {
|
||||
if (entry.name === ".nnbackup") {
|
||||
isValid = true;
|
||||
continue;
|
||||
@@ -199,7 +200,6 @@ export async function restoreBackupFile(backupFile: File) {
|
||||
}
|
||||
|
||||
report({
|
||||
total: totalFiles,
|
||||
text: `Processed ${entry.name}`,
|
||||
current: filesProcessed++
|
||||
});
|
||||
|
||||
@@ -35,20 +35,11 @@ type Item = {
|
||||
};
|
||||
|
||||
async function moveNotesToTrash(notes: Item[], confirm = true) {
|
||||
const item = notes[0];
|
||||
if (confirm && !(await showMultiDeleteConfirmation(notes.length))) return;
|
||||
|
||||
if (notes.length === 1) {
|
||||
if (
|
||||
item.locked &&
|
||||
!(await Vault.unlockNote(item.id, "unlock_and_delete_note"))
|
||||
)
|
||||
return;
|
||||
item.locked = false;
|
||||
}
|
||||
if (notes.some((n) => n.locked) && !(await Vault.unlockVault())) return;
|
||||
|
||||
const items = notes.map((item) => {
|
||||
if (item.locked || db.monographs?.isPublished(item.id)) return 0;
|
||||
if (db.monographs?.isPublished(item.id)) return 0;
|
||||
return item.id;
|
||||
});
|
||||
|
||||
|
||||
@@ -164,7 +164,11 @@ export function Attachment({
|
||||
<AttachmentError
|
||||
color={"icon-error"}
|
||||
size={16}
|
||||
title={attachment.failed}
|
||||
title={
|
||||
typeof attachment.failed === "object"
|
||||
? attachment.failed.toString()
|
||||
: attachment.failed
|
||||
}
|
||||
/>
|
||||
) : attachment.working ? (
|
||||
<Loading size={16} />
|
||||
|
||||
@@ -557,7 +557,7 @@ function EditorChrome(
|
||||
<Box
|
||||
id="editorToolbar"
|
||||
sx={{
|
||||
display: readonly ? "none" : "flex",
|
||||
display: "flex",
|
||||
bg: "background",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
|
||||
@@ -51,6 +51,7 @@ function Toolbar() {
|
||||
const sessionId = useStore((store) => store.session.id);
|
||||
const isDeleted = useStore((store) => store.session.isDeleted);
|
||||
const isLocked = useStore((store) => store.session.locked);
|
||||
const readonly = useStore((store) => store.session.readonly);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||
const toggleFocusMode = useAppStore((store) => store.toggleFocusMode);
|
||||
@@ -227,6 +228,7 @@ function Toolbar() {
|
||||
}}
|
||||
transition={{ duration: 0.5 }}
|
||||
defaultValue={title}
|
||||
readOnly={readonly}
|
||||
onChange={(e) => {
|
||||
AppEventManager.publish(AppEvents.changeNoteTitle, {
|
||||
title: e.target.value,
|
||||
|
||||
@@ -472,7 +472,7 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
|
||||
{
|
||||
type: "button",
|
||||
key: "copy-as-text",
|
||||
tooltip: `Export as Text`,
|
||||
tooltip: `Copy as Text`,
|
||||
title: "Text",
|
||||
icon: Plaintext.path,
|
||||
onClick: () => copyNote(note.id, "txt")
|
||||
@@ -480,7 +480,7 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
|
||||
{
|
||||
type: "button",
|
||||
key: "copy-as-markdown",
|
||||
tooltip: `Export as Markdown`,
|
||||
tooltip: `Copy as Markdown`,
|
||||
title: "Markdown",
|
||||
icon: Markdown.path,
|
||||
onClick: () => copyNote(note.id, "md")
|
||||
@@ -539,7 +539,7 @@ const menuItems: (note: any, items?: any[]) => MenuItem[] = (
|
||||
icon: Trash.path,
|
||||
isDisabled:
|
||||
items.length === 1
|
||||
? db.monographs?.isPublished(note.id) || note.locked
|
||||
? db.monographs?.isPublished(note.id)
|
||||
: items.some((item) => !db.notes?.note(item.id).synced()),
|
||||
onClick: () => Multiselect.moveNotesToTrash(items, items.length > 1),
|
||||
multiSelect: true
|
||||
|
||||
@@ -59,7 +59,9 @@ function TrashItem(props: TrashItemProps) {
|
||||
menuItems={menuItems}
|
||||
onClick={() => {
|
||||
if (item.itemType === "note")
|
||||
hashNavigate(`/notes/${item.id}/edit`, { replace: true });
|
||||
!item.locked
|
||||
? hashNavigate(`/notes/${item.id}/edit`, { replace: true })
|
||||
: showToast("error", "Locked notes cannot be previewed in trash.");
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -89,17 +89,7 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
)
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "Date time shortcuts in note title",
|
||||
subtitle:
|
||||
"You can now use date time shortcuts (/now, /date & /time) in note & task list titles."
|
||||
},
|
||||
{
|
||||
title: "Restore backups without an account",
|
||||
subtitle: "You can now restore backups without logging in."
|
||||
}
|
||||
],
|
||||
: [],
|
||||
cta: {
|
||||
title: "Got it",
|
||||
icon: Checkmark,
|
||||
|
||||
@@ -67,8 +67,9 @@ export const BackupExportSettings: SettingsGroup[] = [
|
||||
type: "button",
|
||||
title: "Restore",
|
||||
action: async () => {
|
||||
await importBackup();
|
||||
await useAppStore.getState().refresh();
|
||||
if (await importBackup()) {
|
||||
await useAppStore.getState().refresh();
|
||||
}
|
||||
},
|
||||
variant: "secondary"
|
||||
}
|
||||
@@ -179,6 +180,11 @@ export const BackupExportSettings: SettingsGroup[] = [
|
||||
{ value: "-", title: "Select format" },
|
||||
{ value: "txt", title: "Text" },
|
||||
{ value: "md", title: "Markdown", premium: true },
|
||||
{
|
||||
value: "md-frontmatter",
|
||||
title: "Markdown + Frontmatter",
|
||||
premium: true
|
||||
},
|
||||
{ value: "html", title: "HTML", premium: true }
|
||||
],
|
||||
selectedOption: () => "-",
|
||||
@@ -186,7 +192,7 @@ export const BackupExportSettings: SettingsGroup[] = [
|
||||
if (!db.notes || value === "-") return;
|
||||
if (await verifyAccount())
|
||||
await exportNotes(
|
||||
value as "txt" | "md" | "html",
|
||||
value as "txt" | "md" | "html" | "md-frontmatter",
|
||||
db.notes.all.map((n) => n.id)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function Importer() {
|
||||
<>
|
||||
<CheckCircleOutline color="accent" sx={{ mt: 150 }} />
|
||||
<Text variant="body" my={2} sx={{ textAlign: "center" }}>
|
||||
Import successful. {errors.length} errors occured.
|
||||
Import completed. {errors.length} errors occured.
|
||||
</Text>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -136,23 +136,25 @@ export function Importer() {
|
||||
</Text>
|
||||
</Flex>
|
||||
<Button
|
||||
variant="accent"
|
||||
onClick={async () => {
|
||||
setIsDone(false);
|
||||
setIsImporting(true);
|
||||
|
||||
await db.syncer?.acquireLock(async () => {
|
||||
try {
|
||||
for await (const {
|
||||
count,
|
||||
filesRead,
|
||||
totalFiles
|
||||
} of importFiles(files)) {
|
||||
if (notesCounter.current)
|
||||
notesCounter.current.innerText = `${count}`;
|
||||
if (importProgress.current)
|
||||
importProgress.current.style.width = `${
|
||||
(filesRead / totalFiles) * 100
|
||||
}%`;
|
||||
for await (const message of importFiles(files)) {
|
||||
switch (message.type) {
|
||||
case "error":
|
||||
setErrors((errors) => [...errors, message.error]);
|
||||
break;
|
||||
case "progress": {
|
||||
const { count } = message;
|
||||
if (notesCounter.current)
|
||||
notesCounter.current.innerText = `${count}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -196,40 +196,27 @@ export function SubscriptionStatus() {
|
||||
Request a refund
|
||||
</Button>
|
||||
</>
|
||||
) : isBasic ? (
|
||||
<>
|
||||
<Button
|
||||
variant="accent"
|
||||
onClick={async () => {
|
||||
showBuyDialog();
|
||||
}}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={activateTrial}
|
||||
sx={{ bg: "background" }}
|
||||
>
|
||||
{isActivatingTrial ? (
|
||||
<Loading size={16} />
|
||||
) : (
|
||||
"Try free for 14 days"
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
) : isTrial ? (
|
||||
<>
|
||||
<Button
|
||||
variant="accent"
|
||||
onClick={async () => {
|
||||
showBuyDialog();
|
||||
}}
|
||||
>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{!isPro && (
|
||||
<>
|
||||
<Button variant="accent" onClick={() => showBuyDialog()}>
|
||||
{isProCancelled ? "Resubscribe" : "Upgrade to Pro"}
|
||||
</Button>
|
||||
{isBasic && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={activateTrial}
|
||||
sx={{ bg: "background" }}
|
||||
>
|
||||
{isActivatingTrial ? (
|
||||
<Loading size={16} />
|
||||
) : (
|
||||
"Try free for 14 days"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{isBasic ? <Features /> : null}
|
||||
|
||||
@@ -75,16 +75,17 @@ export const AboutSettings: SettingsGroup[] = [
|
||||
{
|
||||
key: "source-code",
|
||||
title: "Source code",
|
||||
description: "All the source code for Notesnook is available & open for everyone on GitHub.",
|
||||
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"
|
||||
);
|
||||
},
|
||||
window.open(
|
||||
"https://github.com/streetwriters/notesnook",
|
||||
"_blank"
|
||||
);
|
||||
},
|
||||
title: "View source code",
|
||||
variant: "secondary"
|
||||
}
|
||||
@@ -234,6 +235,23 @@ export const LegalSettings: SettingsGroup[] = [
|
||||
variant: "secondary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "license",
|
||||
title: "License",
|
||||
description: "GNU GENERAL PUBLIC LICENSE Version 3",
|
||||
components: [
|
||||
{
|
||||
type: "button",
|
||||
action: () =>
|
||||
void window.open(
|
||||
"https://github.com/streetwriters/notesnook/blob/master/LICENSE",
|
||||
"_blank"
|
||||
),
|
||||
title: "Read License",
|
||||
variant: "secondary"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export function useKeyboardListNavigation(
|
||||
focusItemAt(nextIndex);
|
||||
return true;
|
||||
},
|
||||
"Ctrl-a": () => {
|
||||
"Mod-a": () => {
|
||||
resetSelection();
|
||||
for (let i = 0; i < length; ++i) select(i);
|
||||
return true;
|
||||
|
||||
@@ -150,5 +150,6 @@ export default hashroutes;
|
||||
export type HashRoute = keyof typeof hashroutes;
|
||||
|
||||
function afterAction() {
|
||||
window.location.hash = "";
|
||||
hashNavigate("/", { replace: true, notify: false });
|
||||
if (!history.state.replace) history.back();
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ export function hashNavigate<TPath extends HashRoute>(
|
||||
let url: string = path;
|
||||
if (addNonce) url += `/${++lastNonce}`;
|
||||
|
||||
if (replace) window.history.replaceState(null, "", `#${url}`);
|
||||
else window.history.pushState(null, "", `#${url}`);
|
||||
if (replace) window.history.replaceState({ replace }, "", `#${url}`);
|
||||
else window.history.pushState({ replace }, "", `#${url}`);
|
||||
|
||||
const event = new HashChangeEvent("hashchange");
|
||||
(event as any).notify = notify;
|
||||
|
||||
@@ -184,7 +184,7 @@ class AppStore extends BaseStore {
|
||||
|
||||
toggleRealtimeSync = () => {
|
||||
const { isRealtimeSyncEnabled } = this.get();
|
||||
Config.set("realtimeSyncEnabled", !isRealtimeSyncEnabled);
|
||||
Config.set("isRealtimeSyncEnabled", !isRealtimeSyncEnabled);
|
||||
this.set(
|
||||
(state) => (state.isRealtimeSyncEnabled = !state.isRealtimeSyncEnabled)
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ import { store as editorStore } from "./editor-store";
|
||||
import { checkAttachment } from "../common/attachments";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { AttachmentStream } from "../utils/streams/attachment-stream";
|
||||
import { ZipStream } from "../utils/streams/zip-stream";
|
||||
import { createZipStream } from "../utils/streams/zip-stream";
|
||||
import { createWriteStream } from "../utils/stream-saver";
|
||||
|
||||
let abortController = undefined;
|
||||
@@ -67,7 +67,7 @@ class AttachmentStore extends BaseStore {
|
||||
}
|
||||
);
|
||||
await attachmentStream
|
||||
.pipeThrough(new ZipStream())
|
||||
.pipeThrough(createZipStream(abortController.signal))
|
||||
.pipeTo(
|
||||
await createWriteStream("attachments.zip", {
|
||||
signal: abortController.signal
|
||||
|
||||
@@ -30,6 +30,9 @@ export function showFilePicker({
|
||||
input.setAttribute("type", "file");
|
||||
input.setAttribute("accept", acceptedFileTypes);
|
||||
input.dispatchEvent(new MouseEvent("click"));
|
||||
input.oncancel = async function () {
|
||||
resolve(undefined);
|
||||
};
|
||||
input.onchange = async function () {
|
||||
if (!input.files) return resolve(undefined);
|
||||
const file = input.files[0];
|
||||
|
||||
@@ -18,28 +18,26 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { db } from "../common/db";
|
||||
import type { Note, Notebook } from "@notesnook-importer/core";
|
||||
import {
|
||||
Note,
|
||||
Notebook,
|
||||
ContentType
|
||||
} from "@notesnook-importer/core/dist/src/models";
|
||||
import {
|
||||
ATTACHMENTS_DIRECTORY_NAME,
|
||||
NOTE_DATA_FILENAME
|
||||
} from "@notesnook-importer/core/dist/src/utils/note-stream";
|
||||
import { Cipher } from "@notesnook/crypto/dist/src/types";
|
||||
import { Reader, Entry } from "./zip-reader";
|
||||
import { path } from "@notesnook-importer/core/dist/src/utils/path";
|
||||
import { ZipEntry, createUnzipIterator } from "./streams/unzip-stream";
|
||||
|
||||
export async function* importFiles(zipFiles: File[]) {
|
||||
const { default: FS } = await import("../interfaces/fs");
|
||||
for (const zip of zipFiles) {
|
||||
let count = 0;
|
||||
let filesRead = 0;
|
||||
|
||||
const attachments: Record<
|
||||
string,
|
||||
Omit<Cipher, "cipher" | "format"> & { key: any; chunkSize: number }
|
||||
> = {};
|
||||
const { read, totalFiles } = await Reader(zip);
|
||||
const attachments: Record<string, any> = {};
|
||||
|
||||
for await (const entry of read()) {
|
||||
for await (const entry of createUnzipIterator(zip)) {
|
||||
++filesRead;
|
||||
|
||||
const isAttachment = entry.name.includes(
|
||||
@@ -47,52 +45,70 @@ export async function* importFiles(zipFiles: File[]) {
|
||||
);
|
||||
const isNote = !isAttachment && entry.name.endsWith(NOTE_DATA_FILENAME);
|
||||
|
||||
if (isAttachment) {
|
||||
const name = path.basename(entry.name);
|
||||
if (!name || attachments[name] || db.attachments?.exists(name))
|
||||
continue;
|
||||
|
||||
const data = await entry.arrayBuffer();
|
||||
const { hash } = await FS.hashBuffer(new Uint8Array(data));
|
||||
if (hash !== name) {
|
||||
throw new Error(`integrity check failed: ${name} !== ${hash}`);
|
||||
try {
|
||||
if (isAttachment) {
|
||||
await processAttachment(entry, attachments);
|
||||
} else if (isNote) {
|
||||
await processNote(entry, attachments);
|
||||
++count;
|
||||
}
|
||||
|
||||
const file = new File([data], name, {
|
||||
type: "application/octet-stream"
|
||||
});
|
||||
const key = await db.attachments?.generateKey();
|
||||
const cipherData = await FS.writeEncryptedFile(file, key, name);
|
||||
attachments[name] = { ...cipherData, key };
|
||||
} else if (isNote) {
|
||||
const note = await fileToJson<Note>(entry);
|
||||
for (const attachment of note.attachments || []) {
|
||||
const cipherData = attachments[attachment.hash];
|
||||
if (!cipherData || db.attachments?.exists(attachment.hash)) continue;
|
||||
|
||||
await db.attachments?.add({
|
||||
...cipherData,
|
||||
hash: attachment.hash,
|
||||
hashType: attachment.hashType,
|
||||
filename: attachment.filename,
|
||||
type: attachment.mime
|
||||
});
|
||||
}
|
||||
await importNote(note);
|
||||
|
||||
++count;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) yield { type: "error" as const, error: e };
|
||||
}
|
||||
|
||||
yield {
|
||||
type: "progress" as const,
|
||||
count,
|
||||
totalFiles,
|
||||
filesRead
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function importNote(note: Note) {
|
||||
async function processAttachment(
|
||||
entry: ZipEntry,
|
||||
attachments: Record<string, any>
|
||||
) {
|
||||
const name = path.basename(entry.name);
|
||||
if (!name || attachments[name] || db.attachments?.exists(name)) return;
|
||||
|
||||
const { default: FS } = await import("../interfaces/fs");
|
||||
|
||||
const data = await entry.arrayBuffer();
|
||||
const { hash } = await FS.hashBuffer(new Uint8Array(data));
|
||||
if (hash !== name) {
|
||||
throw new Error(`integrity check failed: ${name} !== ${hash}`);
|
||||
}
|
||||
|
||||
const file = new File([data], name, {
|
||||
type: "application/octet-stream"
|
||||
});
|
||||
const key = await db.attachments?.generateKey();
|
||||
const cipherData = await FS.writeEncryptedFile(file, key, name);
|
||||
attachments[name] = { ...cipherData, key };
|
||||
}
|
||||
|
||||
async function processNote(entry: ZipEntry, attachments: Record<string, any>) {
|
||||
const note = await fileToJson<Note>(entry);
|
||||
for (const attachment of note.attachments || []) {
|
||||
const cipherData = attachments[attachment.hash];
|
||||
if (!cipherData || db.attachments?.exists(attachment.hash)) continue;
|
||||
|
||||
await db.attachments?.add({
|
||||
...cipherData,
|
||||
hash: attachment.hash,
|
||||
hashType: attachment.hashType,
|
||||
filename: attachment.filename,
|
||||
type: attachment.mime
|
||||
});
|
||||
}
|
||||
|
||||
if (!note.content)
|
||||
note.content = {
|
||||
data: "<p></p>",
|
||||
type: ContentType.HTML
|
||||
};
|
||||
|
||||
if (note.content?.type === "html") (note.content.type as string) = "tiptap";
|
||||
else throw new Error("Invalid content type: " + note.content?.type);
|
||||
|
||||
@@ -101,7 +117,7 @@ async function importNote(note: Note) {
|
||||
const noteId = await db.notes?.add(note);
|
||||
|
||||
for (const nb of notebooks) {
|
||||
const notebook = await importNotebook(nb);
|
||||
const notebook = await importNotebook(nb).catch(() => ({ id: undefined }));
|
||||
if (!notebook.id) continue;
|
||||
await db.notes?.addToNotebook(
|
||||
{ id: notebook.id, topic: notebook.topic },
|
||||
@@ -110,7 +126,7 @@ async function importNote(note: Note) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fileToJson<T>(file: Entry) {
|
||||
async function fileToJson<T>(file: ZipEntry) {
|
||||
const text = await file.text();
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
@@ -126,9 +142,10 @@ async function importNotebook(
|
||||
title: notebook.notebook,
|
||||
topics: notebook.topic ? [notebook.topic] : []
|
||||
});
|
||||
nb = db.notebooks?.notebook(nbId).data;
|
||||
nb = db.notebooks?.notebook(nbId)?.data;
|
||||
if (!nb) return {};
|
||||
}
|
||||
const topic = nb?.topics.find((t: any) => t.title === notebook.topic);
|
||||
|
||||
const topic = nb.topics.find((t: any) => t.title === notebook.topic);
|
||||
return { id: nb ? nb.id : undefined, topic: topic ? topic.id : undefined };
|
||||
}
|
||||
|
||||
@@ -23,27 +23,36 @@ import {
|
||||
logManager
|
||||
} from "@notesnook/core/dist/logger";
|
||||
import { LogMessage } from "@notesnook/logger";
|
||||
import FileSaver from "file-saver";
|
||||
import { DatabasePersistence, NNStorage } from "../interfaces/storage";
|
||||
import { zip } from "./zip";
|
||||
import { ZipFile, createZipStream } from "./streams/zip-stream";
|
||||
import { createWriteStream } from "./stream-saver";
|
||||
|
||||
let logger: typeof _logger;
|
||||
async function initalizeLogger(persistence: DatabasePersistence = "db") {
|
||||
initalize(await NNStorage.createInstance("Logs", persistence));
|
||||
initalize(await NNStorage.createInstance("Logs", persistence), false);
|
||||
logger = _logger.scope("notesnook-web");
|
||||
}
|
||||
|
||||
async function downloadLogs() {
|
||||
if (!logManager) return;
|
||||
const allLogs = await logManager.get();
|
||||
const files = allLogs.map((log) => ({
|
||||
filename: log.key,
|
||||
content: (log.logs as LogMessage[])
|
||||
.map((line) => JSON.stringify(line))
|
||||
.join("\n")
|
||||
}));
|
||||
const archive = await zip(files, "log");
|
||||
FileSaver.saveAs(new Blob([archive.buffer]), "notesnook-logs.zip");
|
||||
const textEncoder = new TextEncoder();
|
||||
await new ReadableStream<ZipFile>({
|
||||
pull(controller) {
|
||||
for (const log of allLogs) {
|
||||
controller.enqueue({
|
||||
path: log.key,
|
||||
data: textEncoder.encode(
|
||||
(log.logs as LogMessage[])
|
||||
.map((line) => JSON.stringify(line))
|
||||
.join("\n")
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.pipeThrough(createZipStream())
|
||||
.pipeTo(await createWriteStream("notesnook-logs.zip"));
|
||||
}
|
||||
|
||||
async function clearLogs() {
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { db } from "../../common/db";
|
||||
import { lazify } from "../lazify";
|
||||
import { makeUniqueFilename } from "./utils";
|
||||
import { ZipFile } from "./zip-stream";
|
||||
|
||||
export const METADATA_FILENAME = "metadata.json";
|
||||
@@ -84,17 +85,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]
|
||||
}`;
|
||||
}
|
||||
|
||||
115
apps/web/src/utils/streams/export-stream.ts
Normal file
115
apps/web/src/utils/streams/export-stream.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 { sanitizeFilename } from "@notesnook/common";
|
||||
import { db } from "../../common/db";
|
||||
import { exportToPDF } from "../../common/export";
|
||||
import Vault from "../../common/vault";
|
||||
import { showToast } from "../toast";
|
||||
import { makeUniqueFilename } from "./utils";
|
||||
import { ZipFile } from "./zip-stream";
|
||||
|
||||
const FORMAT_TO_EXT = {
|
||||
pdf: "pdf",
|
||||
md: "md",
|
||||
txt: "txt",
|
||||
html: "html",
|
||||
"md-frontmatter": "md"
|
||||
} as const;
|
||||
|
||||
export class ExportStream extends ReadableStream<ZipFile> {
|
||||
constructor(
|
||||
noteIds: string[],
|
||||
format: "pdf" | "md" | "txt" | "html" | "md-frontmatter",
|
||||
signal?: AbortSignal,
|
||||
onProgress?: (current: number, text: string) => void
|
||||
) {
|
||||
const textEncoder = new TextEncoder();
|
||||
let index = 0;
|
||||
const counters: Record<string, number> = {};
|
||||
let vaultUnlocked = false;
|
||||
|
||||
super({
|
||||
async start() {
|
||||
if (noteIds.length === 1 && db.notes?.note(noteIds[0])?.data.locked) {
|
||||
vaultUnlocked = await Vault.unlockVault();
|
||||
if (!vaultUnlocked) return false;
|
||||
} else if (noteIds.length > 1 && (await db.vault?.exists())) {
|
||||
vaultUnlocked = await Vault.unlockVault();
|
||||
if (!vaultUnlocked)
|
||||
showToast(
|
||||
"error",
|
||||
"Failed to unlock vault. Locked notes will be skipped."
|
||||
);
|
||||
}
|
||||
},
|
||||
async pull(controller) {
|
||||
if (signal?.aborted) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const note = db.notes?.note(noteIds[index++]);
|
||||
if (!note) return;
|
||||
if (!vaultUnlocked && note.data.locked) return;
|
||||
|
||||
onProgress && onProgress(index, `Exporting "${note.title}"...`);
|
||||
|
||||
const rawContent = await db.content?.raw(note.data.contentId);
|
||||
const content = note.data.locked
|
||||
? await db.vault?.decryptContent(rawContent)
|
||||
: rawContent;
|
||||
|
||||
const exported = await note
|
||||
.export(format === "pdf" ? "html" : format, content)
|
||||
.catch((e: Error) => {
|
||||
console.error(note.data, e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to export note "${note.title}": ${e.message}`
|
||||
);
|
||||
});
|
||||
|
||||
if (typeof exported !== "string") {
|
||||
showToast("error", `Failed to export note "${note.title}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format === "pdf") {
|
||||
await exportToPDF(note.title, exported);
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = sanitizeFilename(note.title, { replacement: "-" });
|
||||
const ext = FORMAT_TO_EXT[format];
|
||||
controller.enqueue({
|
||||
path: makeUniqueFilename([filename, ext].join("."), counters),
|
||||
data: textEncoder.encode(exported),
|
||||
mtime: new Date(note.data.dateEdited),
|
||||
ctime: new Date(note.data.dateCreated)
|
||||
});
|
||||
|
||||
if (index === noteIds.length) {
|
||||
controller.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
101
apps/web/src/utils/streams/fflate-shim.js
Normal file
101
apps/web/src/utils/streams/fflate-shim.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
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 { Deflate as FflateDeflate, Inflate as FflateInflate } from "fflate";
|
||||
|
||||
function initShimAsyncCodec(library, options = {}, registerDataHandler) {
|
||||
return {
|
||||
Deflate: createCodecClass(
|
||||
library.Deflate,
|
||||
options.deflate,
|
||||
registerDataHandler
|
||||
),
|
||||
Inflate: createCodecClass(
|
||||
library.Inflate,
|
||||
options.inflate,
|
||||
registerDataHandler
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function objectHasOwn(object, propertyName) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
return typeof Object.hasOwn === "function"
|
||||
? Object.hasOwn(object, propertyName)
|
||||
: // eslint-disable-next-line no-prototype-builtins
|
||||
object.hasOwnProperty(propertyName);
|
||||
}
|
||||
|
||||
function createCodecClass(
|
||||
constructor,
|
||||
constructorOptions,
|
||||
registerDataHandler
|
||||
) {
|
||||
return class {
|
||||
constructor(options) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const codecAdapter = this;
|
||||
const onData = (data) => {
|
||||
if (codecAdapter.pendingData) {
|
||||
const previousPendingData = codecAdapter.pendingData;
|
||||
codecAdapter.pendingData = new Uint8Array(
|
||||
previousPendingData.length + data.length
|
||||
);
|
||||
const { pendingData } = codecAdapter;
|
||||
pendingData.set(previousPendingData, 0);
|
||||
pendingData.set(data, previousPendingData.length);
|
||||
} else {
|
||||
codecAdapter.pendingData = new Uint8Array(data);
|
||||
}
|
||||
};
|
||||
if (objectHasOwn(options, "level") && options.level === undefined) {
|
||||
delete options.level;
|
||||
}
|
||||
codecAdapter.codec = new constructor(
|
||||
Object.assign({}, constructorOptions, options)
|
||||
);
|
||||
registerDataHandler(codecAdapter.codec, onData);
|
||||
}
|
||||
append(data) {
|
||||
this.codec.push(data);
|
||||
return getResponse(this);
|
||||
}
|
||||
flush() {
|
||||
this.codec.push(new Uint8Array(), true);
|
||||
return getResponse(this);
|
||||
}
|
||||
};
|
||||
|
||||
function getResponse(codec) {
|
||||
if (codec.pendingData) {
|
||||
const output = codec.pendingData;
|
||||
codec.pendingData = null;
|
||||
return output;
|
||||
} else {
|
||||
return new Uint8Array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { Deflate, Inflate } = initShimAsyncCodec(
|
||||
{ Deflate: FflateDeflate, Inflate: FflateInflate },
|
||||
undefined,
|
||||
(codec, onData) => (codec.ondata = onData)
|
||||
);
|
||||
export { Deflate, Inflate };
|
||||
77
apps/web/src/utils/streams/unzip-stream.ts
Normal file
77
apps/web/src/utils/streams/unzip-stream.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 { Deflate, Inflate } from "./fflate-shim";
|
||||
import {
|
||||
Entry,
|
||||
TextWriter,
|
||||
Uint8ArrayWriter,
|
||||
ZipReader,
|
||||
configure
|
||||
} from "@zip.js/zip.js";
|
||||
|
||||
configure({ Deflate, Inflate });
|
||||
|
||||
export async function* createUnzipIterator(file: File) {
|
||||
const reader = new ZipReader(file.stream());
|
||||
for await (const entry of reader.getEntriesGenerator()) {
|
||||
yield new ZipEntry(entry);
|
||||
}
|
||||
await reader.close();
|
||||
}
|
||||
|
||||
export class ZipEntry extends Blob {
|
||||
private ts = new TransformStream();
|
||||
constructor(public readonly entry: Entry) {
|
||||
super();
|
||||
}
|
||||
|
||||
stream(): ReadableStream<Uint8Array> {
|
||||
this.entry.getData?.(this.ts.writable);
|
||||
return this.ts.readable;
|
||||
}
|
||||
|
||||
async text(): Promise<string> {
|
||||
const writer = new TextWriter("utf-8");
|
||||
await this.entry.getData?.(writer);
|
||||
return await writer.getData();
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.entry.uncompressedSize;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.entry.filename;
|
||||
}
|
||||
|
||||
async arrayBuffer(): Promise<ArrayBuffer> {
|
||||
const writer = new Uint8ArrayWriter();
|
||||
await this.entry.getData?.(writer);
|
||||
return (await writer.getData()).buffer;
|
||||
}
|
||||
|
||||
slice(
|
||||
_start?: number | undefined,
|
||||
_end?: number | undefined,
|
||||
_contentType?: string | undefined
|
||||
): Blob {
|
||||
throw new Error("Slice is not supported.");
|
||||
}
|
||||
}
|
||||
32
apps/web/src/utils/streams/utils.ts
Normal file
32
apps/web/src/utils/streams/utils.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
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 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]
|
||||
}`;
|
||||
}
|
||||
@@ -17,32 +17,51 @@ 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 { Zip, ZipDeflate } from "fflate";
|
||||
import { Deflate, Inflate } from "./fflate-shim";
|
||||
import { Uint8ArrayReader, ZipWriter, configure } from "@zip.js/zip.js";
|
||||
|
||||
export type ZipFile = { path: string; data: Uint8Array };
|
||||
export class ZipStream extends TransformStream<ZipFile, Uint8Array> {
|
||||
constructor() {
|
||||
const zipper = new Zip();
|
||||
super({
|
||||
start(controller) {
|
||||
zipper.ondata = (err, data) => {
|
||||
if (err) {
|
||||
controller.error(err);
|
||||
} else controller.enqueue(data);
|
||||
};
|
||||
},
|
||||
transform(chunk) {
|
||||
const fileStream = new ZipDeflate(chunk.path, {
|
||||
level: 5,
|
||||
mem: 8
|
||||
configure({ Deflate, Inflate });
|
||||
|
||||
export type ZipFile = {
|
||||
path: string;
|
||||
data: Uint8Array;
|
||||
mtime?: Date;
|
||||
ctime?: Date;
|
||||
};
|
||||
|
||||
export function createZipStream(signal?: AbortSignal) {
|
||||
const written = new Set<string>();
|
||||
const ts = new TransformStream<Uint8Array, Uint8Array>();
|
||||
const writer = new ZipWriter<Uint8Array>(ts.writable, {
|
||||
zip64: true,
|
||||
signal
|
||||
});
|
||||
const entryWriter = new WritableStream<ZipFile>({
|
||||
start() {},
|
||||
async write(chunk, c) {
|
||||
// zip.js doesn't support overwriting files.
|
||||
if (written.has(chunk.path)) return;
|
||||
|
||||
await writer
|
||||
.add(chunk.path, new Uint8ArrayReader(chunk.data), {
|
||||
creationDate: chunk.ctime,
|
||||
lastModDate: chunk.mtime
|
||||
})
|
||||
.catch(async (e) => {
|
||||
await ts.writable.abort(e);
|
||||
await ts.readable.cancel(e);
|
||||
c.error(e);
|
||||
});
|
||||
zipper.add(fileStream);
|
||||
fileStream.push(chunk.data, true);
|
||||
},
|
||||
flush() {
|
||||
zipper.end();
|
||||
zipper.terminate();
|
||||
}
|
||||
});
|
||||
}
|
||||
written.add(chunk.path);
|
||||
},
|
||||
async close() {
|
||||
await writer.close();
|
||||
await ts.writable.close();
|
||||
},
|
||||
async abort(reason) {
|
||||
await ts.writable.abort(reason);
|
||||
await ts.readable.cancel(reason);
|
||||
}
|
||||
});
|
||||
return { writable: entryWriter, readable: ts.readable };
|
||||
}
|
||||
|
||||
@@ -1,418 +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/>.
|
||||
*/
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/**
|
||||
* Conflux
|
||||
* Read (and build) zip files with whatwg streams in the browser.
|
||||
*
|
||||
* @author Transcend Inc. <https://transcend.io>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Inflate } from "fflate";
|
||||
|
||||
const ERR_BAD_FORMAT = "File format is not recognized.";
|
||||
const ZIP_COMMENT_MAX = 65536;
|
||||
const EOCDR_MIN = 22;
|
||||
const EOCDR_MAX = EOCDR_MIN + ZIP_COMMENT_MAX;
|
||||
const MAX_VALUE_32BITS = 0xffffffff;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const uint16e = (b: Uint8Array, n: number) => b[n] | (b[n + 1] << 8);
|
||||
|
||||
export class Entry {
|
||||
private dataView: DataView;
|
||||
private _fileLike: File;
|
||||
private _extraFields: Record<string, DataView>;
|
||||
constructor(dataView: DataView, fileLike: File) {
|
||||
if (dataView.getUint32(0) !== 0x504b0102) {
|
||||
throw new Error("ERR_BAD_FORMAT");
|
||||
}
|
||||
|
||||
const dv = dataView;
|
||||
|
||||
this.dataView = dv;
|
||||
this._fileLike = fileLike;
|
||||
this._extraFields = {};
|
||||
|
||||
for (let i = 46 + this.filenameLength; i < dv.byteLength; ) {
|
||||
const id = dv.getUint16(i, true);
|
||||
const len = dv.getUint16(i + 2, true);
|
||||
const start = dv.byteOffset + i + 4;
|
||||
this._extraFields[id] = new DataView(dv.buffer.slice(start, start + len));
|
||||
i += len + 4;
|
||||
}
|
||||
}
|
||||
get webkitRelativePath(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
get versionMadeBy() {
|
||||
return this.dataView.getUint16(4, true);
|
||||
}
|
||||
|
||||
get versionNeeded() {
|
||||
return this.dataView.getUint16(6, true);
|
||||
}
|
||||
|
||||
get bitFlag() {
|
||||
return this.dataView.getUint16(8, true);
|
||||
}
|
||||
|
||||
get encrypted() {
|
||||
return (this.bitFlag & 0x0001) === 0x0001;
|
||||
}
|
||||
|
||||
get compressionMethod() {
|
||||
return this.dataView.getUint16(10, true);
|
||||
}
|
||||
|
||||
get crc32() {
|
||||
return this.dataView.getUint32(16, true);
|
||||
}
|
||||
|
||||
get compressedSize() {
|
||||
return this.dataView.getUint32(20, true);
|
||||
}
|
||||
|
||||
get filenameLength() {
|
||||
return this.dataView.getUint16(28, true);
|
||||
}
|
||||
|
||||
get extraFieldLength() {
|
||||
return this.dataView.getUint16(30, true);
|
||||
}
|
||||
|
||||
get commentLength() {
|
||||
return this.dataView.getUint16(32, true);
|
||||
}
|
||||
|
||||
get diskNumberStart() {
|
||||
return this.dataView.getUint16(34, true);
|
||||
}
|
||||
|
||||
get internalFileAttributes() {
|
||||
return this.dataView.getUint16(36, true);
|
||||
}
|
||||
|
||||
get externalFileAttributes() {
|
||||
return this.dataView.getUint32(38, true);
|
||||
}
|
||||
|
||||
get directory() {
|
||||
return !!(this.dataView.getUint8(38) & 16);
|
||||
}
|
||||
|
||||
get offset() {
|
||||
return this.dataView.getUint32(42, true);
|
||||
}
|
||||
|
||||
get zip64() {
|
||||
return this.dataView.getUint32(24, true) === MAX_VALUE_32BITS;
|
||||
}
|
||||
|
||||
get comment() {
|
||||
const dv = this.dataView;
|
||||
const uint8 = new Uint8Array(
|
||||
dv.buffer,
|
||||
dv.byteOffset + this.filenameLength + this.extraFieldLength + 46,
|
||||
this.commentLength
|
||||
);
|
||||
return decoder.decode(uint8);
|
||||
}
|
||||
|
||||
// File like IDL methods
|
||||
get lastModifiedDate() {
|
||||
const t = this.dataView.getUint32(12, true);
|
||||
|
||||
return new Date(
|
||||
// Date.UTC(
|
||||
((t >> 25) & 0x7f) + 1980, // year
|
||||
((t >> 21) & 0x0f) - 1, // month
|
||||
(t >> 16) & 0x1f, // day
|
||||
(t >> 11) & 0x1f, // hour
|
||||
(t >> 5) & 0x3f, // minute
|
||||
(t & 0x1f) << 1
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
get lastModified() {
|
||||
return +this.lastModifiedDate;
|
||||
}
|
||||
|
||||
get name() {
|
||||
if (!this.bitFlag && this._extraFields && this._extraFields[0x7075]) {
|
||||
return decoder.decode(this._extraFields[0x7075].buffer.slice(5));
|
||||
}
|
||||
|
||||
const dv = this.dataView;
|
||||
const uint8 = new Uint8Array(
|
||||
dv.buffer,
|
||||
dv.byteOffset + 46,
|
||||
this.filenameLength
|
||||
);
|
||||
return decoder.decode(uint8);
|
||||
}
|
||||
|
||||
get size() {
|
||||
const size = this.dataView.getUint32(24, true);
|
||||
return size === MAX_VALUE_32BITS ? this._extraFields[1].getUint8(0) : size;
|
||||
}
|
||||
|
||||
stream() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
|
||||
const crc = new Crc32();
|
||||
let inflator: Inflate | undefined;
|
||||
const onEnd = (ctrl: ReadableStreamController<Uint8Array>) =>
|
||||
crc.get() === self.crc32
|
||||
? ctrl.close()
|
||||
: ctrl.error(new Error("The crc32 checksum don't match"));
|
||||
|
||||
return new ReadableStream<Uint8Array>({
|
||||
async start(ctrl) {
|
||||
// Need to read local header to get fileName + extraField length
|
||||
// Since they are not always the same length as in central dir...
|
||||
const ab = await self._fileLike
|
||||
.slice(self.offset + 26, self.offset + 30)
|
||||
.arrayBuffer();
|
||||
|
||||
const bytes = new Uint8Array(ab);
|
||||
const localFileOffset = uint16e(bytes, 0) + uint16e(bytes, 2) + 30;
|
||||
const start = self.offset + localFileOffset;
|
||||
const end = start + self.compressedSize;
|
||||
(this as any).reader = self._fileLike
|
||||
.slice(start, end)
|
||||
.stream()
|
||||
.getReader();
|
||||
|
||||
if (self.compressionMethod) {
|
||||
inflator = new Inflate();
|
||||
inflator.ondata = (chunk, final) => {
|
||||
crc.append(chunk);
|
||||
ctrl.enqueue(chunk);
|
||||
if (final) onEnd(ctrl);
|
||||
};
|
||||
}
|
||||
},
|
||||
async pull(ctrl) {
|
||||
const v = await (this as any).reader.read();
|
||||
if (inflator && !v.done) {
|
||||
inflator.push(v.value);
|
||||
} else if (v.done) {
|
||||
onEnd(ctrl);
|
||||
} else {
|
||||
ctrl.enqueue(v.value);
|
||||
crc.append(v.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
arrayBuffer() {
|
||||
return new Response(this.stream()).arrayBuffer().catch((e) => {
|
||||
throw new Error(`Failed to read Entry\n${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
text() {
|
||||
return new Response(this.stream()).text().catch((e) => {
|
||||
throw new Error(`Failed to read Entry\n${e}`);
|
||||
});
|
||||
}
|
||||
|
||||
file() {
|
||||
return new Response(this.stream())
|
||||
.blob()
|
||||
.then(
|
||||
(blob) =>
|
||||
new File([blob], this.name, { lastModified: this.lastModified })
|
||||
)
|
||||
.catch((e) => {
|
||||
throw new Error(`Failed to read Entry\n${e}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a BigInt 64 from a DataView
|
||||
*
|
||||
* @param {DataView} view a dataview
|
||||
* @param {number} position the position
|
||||
* @param {boolean} littleEndian whether this uses littleEndian encoding
|
||||
* @returns BigInt
|
||||
*/
|
||||
function getBigInt64(view: DataView, position: number, littleEndian = false) {
|
||||
if ("getBigInt64" in DataView.prototype) {
|
||||
return view.getBigInt64(position, littleEndian);
|
||||
}
|
||||
|
||||
let value = BigInt(0);
|
||||
const isNegative =
|
||||
(view.getUint8(position + (littleEndian ? 7 : 0)) & 0x80) > 0;
|
||||
let carrying = true;
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
let byte = view.getUint8(position + (littleEndian ? i : 7 - i));
|
||||
|
||||
if (isNegative) {
|
||||
if (carrying) {
|
||||
if (byte !== 0x00) {
|
||||
byte = ~(byte - 1) & 0xff;
|
||||
carrying = false;
|
||||
}
|
||||
} else {
|
||||
byte = ~byte & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
value = value + BigInt(byte) + BigInt(256) ** BigInt(i);
|
||||
}
|
||||
|
||||
if (isNegative) {
|
||||
value = -value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function Reader(file: File) {
|
||||
// Seek EOCDR - "End of central directory record" is the last part of a zip archive, and is at least 22 bytes long.
|
||||
// Zip file comment is the last part of EOCDR and has max length of 64KB,
|
||||
// so we only have to search the last 64K + 22 bytes of a archive for EOCDR signature (0x06054b50).
|
||||
if (file.size < EOCDR_MIN) throw new Error(ERR_BAD_FORMAT);
|
||||
|
||||
// seek last length bytes of file for EOCDR
|
||||
async function doSeek(length: number) {
|
||||
const ab = await file.slice(file.size - length).arrayBuffer();
|
||||
const bytes = new Uint8Array(ab);
|
||||
for (let i = bytes.length - EOCDR_MIN; i >= 0; i--) {
|
||||
if (
|
||||
bytes[i] === 0x50 &&
|
||||
bytes[i + 1] === 0x4b &&
|
||||
bytes[i + 2] === 0x05 &&
|
||||
bytes[i + 3] === 0x06
|
||||
) {
|
||||
return new DataView(bytes.buffer, i, EOCDR_MIN);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// In most cases, the EOCDR is EOCDR_MIN bytes long
|
||||
let dv =
|
||||
(await doSeek(EOCDR_MIN)) || (await doSeek(Math.min(EOCDR_MAX, file.size)));
|
||||
|
||||
if (!dv) throw new Error(ERR_BAD_FORMAT);
|
||||
|
||||
let fileslength = dv.getUint16(8, true);
|
||||
let centralDirSize = dv.getUint32(12, true);
|
||||
let centralDirOffset = dv.getUint32(16, true);
|
||||
// const fileCommentLength = dv.getUint16(20, true);
|
||||
|
||||
const isZip64 = centralDirOffset === MAX_VALUE_32BITS;
|
||||
|
||||
if (isZip64) {
|
||||
const l = -dv.byteLength - 20;
|
||||
dv = new DataView(await file.slice(l, -dv.byteLength).arrayBuffer());
|
||||
|
||||
// const signature = dv.getUint32(0, true) // 4 bytes
|
||||
// const diskWithZip64CentralDirStart = dv.getUint32(4, true) // 4 bytes
|
||||
const relativeOffsetEndOfZip64CentralDir = Number(getBigInt64(dv, 8, true)); // 8 bytes
|
||||
// const numberOfDisks = dv.getUint32(16, true) // 4 bytes
|
||||
|
||||
const zip64centralBlob = file.slice(relativeOffsetEndOfZip64CentralDir, l);
|
||||
dv = new DataView(await zip64centralBlob.arrayBuffer());
|
||||
// const zip64EndOfCentralSize = dv.getBigInt64(4, true)
|
||||
// const diskNumber = dv.getUint32(16, true)
|
||||
// const diskWithCentralDirStart = dv.getUint32(20, true)
|
||||
// const centralDirRecordsOnThisDisk = dv.getBigInt64(24, true)
|
||||
fileslength = Number(getBigInt64(dv, 32, true));
|
||||
centralDirSize = Number(getBigInt64(dv, 40, true));
|
||||
centralDirOffset = Number(getBigInt64(dv, 48, true));
|
||||
}
|
||||
|
||||
if (centralDirOffset < 0 || centralDirOffset >= file.size) {
|
||||
throw new Error(ERR_BAD_FORMAT);
|
||||
}
|
||||
|
||||
const start = centralDirOffset;
|
||||
const end = centralDirOffset + centralDirSize;
|
||||
const blob = file.slice(start, end);
|
||||
const bytes = new Uint8Array(await blob.arrayBuffer());
|
||||
|
||||
async function* read() {
|
||||
for (let i = 0, index = 0; i < fileslength; i++) {
|
||||
const size =
|
||||
uint16e(bytes, index + 28) + // filenameLength
|
||||
uint16e(bytes, index + 30) + // extraFieldLength
|
||||
uint16e(bytes, index + 32) + // commentLength
|
||||
46;
|
||||
|
||||
if (index + size > bytes.length) {
|
||||
throw new Error("Invalid ZIP file.");
|
||||
}
|
||||
|
||||
yield new Entry(new DataView(bytes.buffer, index, size), file);
|
||||
|
||||
index += size;
|
||||
}
|
||||
}
|
||||
|
||||
return { read, totalFiles: fileslength };
|
||||
}
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
class Crc32 {
|
||||
crc: number;
|
||||
constructor() {
|
||||
this.crc = -1;
|
||||
}
|
||||
|
||||
append(data: Uint8Array) {
|
||||
let crc = this.crc | 0;
|
||||
const { table } = this;
|
||||
for (let offset = 0, len = data.length | 0; offset < len; offset++) {
|
||||
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xff];
|
||||
}
|
||||
this.crc = crc;
|
||||
}
|
||||
|
||||
get() {
|
||||
return (this.crc ^ -1) >>> 0;
|
||||
}
|
||||
|
||||
private table = ((table: number[], i: number, j: number, t: number) => {
|
||||
for (i = 0; i < 256; i++) {
|
||||
t = i;
|
||||
for (j = 0; j < 8; j++) {
|
||||
t = t & 1 ? (t >>> 1) ^ 0xedb88320 : t >>> 1;
|
||||
}
|
||||
table[i] = t;
|
||||
}
|
||||
return table;
|
||||
})([], 0, 0, 0);
|
||||
}
|
||||
@@ -1,48 +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 { AsyncZippable, zip as zipAsync } from "fflate";
|
||||
import { sanitizeFilename } from "@notesnook/common";
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
type File = { filename: string; content: string; date: number };
|
||||
async function zip(files: File[], format: string): Promise<Uint8Array> {
|
||||
const obj: AsyncZippable = 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 }
|
||||
];
|
||||
});
|
||||
return new Promise((resolve, reject) =>
|
||||
zipAsync(obj, (err, data) => (err ? reject(err) : resolve(data)))
|
||||
);
|
||||
}
|
||||
export { zip };
|
||||
|
||||
function makeFilename(filename: string, extension: string, counter: number) {
|
||||
let final = filename;
|
||||
if (counter) final += `-${counter}`;
|
||||
final += `.${extension}`;
|
||||
return final;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ In Notesnook, one note can belong to multiple notebooks & topics. This allows fo
|
||||
|
||||
1. Go to `Notebooks`
|
||||
2. Press the  button.
|
||||
3. Press `Move to trash` to delete the topic.
|
||||
3. Press `Move to trash` to delete the notebook.
|
||||
4. Select whether you also want to delete the notes inside this notebook.
|
||||
|
||||
---
|
||||
|
||||
6
fastlane/metadata/android/en-US/changelogs/10369.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/10369.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
- Added support to check/uncheck all items in a checklist
|
||||
- Now you can open pdfs/images in readonly mode
|
||||
- Fixed a bug where users are unable to login when app lock is enabled
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
3
fastlane/metadata/android/en-US/changelogs/10374.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/10374.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
4
packages/common/package-lock.json
generated
4
packages/common/package-lock.json
generated
@@ -31,7 +31,7 @@
|
||||
"@microsoft/signalr": "^7.0.10",
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"async-mutex": "^0.3.2",
|
||||
"dayjs": "1.11.9",
|
||||
"entities": "^4.3.1",
|
||||
@@ -147,7 +147,7 @@
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"@types/html-to-text": "^9.0.0",
|
||||
"@types/katex": "^0.16.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
|
||||
14
packages/core/package-lock.json
generated
14
packages/core/package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"@microsoft/signalr": "^7.0.10",
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"async-mutex": "^0.3.2",
|
||||
"dayjs": "1.11.9",
|
||||
"entities": "^4.3.1",
|
||||
@@ -589,9 +589,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@streetwriters/showdown": {
|
||||
"version": "3.0.1-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.1-alpha.2.tgz",
|
||||
"integrity": "sha512-GkIDQlcvJVVMgp1J5H5upiRr2hZQJAJK6gnDzOjvJAflKO4gV96xCq4RFakwahLLhL7uQ8Yx10qtmrtBuJMsEw==",
|
||||
"version": "3.0.2-alpha",
|
||||
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.2-alpha.tgz",
|
||||
"integrity": "sha512-j6IuhMgCBKnOPruierM9Lbrd+Zgj9bc80K6jYXaKcqvxd9hFnNNvbzm24IMxfuEGxv+Tqv+ukr9gxOBZU+5WuA==",
|
||||
"bin": {
|
||||
"showdown": "bin/showdown.js"
|
||||
},
|
||||
@@ -3263,9 +3263,9 @@
|
||||
}
|
||||
},
|
||||
"@streetwriters/showdown": {
|
||||
"version": "3.0.1-alpha.2",
|
||||
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.1-alpha.2.tgz",
|
||||
"integrity": "sha512-GkIDQlcvJVVMgp1J5H5upiRr2hZQJAJK6gnDzOjvJAflKO4gV96xCq4RFakwahLLhL7uQ8Yx10qtmrtBuJMsEw=="
|
||||
"version": "3.0.2-alpha",
|
||||
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.2-alpha.tgz",
|
||||
"integrity": "sha512-j6IuhMgCBKnOPruierM9Lbrd+Zgj9bc80K6jYXaKcqvxd9hFnNNvbzm24IMxfuEGxv+Tqv+ukr9gxOBZU+5WuA=="
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"@microsoft/signalr": "^7.0.10",
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"async-mutex": "^0.3.2",
|
||||
"dayjs": "1.11.9",
|
||||
"entities": "^4.3.1",
|
||||
|
||||
@@ -160,7 +160,6 @@ class Database {
|
||||
this.notes = await Notes.new(this, "notes");
|
||||
|
||||
await this.trash.init();
|
||||
await this.relations.cleanup();
|
||||
|
||||
this.monographs.init().catch(console.error);
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ export default class Vault {
|
||||
return await this._lockNote(note, this._password);
|
||||
}
|
||||
|
||||
async exists(vaultKey) {
|
||||
async exists(vaultKey = undefined) {
|
||||
if (!vaultKey) vaultKey = await this._getKey();
|
||||
return vaultKey && vaultKey.cipher && vaultKey.iv;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { logger } from "../logger";
|
||||
import { makeId } from "../utils/id";
|
||||
import Collection from "./collection";
|
||||
|
||||
@@ -189,39 +188,6 @@ export default class Relations extends Collection {
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
const relationsLogger = logger.scope("Relations");
|
||||
const relations = this._collection.getItems();
|
||||
for (const relation of relations) {
|
||||
const references = [relation.to, relation.from];
|
||||
for (let reference of references) {
|
||||
let exists = false;
|
||||
switch (reference.type) {
|
||||
case "reminder":
|
||||
exists = this._db.reminders.exists(reference.id);
|
||||
break;
|
||||
case "note":
|
||||
exists =
|
||||
this._db.notes.exists(reference.id) ||
|
||||
this._db.trash.exists(reference.id);
|
||||
break;
|
||||
case "notebook":
|
||||
exists =
|
||||
this._db.notebooks.exists(reference.id) ||
|
||||
this._db.trash.exists(reference.id);
|
||||
break;
|
||||
}
|
||||
if (!exists) {
|
||||
relationsLogger.debug(
|
||||
`Removing relation during cleanup`,
|
||||
relation.id
|
||||
);
|
||||
await this.remove(relation.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -276,6 +276,10 @@ export default class Backup {
|
||||
if (attachment) {
|
||||
item = {
|
||||
...attachment,
|
||||
metadata: {
|
||||
...attachment.metadata,
|
||||
type: item.metadata.type || attachment.metadata.type
|
||||
},
|
||||
noteIds: setManipulator.union(attachment.noteIds, item.noteIds)
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -163,7 +163,9 @@ function addToGroup(groups, groupTitle, item) {
|
||||
const REGEX = /\S/;
|
||||
function getFirstCharacter(str) {
|
||||
if (!str) return "-";
|
||||
return REGEX.exec(str)[0].toUpperCase();
|
||||
const matches = REGEX.exec(str);
|
||||
if (!matches || !matches.length) return "-";
|
||||
return matches[0].toUpperCase();
|
||||
}
|
||||
|
||||
function flattenGroups(groups) {
|
||||
|
||||
2
packages/editor-mobile/package-lock.json
generated
2
packages/editor-mobile/package-lock.json
generated
@@ -73,6 +73,7 @@
|
||||
"@tiptap/extension-underline": "2.1.12",
|
||||
"@tiptap/pm": "2.1.12",
|
||||
"@tiptap/starter-kit": "2.1.12",
|
||||
"async-mutex": "^0.4.0",
|
||||
"clipboard-polyfill": "4.0.0",
|
||||
"detect-indent": "^7.0.0",
|
||||
"entities": "^4.5.0",
|
||||
@@ -21729,6 +21730,7 @@
|
||||
"@types/react-dom": "17.0.2",
|
||||
"@types/react-modal": "3.13.1",
|
||||
"@types/tinycolor2": "^1.4.3",
|
||||
"async-mutex": "^0.4.0",
|
||||
"clipboard-polyfill": "4.0.0",
|
||||
"detect-indent": "^7.0.0",
|
||||
"entities": "^4.5.0",
|
||||
|
||||
@@ -246,7 +246,7 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
|
||||
}}
|
||||
editor={_editor}
|
||||
location="bottom"
|
||||
tools={[...settings.tools]}
|
||||
tools={settings.tools}
|
||||
defaultFontFamily={settings.fontFamily}
|
||||
defaultFontSize={settings.fontSize}
|
||||
/>
|
||||
|
||||
18
packages/editor/package-lock.json
generated
18
packages/editor/package-lock.json
generated
@@ -23,7 +23,7 @@
|
||||
"@tiptap/extension-font-family": "2.1.12",
|
||||
"@tiptap/extension-history": "2.1.12",
|
||||
"@tiptap/extension-horizontal-rule": "2.1.12",
|
||||
"@tiptap/extension-link": "2.1.12",
|
||||
"@tiptap/extension-link": "^2.1.16",
|
||||
"@tiptap/extension-list-keymap": "2.1.12",
|
||||
"@tiptap/extension-placeholder": "2.1.12",
|
||||
"@tiptap/extension-subscript": "2.1.12",
|
||||
@@ -95,7 +95,7 @@
|
||||
"@microsoft/signalr": "^7.0.10",
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"async-mutex": "^0.3.2",
|
||||
"dayjs": "1.11.9",
|
||||
"entities": "^4.3.1",
|
||||
@@ -1234,9 +1234,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-link": {
|
||||
"version": "2.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.12.tgz",
|
||||
"integrity": "sha512-Sti5hhlkCqi5vzdQjU/gbmr8kb578p+u0J4kWS+SSz3BknNThEm/7Id67qdjBTOQbwuN07lHjDaabJL0hSkzGQ==",
|
||||
"version": "2.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.16.tgz",
|
||||
"integrity": "sha512-QIXYwxHi2kKU2sqDXngTpggO4ZmLm4vMxDlbWT9so1iUPAqQJW2ZRbdygFYy1txirWcoaJKocPwSJemyAeUzmw==",
|
||||
"dependencies": {
|
||||
"linkifyjs": "^4.1.0"
|
||||
},
|
||||
@@ -4344,7 +4344,7 @@
|
||||
"@microsoft/signalr-protocol-msgpack": "^7.0.10",
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
"@notesnook/logger": "file:../logger",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@streetwriters/showdown": "^3.0.2-alpha",
|
||||
"@types/html-to-text": "^9.0.0",
|
||||
"@types/katex": "^0.16.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
@@ -4665,9 +4665,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@tiptap/extension-link": {
|
||||
"version": "2.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.12.tgz",
|
||||
"integrity": "sha512-Sti5hhlkCqi5vzdQjU/gbmr8kb578p+u0J4kWS+SSz3BknNThEm/7Id67qdjBTOQbwuN07lHjDaabJL0hSkzGQ==",
|
||||
"version": "2.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.16.tgz",
|
||||
"integrity": "sha512-QIXYwxHi2kKU2sqDXngTpggO4ZmLm4vMxDlbWT9so1iUPAqQJW2ZRbdygFYy1txirWcoaJKocPwSJemyAeUzmw==",
|
||||
"requires": {
|
||||
"linkifyjs": "^4.1.0"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@tiptap/extension-font-family": "2.1.12",
|
||||
"@tiptap/extension-history": "2.1.12",
|
||||
"@tiptap/extension-horizontal-rule": "2.1.12",
|
||||
"@tiptap/extension-link": "2.1.12",
|
||||
"@tiptap/extension-link": "^2.1.16",
|
||||
"@tiptap/extension-list-keymap": "2.1.12",
|
||||
"@tiptap/extension-placeholder": "2.1.12",
|
||||
"@tiptap/extension-subscript": "2.1.12",
|
||||
|
||||
28
packages/editor/patches/@tiptap+core+2.1.12.patch
Normal file
28
packages/editor/patches/@tiptap+core+2.1.12.patch
Normal file
@@ -0,0 +1,28 @@
|
||||
diff --git a/node_modules/@tiptap/core/dist/index.js b/node_modules/@tiptap/core/dist/index.js
|
||||
index 38c9884..2dad7fe 100644
|
||||
--- a/node_modules/@tiptap/core/dist/index.js
|
||||
+++ b/node_modules/@tiptap/core/dist/index.js
|
||||
@@ -1876,8 +1876,21 @@ const lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
|
||||
return lift$1(state, dispatch);
|
||||
};
|
||||
|
||||
-const liftEmptyBlock = () => ({ state, dispatch }) => {
|
||||
- return liftEmptyBlock$1(state, dispatch);
|
||||
+const liftEmptyBlock = () => ({ state, dispatch, editor }) => {
|
||||
+ return liftEmptyBlock$1(state, (tr) => {
|
||||
+ if (!dispatch) return true;
|
||||
+
|
||||
+ const { selection, storedMarks } = state;
|
||||
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks());
|
||||
+
|
||||
+ const { splittableMarks } = editor.extensionManager;
|
||||
+ const filteredMarks = marks.filter((mark) =>
|
||||
+ splittableMarks.includes(mark.type.name)
|
||||
+ );
|
||||
+ tr.ensureMarks(filteredMarks)
|
||||
+
|
||||
+ return dispatch(tr);
|
||||
+ });
|
||||
};
|
||||
|
||||
const liftListItem = typeOrName => ({ state, dispatch }) => {
|
||||
@@ -37,7 +37,7 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
|
||||
}
|
||||
|
||||
parseSlice(dom: Node, options?: ParseOptions | undefined): Slice {
|
||||
if (dom instanceof HTMLElement) {
|
||||
if (dom instanceof HTMLElement || dom instanceof Document) {
|
||||
formatCodeblocks(dom);
|
||||
convertBrToSingleSpacedParagraphs(dom);
|
||||
}
|
||||
@@ -47,6 +47,7 @@ export class ClipboardDOMParser extends ProsemirrorDOMParser {
|
||||
|
||||
export function formatCodeblocks(dom: HTMLElement | Document) {
|
||||
for (const pre of dom.querySelectorAll("pre")) {
|
||||
pre.innerHTML = pre.innerHTML?.replaceAll(/<br.*?>/g, "\n");
|
||||
const codeAsText = pre.textContent;
|
||||
const languageElement = pre.querySelector(
|
||||
'[class*="language-"],[class*="lang-"]'
|
||||
@@ -58,6 +59,16 @@ export function formatCodeblocks(dom: HTMLElement | Document) {
|
||||
code.innerHTML = encodeNonAsciiHTML(codeAsText || "");
|
||||
pre.replaceChildren(code);
|
||||
}
|
||||
|
||||
for (const div of dom.querySelectorAll(".w3-code")) {
|
||||
div.innerHTML = div.innerHTML?.replaceAll(/<br.*?>/g, "\n");
|
||||
const codeAsText = div.textContent;
|
||||
const pre = document.createElement("pre");
|
||||
const code = document.createElement("code");
|
||||
code.innerHTML = encodeNonAsciiHTML(codeAsText || "");
|
||||
pre.replaceChildren(code);
|
||||
div.replaceChildren(pre);
|
||||
}
|
||||
}
|
||||
|
||||
export function convertBrToSingleSpacedParagraphs(dom: HTMLElement | Document) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { ClipboardDOMParser } from "./clipboard-dom-parser";
|
||||
import { ClipboardDOMSerializer } from "./clipboard-dom-serializer";
|
||||
import { clipboardTextParser } from "./clipboard-text-parser";
|
||||
import { clipboardTextSerializer } from "./clipboard-text-serializer";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
@@ -76,7 +77,7 @@ export const Clipboard = Extension.create({
|
||||
}
|
||||
});
|
||||
|
||||
export function transformCopied(slice: Slice): any {
|
||||
export function transformCopied(slice: Slice, view: EditorView): any {
|
||||
// when copying a single list item, we shouldn't retain the
|
||||
// list formatting but copy it as a paragraph.
|
||||
const maybeList = slice.content.firstChild;
|
||||
@@ -86,7 +87,11 @@ export function transformCopied(slice: Slice): any {
|
||||
maybeList.childCount === 1 &&
|
||||
maybeList.firstChild
|
||||
) {
|
||||
return transformCopied(new Slice(maybeList.firstChild.content, 0, 0));
|
||||
return transformCopied(
|
||||
new Slice(maybeList.firstChild.content, slice.openStart, slice.openEnd),
|
||||
view
|
||||
);
|
||||
}
|
||||
|
||||
return slice;
|
||||
}
|
||||
|
||||
@@ -29,5 +29,6 @@ exports[`copying a single nested list item shouldn't copy the list metadata 1`]
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"openStart": 5,
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -81,7 +81,6 @@ export function CodeblockComponent(
|
||||
}
|
||||
},
|
||||
fontFamily: "monospace",
|
||||
fontSize: "code",
|
||||
whiteSpace: "pre", // TODO !important
|
||||
tabSize: 1,
|
||||
position: "relative",
|
||||
|
||||
@@ -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 { Extension } from "@tiptap/core";
|
||||
import "@tiptap/extension-text-style";
|
||||
import { useToolbarStore } from "../../toolbar/stores/toolbar-store";
|
||||
import { Editor, Extension } from "@tiptap/core";
|
||||
|
||||
type FontSizeOptions = {
|
||||
types: string[];
|
||||
@@ -83,5 +83,33 @@ export const FontSize = Extension.create<FontSizeOptions>({
|
||||
.run();
|
||||
}
|
||||
};
|
||||
},
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
"ctrl-]": ({ editor }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setFontSize(`${Math.min(120, getFontSize(editor) + 1)}px`)
|
||||
.run();
|
||||
return true;
|
||||
},
|
||||
"Ctrl-[": ({ editor }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setFontSize(`${Math.max(8, getFontSize(editor) - 1)}px`)
|
||||
.run();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function getFontSize(editor: Editor) {
|
||||
const defaultFontSize = useToolbarStore.getState().fontSize;
|
||||
const { fontSize } = editor.getAttributes("textStyle");
|
||||
return fontSize
|
||||
? parseInt(fontSize.replace("px", "")) || 16
|
||||
: defaultFontSize;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ export const KeyMap = Extension.create({
|
||||
"Shift-Tab": ({ editor }) => {
|
||||
if (isListActive(editor)) return false;
|
||||
return true;
|
||||
},
|
||||
"Mod-\\": ({ editor }) => {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.clearNodes()
|
||||
.unsetAllMarks()
|
||||
.unsetMark("link")
|
||||
.run();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,21 +16,38 @@ 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 { markInputRule } from "@tiptap/core";
|
||||
import { markInputRule, markPasteRule } from "@tiptap/core";
|
||||
import TiptapLink from "@tiptap/extension-link";
|
||||
|
||||
const linkRegex = /(?:__|[*#])|\[(.*?)\]\(.*?\)/gm;
|
||||
const regExp = /\((.*?)\)/;
|
||||
const regExp = /(?:__|[*#])|\[.*?\]\((.*?)\)/gm;
|
||||
|
||||
export const Link = TiptapLink.extend({
|
||||
addInputRules() {
|
||||
return [
|
||||
...(this.parent?.() || []),
|
||||
markInputRule({
|
||||
find: linkRegex,
|
||||
type: this.type,
|
||||
getAttributes: (match) => ({
|
||||
href: regExp.exec(match[0])?.[1]
|
||||
})
|
||||
getAttributes: (match) => {
|
||||
return {
|
||||
href: regExp.exec(match[0])?.[1]
|
||||
};
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
addPasteRules() {
|
||||
return [
|
||||
...(this.parent?.() || []),
|
||||
markPasteRule({
|
||||
find: linkRegex,
|
||||
type: this.type,
|
||||
getAttributes(match) {
|
||||
return {
|
||||
href: regExp.exec(match[0])?.[1]
|
||||
};
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
@@ -21,17 +21,22 @@ import { Box, Flex, Input, Text } from "@theme-ui/components";
|
||||
import { useMemo } from "react";
|
||||
import { ToolButton } from "../../toolbar/components/tool-button";
|
||||
import { ReactNodeViewProps } from "../react";
|
||||
import { type TaskListAttributes } from "./task-list";
|
||||
import { toggleChildren, type TaskListAttributes } from "./task-list";
|
||||
import { replaceDateTime } from "../date-time";
|
||||
import { deleteCheckedItems, sortList } from "./utils";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import TaskItem from "@tiptap/extension-task-item";
|
||||
import { useIsMobile } from "../../toolbar/stores/toolbar-store";
|
||||
import { Icons } from "../../toolbar/icons";
|
||||
import { Icon } from "@notesnook/ui";
|
||||
|
||||
export function TaskListComponent(
|
||||
props: ReactNodeViewProps<TaskListAttributes>
|
||||
) {
|
||||
const { editor, getPos, node, updateAttributes, forwardRef, pos } = props;
|
||||
const { title, textDirection, readonly, stats } = node.attrs;
|
||||
const isMobile = useIsMobile();
|
||||
const checked = stats.total > 0 && stats.total === stats.checked;
|
||||
|
||||
const isNested = useMemo(() => {
|
||||
if (!pos) return false;
|
||||
@@ -68,6 +73,41 @@ export function TaskListComponent(
|
||||
transition: "width 250ms ease-out"
|
||||
}}
|
||||
/>
|
||||
{editor.isEditable ? (
|
||||
<Icon
|
||||
path={checked ? Icons.check : ""}
|
||||
stroke="1px"
|
||||
contentEditable={false}
|
||||
tabIndex={1}
|
||||
sx={{
|
||||
border: "2px solid",
|
||||
borderColor: checked ? "accent" : "icon",
|
||||
borderRadius: "default",
|
||||
p: "1px",
|
||||
zIndex: 1,
|
||||
// mt: isMobile ? "0.20ch" : "0.36ch",
|
||||
marginInlineStart: 1,
|
||||
cursor: editor.isEditable ? "pointer" : "unset",
|
||||
":hover": isMobile
|
||||
? undefined
|
||||
: {
|
||||
borderColor: "accent"
|
||||
},
|
||||
fontFamily: "inherit"
|
||||
}}
|
||||
onClick={() => {
|
||||
const parentPos = getPos();
|
||||
editor.current?.commands.command(({ tr }) => {
|
||||
const node = tr.doc.nodeAt(parentPos);
|
||||
if (!node) return false;
|
||||
toggleChildren(tr, node, !checked, parentPos);
|
||||
return true;
|
||||
});
|
||||
}}
|
||||
color={checked ? "accent" : "icon"}
|
||||
size={isMobile ? "1.70ch" : "1.46ch"}
|
||||
/>
|
||||
) : null}
|
||||
<Input
|
||||
readOnly={!editor.isEditable || readonly}
|
||||
value={title || ""}
|
||||
@@ -75,7 +115,7 @@ export function TaskListComponent(
|
||||
sx={{
|
||||
flex: 1,
|
||||
p: 0,
|
||||
px: 2,
|
||||
px: 1,
|
||||
zIndex: 1,
|
||||
color: "var(--paragraph-secondary)",
|
||||
fontSize: "inherit",
|
||||
@@ -172,7 +212,7 @@ export function TaskListComponent(
|
||||
variant={"body"}
|
||||
sx={{
|
||||
ml: 1,
|
||||
mr: 2,
|
||||
mr: 1,
|
||||
color: "var(--paragraph-secondary)",
|
||||
flexShrink: 0,
|
||||
zIndex: 1,
|
||||
|
||||
@@ -268,12 +268,14 @@ export const TaskListNode = TaskList.extend({
|
||||
// Case # 1
|
||||
// if the user clicks on a task item that has children, we
|
||||
// should automatically check/uncheck all its children
|
||||
// const oldTaskList = oldState.doc.nodeAt(edit.pos);
|
||||
// const newTaskList = newState.doc.nodeAt(edit.pos);
|
||||
if (
|
||||
// when a task item has children, the task list is always the
|
||||
// last child
|
||||
edit.node.lastChild?.type.name === TaskList.name &&
|
||||
oldState.doc.nodeAt(edit.pos)?.attrs.checked !==
|
||||
newState.doc.nodeAt(edit.pos)?.attrs.checked
|
||||
!!oldState.doc.nodeAt(edit.pos)?.attrs.checked !==
|
||||
!!newState.doc.nodeAt(edit.pos)?.attrs.checked
|
||||
) {
|
||||
changeCount += toggleChildren(
|
||||
tr,
|
||||
@@ -381,7 +383,7 @@ function areAllChecked(node: ProsemirrorNode) {
|
||||
return allChecked;
|
||||
}
|
||||
|
||||
function toggleChildren(
|
||||
export function toggleChildren(
|
||||
tr: Transaction,
|
||||
node: ProsemirrorNode,
|
||||
toggleState: boolean,
|
||||
|
||||
@@ -28,9 +28,10 @@ export type CounterProps = {
|
||||
onDecrease: () => void;
|
||||
onReset: () => void;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
function _Counter(props: CounterProps) {
|
||||
const { title, onDecrease, onIncrease, onReset, value } = props;
|
||||
const { title, onDecrease, onIncrease, onReset, value, disabled } = props;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
@@ -39,24 +40,29 @@ function _Counter(props: CounterProps) {
|
||||
alignItems: "stretch",
|
||||
borderRadius: "default",
|
||||
overflow: "hidden",
|
||||
cursor: "pointer",
|
||||
cursor: disabled ? "not-allowed" : "pointer",
|
||||
height: "100%",
|
||||
":hover": {
|
||||
bg: isMobile ? "transparent" : "hover-secondary"
|
||||
bg: isMobile || disabled ? "transparent" : "hover-secondary"
|
||||
}
|
||||
}}
|
||||
onClick={onReset}
|
||||
title={`Click to reset ${title}`}
|
||||
onClick={disabled ? undefined : onReset}
|
||||
title={disabled ? "" : `Click to reset ${title}`}
|
||||
>
|
||||
<ToolButton
|
||||
toggled={false}
|
||||
title={`Decrease ${title}`}
|
||||
icon="minus"
|
||||
variant={"small"}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDecrease();
|
||||
}}
|
||||
disabled={disabled}
|
||||
onClick={
|
||||
disabled
|
||||
? undefined
|
||||
: (e) => {
|
||||
e.stopPropagation();
|
||||
onDecrease();
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<Text
|
||||
@@ -65,7 +71,8 @@ function _Counter(props: CounterProps) {
|
||||
fontSize: "subBody",
|
||||
alignSelf: "center",
|
||||
mx: 1,
|
||||
textAlign: "center"
|
||||
textAlign: "center",
|
||||
opacity: disabled ? 0.5 : 1
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
@@ -76,15 +83,20 @@ function _Counter(props: CounterProps) {
|
||||
title={`Increase ${title}`}
|
||||
icon="plus"
|
||||
variant={"small"}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onIncrease();
|
||||
}}
|
||||
disabled={disabled}
|
||||
onClick={
|
||||
disabled
|
||||
? undefined
|
||||
: (e) => {
|
||||
e.stopPropagation();
|
||||
onIncrease();
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export const Counter = React.memo(_Counter, (prev, next) => {
|
||||
return prev.value === next.value;
|
||||
return prev.value === next.value && prev.disabled === next.disabled;
|
||||
});
|
||||
|
||||
@@ -33,9 +33,11 @@ type DropdownProps = {
|
||||
items: MenuItem[];
|
||||
buttonRef?: React.MutableRefObject<HTMLButtonElement | undefined>;
|
||||
menuWidth?: number;
|
||||
disabled?: boolean;
|
||||
};
|
||||
export function Dropdown(props: DropdownProps) {
|
||||
const { id, group, items, selectedItem, buttonRef, menuWidth } = props;
|
||||
const { id, group, items, selectedItem, buttonRef, menuWidth, disabled } =
|
||||
props;
|
||||
const internalRef = useRef<HTMLButtonElement>();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const toolbarLocation = useToolbarLocation();
|
||||
@@ -74,6 +76,7 @@ export function Dropdown(props: DropdownProps) {
|
||||
bg: "transparent"
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
onClick={() => setIsOpen((s) => !s)}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
>
|
||||
|
||||
@@ -89,6 +89,7 @@ export const ToolButton = React.memo(
|
||||
return (
|
||||
prev.toggled === next.toggled &&
|
||||
prev.icon === next.icon &&
|
||||
prev.disabled === next.disabled &&
|
||||
JSON.stringify(prev.sx) === JSON.stringify(next.sx)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,9 @@ import {
|
||||
mdiUploadOutline,
|
||||
mdiWeb,
|
||||
mdiPencilOff,
|
||||
mdiPencil
|
||||
mdiPencil,
|
||||
mdiCheckboxMultipleBlankOutline,
|
||||
mdiCheckboxMultipleMarked
|
||||
} from "@mdi/js";
|
||||
|
||||
export const Icons = {
|
||||
@@ -243,6 +245,8 @@ export const Icons = {
|
||||
resize: mdiResizeBottomRight,
|
||||
readonlyOn: mdiPencilOff,
|
||||
readonlyOff: mdiPencil,
|
||||
selectAllUnchecked: mdiCheckboxMultipleBlankOutline,
|
||||
selectAllChecked: mdiCheckboxMultipleMarked,
|
||||
none: ""
|
||||
};
|
||||
|
||||
|
||||
@@ -375,6 +375,16 @@ export const MOBILE_STATIC_TOOLBAR_GROUPS: ToolbarDefinition = [
|
||||
[...STATIC_TOOLBAR_GROUPS[0], "previewAttachment"]
|
||||
];
|
||||
|
||||
export const READONLY_MOBILE_STATIC_TOOLBAR_GROUPS: ToolbarDefinition = [
|
||||
[
|
||||
"imageSettings",
|
||||
"attachmentSettings",
|
||||
"linkSettings",
|
||||
"webclipSettings",
|
||||
"previewAttachment"
|
||||
]
|
||||
];
|
||||
|
||||
const defaultPresets: Record<"default" | "minimal", ToolbarDefinition> = {
|
||||
default: [
|
||||
[
|
||||
|
||||
@@ -21,7 +21,8 @@ import { Flex, FlexProps } from "@theme-ui/components";
|
||||
import {
|
||||
getDefaultPresets,
|
||||
STATIC_TOOLBAR_GROUPS,
|
||||
MOBILE_STATIC_TOOLBAR_GROUPS
|
||||
MOBILE_STATIC_TOOLBAR_GROUPS,
|
||||
READONLY_MOBILE_STATIC_TOOLBAR_GROUPS
|
||||
} from "./tool-definitions";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Editor } from "../types";
|
||||
@@ -57,7 +58,9 @@ export function Toolbar(props: ToolbarProps) {
|
||||
const toolbarTools = useMemo(
|
||||
() =>
|
||||
isMobile
|
||||
? [...MOBILE_STATIC_TOOLBAR_GROUPS, ...tools]
|
||||
? editor?.current?.isEditable
|
||||
? [...MOBILE_STATIC_TOOLBAR_GROUPS, ...tools]
|
||||
: READONLY_MOBILE_STATIC_TOOLBAR_GROUPS
|
||||
: [...STATIC_TOOLBAR_GROUPS, ...tools],
|
||||
[tools, isMobile]
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ToolProps } from "../types";
|
||||
import { ToolButton } from "../components/tool-button";
|
||||
import { useRefValue } from "../../hooks/use-ref-value";
|
||||
import { IconNames } from "../icons";
|
||||
import { CodeBlock } from "../../extensions/code-block";
|
||||
|
||||
type Alignment = "left" | "right" | "center" | "justify";
|
||||
type AlignmentToolProps = ToolProps & {
|
||||
@@ -40,6 +41,7 @@ function AlignmentTool(props: AlignmentToolProps) {
|
||||
.setTextAlign(alignmentRef.current)
|
||||
.run();
|
||||
}}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
toggled={false}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -38,7 +38,11 @@ export function AttachmentSettings(props: ToolProps) {
|
||||
{...props}
|
||||
autoCloseOnUnmount
|
||||
popupId="attachmentSettings"
|
||||
tools={["downloadAttachment", "removeAttachment"]}
|
||||
tools={
|
||||
editor.isEditable
|
||||
? ["downloadAttachment"]
|
||||
: ["downloadAttachment", "removeAttachment"]
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Counter } from "../components/counter";
|
||||
import { useRefValue } from "../../hooks/use-ref-value";
|
||||
import { useToolbarStore } from "../stores/toolbar-store";
|
||||
import { getFontById, getFontIds, getFonts } from "../../utils/font";
|
||||
import { CodeBlock } from "../../extensions/code-block";
|
||||
|
||||
export function FontSize(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
@@ -45,7 +46,8 @@ export function FontSize(props: ToolProps) {
|
||||
|
||||
return (
|
||||
<Counter
|
||||
title="font size"
|
||||
title="Font size"
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
onDecrease={() =>
|
||||
editor.current
|
||||
?.chain()
|
||||
@@ -93,6 +95,7 @@ export function FontFamily(props: ToolProps) {
|
||||
selectedItem={getFontById(currentFontFamily)?.title || defaultFontFamily}
|
||||
items={items}
|
||||
menuWidth={130}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Dropdown } from "../components/dropdown";
|
||||
import { MenuItem } from "@notesnook/ui";
|
||||
import { ToolbarLocation, useToolbarLocation } from "../stores/toolbar-store";
|
||||
import { useMemo } from "react";
|
||||
import { CodeBlock } from "../../extensions/code-block";
|
||||
|
||||
const defaultLevels = [1, 2, 3, 4, 5, 6] as const;
|
||||
|
||||
@@ -48,6 +49,7 @@ export function Headings(props: ToolProps) {
|
||||
}
|
||||
items={items}
|
||||
menuWidth={130}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,20 +42,22 @@ export function ImageSettings(props: ToolProps) {
|
||||
autoCloseOnUnmount
|
||||
popupId="imageSettings"
|
||||
tools={
|
||||
findSelectedNode(editor, "image")?.attrs?.float
|
||||
? [
|
||||
"downloadAttachment",
|
||||
"imageAlignLeft",
|
||||
"imageAlignRight",
|
||||
"imageProperties"
|
||||
]
|
||||
: [
|
||||
"downloadAttachment",
|
||||
"imageAlignLeft",
|
||||
"imageAlignCenter",
|
||||
"imageAlignRight",
|
||||
"imageProperties"
|
||||
]
|
||||
editor.isEditable
|
||||
? findSelectedNode(editor, "image")?.attrs?.float
|
||||
? [
|
||||
"downloadAttachment",
|
||||
"imageAlignLeft",
|
||||
"imageAlignRight",
|
||||
"imageProperties"
|
||||
]
|
||||
: [
|
||||
"downloadAttachment",
|
||||
"imageAlignLeft",
|
||||
"imageAlignCenter",
|
||||
"imageAlignRight",
|
||||
"imageProperties"
|
||||
]
|
||||
: ["downloadAttachment"]
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { ToolProps } from "../types";
|
||||
import { ToolButton } from "../components/tool-button";
|
||||
import { useToolbarLocation } from "../stores/toolbar-store";
|
||||
import { CodeBlock } from "../../extensions/code-block";
|
||||
|
||||
export function Italic(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
@@ -61,6 +62,7 @@ export function Code(props: ToolProps) {
|
||||
<ToolButton
|
||||
{...props}
|
||||
toggled={editor.isActive("code")}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
onClick={() => editor.current?.chain().focus().toggleCode().run()}
|
||||
/>
|
||||
);
|
||||
@@ -84,6 +86,7 @@ export function Subscript(props: ToolProps) {
|
||||
<ToolButton
|
||||
{...props}
|
||||
toggled={editor.isActive("subscript")}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
onClick={() => editor.current?.chain().focus().toggleSubscript().run()}
|
||||
/>
|
||||
);
|
||||
@@ -95,6 +98,7 @@ export function Superscript(props: ToolProps) {
|
||||
<ToolButton
|
||||
{...props}
|
||||
toggled={editor.isActive("superscript")}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
onClick={() => editor.current?.chain().focus().toggleSuperscript().run()}
|
||||
/>
|
||||
);
|
||||
@@ -139,6 +143,7 @@ export function Math(props: ToolProps) {
|
||||
{...props}
|
||||
toggled={false}
|
||||
onClick={() => editor.current?.chain().focus().insertMathInline().run()}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ export function LinkSettings(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
const isBottom = useToolbarLocation() === "bottom";
|
||||
if (!editor.isActive("link") || !isBottom) return null;
|
||||
if (!editor.isEditable && isBottom) return null;
|
||||
|
||||
return (
|
||||
<MoreTools
|
||||
@@ -42,7 +41,11 @@ export function LinkSettings(props: ToolProps) {
|
||||
autoOpen
|
||||
autoCloseOnUnmount
|
||||
popupId="linkSettings"
|
||||
tools={["openLink", "editLink", "removeLink", "copyLink"]}
|
||||
tools={
|
||||
editor.isEditable
|
||||
? ["openLink", "editLink", "removeLink", "copyLink"]
|
||||
: ["openLink", "copyLink"]
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getTextDirection,
|
||||
TextDirections
|
||||
} from "../../extensions/text-direction";
|
||||
import { CodeBlock } from "../../extensions/code-block";
|
||||
|
||||
type TextDirectionToolProps = ToolProps & {
|
||||
direction: TextDirections;
|
||||
@@ -43,6 +44,7 @@ function TextDirectionTool(props: TextDirectionToolProps) {
|
||||
.setTextDirection(directionRef.current)
|
||||
.run()
|
||||
}
|
||||
disabled={editor.isActive(CodeBlock.name)}
|
||||
toggled={false}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -364,6 +364,7 @@ export function getDeletedNodes(
|
||||
typeof step.to === "number" &&
|
||||
"from" in step &&
|
||||
typeof step.from === "number" &&
|
||||
step.from < tr.doc.nodeSize - 1 &&
|
||||
step.slice === Slice.empty
|
||||
) {
|
||||
const $from = tr.doc.resolve(step.from);
|
||||
|
||||
@@ -481,6 +481,10 @@ span:focus .fake-cursor {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.math-block .katex-html {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
/* -- Selection Plugin ---------------------------------- */
|
||||
|
||||
/* p::selection,
|
||||
@@ -516,7 +520,7 @@ p > *::selection {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outline-list li > :first-child::before {
|
||||
.outline-list > li > :first-child::before {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -314,7 +314,9 @@ export const ALPHA_COLORS: readonly (keyof Colors)[] = [
|
||||
"hover",
|
||||
"backdrop",
|
||||
"background",
|
||||
"placeholder"
|
||||
"placeholder",
|
||||
"textSelection",
|
||||
"shade"
|
||||
];
|
||||
|
||||
export const Variants: readonly (keyof Variants)[] = [
|
||||
|
||||
@@ -50,7 +50,7 @@ export const createButtonVariant = (
|
||||
filter: "brightness(90%)",
|
||||
...states?.hover
|
||||
},
|
||||
":active": {
|
||||
":active:not(:disabled)": {
|
||||
bg: background,
|
||||
filter: "brightness(85%)",
|
||||
transform: "scale(0.98) !important",
|
||||
|
||||
@@ -141,10 +141,16 @@ export function MenuButton(props: MenuButtonProps) {
|
||||
const platform = getPlatform();
|
||||
function translateModifier(modifier: string) {
|
||||
if (platform === "Android" || platform === "iOS") return "";
|
||||
const isMacOS = platform === "macOS";
|
||||
const parts = modifier.split("-");
|
||||
return parts
|
||||
.map((p) => (p === "Mod" ? (platform === "macOS" ? "Cmd" : "Ctrl") : p))
|
||||
.join("+");
|
||||
.map((p) => {
|
||||
if (isMacOS) {
|
||||
return p === "Mod" ? "⌘" : p === "Alt" ? "⌥" : p === "Shift" ? "⇧" : p;
|
||||
}
|
||||
return p === "Mod" ? "Ctrl" : p;
|
||||
})
|
||||
.join(isMacOS ? "" : "+");
|
||||
}
|
||||
|
||||
function getPlatform() {
|
||||
|
||||
Reference in New Issue
Block a user