From e9e0abe380a5f503d56beecb8a4baf503149fdd4 Mon Sep 17 00:00:00 2001 From: nicwands Date: Tue, 3 Mar 2026 17:09:29 -0500 Subject: [PATCH] new category flow --- out/main/index.js | 45 ++++-- package-lock.json | 164 ++++++++++++-------- package.json | 7 +- src/main/core/NotesAPI.js | 56 +++++-- src/renderer/src/components/CategoryRow.vue | 110 ++++++++++++- src/renderer/src/components/menu/Index.vue | 10 +- src/renderer/src/composables/useMenu.js | 2 +- src/renderer/src/composables/useNotes.js | 11 ++ src/renderer/src/plugins/router.js | 2 + src/renderer/src/views/Category.vue | 26 +++- src/renderer/src/views/CreateCategory.vue | 21 +++ src/renderer/src/views/Editor.vue | 118 +++++++++++++- 12 files changed, 463 insertions(+), 109 deletions(-) create mode 100644 src/renderer/src/views/CreateCategory.vue diff --git a/out/main/index.js b/out/main/index.js index 558ec34..369e19a 100644 --- a/out/main/index.js +++ b/out/main/index.js @@ -114,7 +114,9 @@ class NotesAPI { } const key = Buffer.from(this.encryptionKey, "hex"); if (key.length !== 32) { - throw new Error("Encryption key must be 64 hex characters (32 bytes)"); + throw new Error( + "Encryption key must be 64 hex characters (32 bytes)" + ); } const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); const message = JSON.stringify(note); @@ -132,17 +134,42 @@ class NotesAPI { } const key = Buffer.from(this.encryptionKey, "hex"); if (key.length !== 32) { - throw new Error("Encryption key must be 64 hex characters (32 bytes)"); + throw new Error( + "Encryption key must be 64 hex characters (32 bytes)" + ); + } + let combined; + try { + combined = Buffer.from(encryptedData, "base64"); + } catch (e) { + throw new Error("Invalid encrypted data: not valid base64"); + } + if (combined.length < sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES) { + throw new Error("Invalid encrypted data: too short"); } - const combined = Buffer.from(encryptedData, "base64"); const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES); const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES); - const decrypted = sodium.crypto_secretbox_open_easy( - ciphertext, - nonce, - key - ); - return JSON.parse(decrypted.toString()); + let decrypted; + try { + decrypted = sodium.crypto_secretbox_open_easy( + ciphertext, + nonce, + key + ); + } catch (e) { + throw new Error("Decryption failed: wrong key or corrupted data"); + } + if (!decrypted) { + throw new Error("Decryption failed: no data returned"); + } + const decryptedStr = Buffer.from(decrypted).toString("utf8"); + try { + return JSON.parse(decryptedStr); + } catch (e) { + throw new Error( + `Decryption succeeded but invalid JSON: ${decryptedStr}` + ); + } } async init() { await this._initSodium(); diff --git a/package-lock.json b/package-lock.json index 2d31773..13e7317 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@fuzzco/font-loader": "^1.0.2", "@takerofnotes/plugin-filesystem": "^0.2.0", "@takerofnotes/plugin-supabase": "^0.1.0", + "@tiptap/extension-code-block-lowlight": "^3.20.0", "@tiptap/extension-document": "^3.19.0", - "@tiptap/extension-image": "^3.19.0", - "@tiptap/extension-table": "^3.19.0", - "@tiptap/markdown": "^3.19.0", + "@tiptap/extension-highlight": "^3.20.0", + "@tiptap/extension-list": "^3.20.0", "@tiptap/starter-kit": "^3.19.0", "@tiptap/vue-3": "^3.19.0", "@vueuse/core": "^14.2.1", @@ -29,6 +29,7 @@ "lenis": "^1.3.17", "libsodium-wrappers": "^0.8.2", "lodash": "^4.17.23", + "lowlight": "^3.3.0", "sass": "^1.97.3", "sass-embedded": "^1.97.3", "tempus": "^1.0.0-dev.17", @@ -2464,17 +2465,34 @@ } }, "node_modules/@tiptap/extension-code-block": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.19.0.tgz", - "integrity": "sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.20.0.tgz", + "integrity": "sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-code-block-lowlight": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.20.0.tgz", + "integrity": "sha512-9lN9rn07lOWkLnByT5C1axtq56MHpOI7MpLaCmX3p+x1bDl6Uvixm6AoBdTLfZUmUYeEFBsf7t5cR+QepMbkiA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/extension-code-block": "^3.20.0", + "@tiptap/pm": "^3.20.0", + "highlight.js": "^11", + "lowlight": "^2 || ^3" } }, "node_modules/@tiptap/extension-document": { @@ -2558,6 +2576,19 @@ "@tiptap/core": "^3.19.0" } }, + "node_modules/@tiptap/extension-highlight": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.20.0.tgz", + "integrity": "sha512-ANL1wFz0s1ScNR4uBfO0s6Sz+qqGp2u6ynrCVk6TCT3d10CQ+gD1gSDTrVRC3CtlMKtHHH4fYrFAq284+J0gKA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, "node_modules/@tiptap/extension-horizontal-rule": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.19.0.tgz", @@ -2572,19 +2603,6 @@ "@tiptap/pm": "^3.19.0" } }, - "node_modules/@tiptap/extension-image": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.19.0.tgz", - "integrity": "sha512-/rGl8nBziBPVJJ/9639eQWFDKcI3RQsDM3s+cqYQMFQfMqc7sQB9h4o4sHCBpmKxk3Y0FV/0NjnjLbBVm8OKdQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.19.0" - } - }, "node_modules/@tiptap/extension-italic": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.19.0.tgz", @@ -2694,20 +2712,6 @@ "@tiptap/core": "^3.19.0" } }, - "node_modules/@tiptap/extension-table": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-3.19.0.tgz", - "integrity": "sha512-Lg8DlkkDUMYE/CcGOxoCWF98B2i7VWh+AGgqlF+XWrHjhlKHfENLRXm1a0vWuyyP3NknRYILoaaZ1s7QzmXKRA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" - } - }, "node_modules/@tiptap/extension-text": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.19.0.tgz", @@ -2748,23 +2752,6 @@ "@tiptap/pm": "^3.19.0" } }, - "node_modules/@tiptap/markdown": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/@tiptap/markdown/-/markdown-3.19.0.tgz", - "integrity": "sha512-Pnfacq2FHky1rqwmGwEmUJxuZu8VZ8XjaJIqsQC34S3CQWiOU+PukC9In2odzcooiVncLWT9s97jKuYpbmF1tQ==", - "license": "MIT", - "dependencies": { - "marked": "^17.0.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.19.0", - "@tiptap/pm": "^3.19.0" - } - }, "node_modules/@tiptap/pm": { "version": "3.20.0", "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.0.tgz", @@ -2890,6 +2877,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -2970,6 +2966,12 @@ "@types/node": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/verror": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", @@ -4422,6 +4424,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4439,6 +4450,19 @@ "license": "MIT", "optional": true }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", @@ -5670,6 +5694,15 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", @@ -6202,6 +6235,21 @@ "node": ">=8" } }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6288,18 +6336,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/marked": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.2.tgz", - "integrity": "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", diff --git a/package.json b/package.json index 875f68a..60626e4 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,10 @@ "@fuzzco/font-loader": "^1.0.2", "@takerofnotes/plugin-filesystem": "^0.2.0", "@takerofnotes/plugin-supabase": "^0.1.0", + "@tiptap/extension-code-block-lowlight": "^3.20.0", "@tiptap/extension-document": "^3.19.0", - "@tiptap/extension-image": "^3.19.0", - "@tiptap/extension-table": "^3.19.0", - "@tiptap/markdown": "^3.19.0", + "@tiptap/extension-highlight": "^3.20.0", + "@tiptap/extension-list": "^3.20.0", "@tiptap/starter-kit": "^3.19.0", "@tiptap/vue-3": "^3.19.0", "@vueuse/core": "^14.2.1", @@ -45,6 +45,7 @@ "lenis": "^1.3.17", "libsodium-wrappers": "^0.8.2", "lodash": "^4.17.23", + "lowlight": "^3.3.0", "sass": "^1.97.3", "sass-embedded": "^1.97.3", "tempus": "^1.0.0-dev.17", diff --git a/src/main/core/NotesAPI.js b/src/main/core/NotesAPI.js index 05b4e5f..8f3f8d8 100644 --- a/src/main/core/NotesAPI.js +++ b/src/main/core/NotesAPI.js @@ -33,7 +33,9 @@ export default class NotesAPI { const key = Buffer.from(this.encryptionKey, 'hex') if (key.length !== 32) { - throw new Error('Encryption key must be 64 hex characters (32 bytes)') + throw new Error( + 'Encryption key must be 64 hex characters (32 bytes)', + ) } const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES) @@ -42,7 +44,7 @@ export default class NotesAPI { const ciphertext = sodium.crypto_secretbox_easy( Buffer.from(message), nonce, - key + key, ) const combined = Buffer.concat([nonce, ciphertext]) @@ -56,20 +58,53 @@ export default class NotesAPI { const key = Buffer.from(this.encryptionKey, 'hex') if (key.length !== 32) { - throw new Error('Encryption key must be 64 hex characters (32 bytes)') + throw new Error( + 'Encryption key must be 64 hex characters (32 bytes)', + ) + } + + let combined + try { + combined = Buffer.from(encryptedData, 'base64') + } catch (e) { + throw new Error('Invalid encrypted data: not valid base64') + } + + if ( + combined.length < + sodium.crypto_secretbox_NONCEBYTES + + sodium.crypto_secretbox_MACBYTES + ) { + throw new Error('Invalid encrypted data: too short') } - const combined = Buffer.from(encryptedData, 'base64') const nonce = combined.slice(0, sodium.crypto_secretbox_NONCEBYTES) const ciphertext = combined.slice(sodium.crypto_secretbox_NONCEBYTES) - const decrypted = sodium.crypto_secretbox_open_easy( - ciphertext, - nonce, - key - ) + let decrypted + try { + decrypted = sodium.crypto_secretbox_open_easy( + ciphertext, + nonce, + key, + ) + } catch (e) { + throw new Error('Decryption failed: wrong key or corrupted data') + } - return JSON.parse(decrypted.toString()) + if (!decrypted) { + throw new Error('Decryption failed: no data returned') + } + + const decryptedStr = Buffer.from(decrypted).toString('utf8') + + try { + return JSON.parse(decryptedStr) + } catch (e) { + throw new Error( + `Decryption succeeded but invalid JSON: ${decryptedStr}`, + ) + } } async init() { @@ -81,6 +116,7 @@ export default class NotesAPI { for (const encryptedNote of encryptedNotes) { try { const note = this._decrypt(encryptedNote.data || encryptedNote) + this.notesCache.set(note.id, note) this.index.add(note.id, note.title + '\n' + note.content) } catch (error) { diff --git a/src/renderer/src/components/CategoryRow.vue b/src/renderer/src/components/CategoryRow.vue index 94c4578..2507d4c 100644 --- a/src/renderer/src/components/CategoryRow.vue +++ b/src/renderer/src/components/CategoryRow.vue @@ -1,18 +1,84 @@ diff --git a/src/renderer/src/views/Editor.vue b/src/renderer/src/views/Editor.vue index b456b8d..045320a 100644 --- a/src/renderer/src/views/Editor.vue +++ b/src/renderer/src/views/Editor.vue @@ -14,6 +14,12 @@ > Italic + @@ -24,12 +30,14 @@