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 @@
-
+
{{ String(index + 1).padStart(2, '0') }}.
- {{ category }}
-
+
+ {{ categoryInput }}
+
+
+
+
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 @@