This commit is contained in:
James 2025-11-24 19:48:46 +00:00
parent 50d7f41248
commit 51709c9731
13 changed files with 163 additions and 124 deletions

Binary file not shown.

View file

@ -59,7 +59,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@ -636,7 +635,6 @@
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.23.0",
@ -726,7 +724,6 @@
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@marijn/find-cluster-break": "^1.0.0" "@marijn/find-cluster-break": "^1.0.0"
} }
@ -736,7 +733,6 @@
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
"integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@codemirror/state": "^6.5.0", "@codemirror/state": "^6.5.0",
"crelt": "^1.0.6", "crelt": "^1.0.6",
@ -1273,7 +1269,6 @@
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz",
"integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==", "integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": "7.1.0" "@fortawesome/fontawesome-common-types": "7.1.0"
}, },
@ -1638,7 +1633,6 @@
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@lezer/common": "^1.3.0" "@lezer/common": "^1.3.0"
} }
@ -3336,7 +3330,6 @@
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@ -3347,7 +3340,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@ -3384,7 +3376,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -3492,7 +3483,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.8.25", "baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754", "caniuse-lite": "^1.0.30001754",
@ -4351,6 +4341,7 @@
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "GitHub Sponsors ❤", "type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/dmonad" "url": "https://github.com/sponsors/dmonad"
@ -4429,6 +4420,7 @@
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz",
"integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"isomorphic.js": "^0.2.4" "isomorphic.js": "^0.2.4"
}, },
@ -5896,7 +5888,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@ -5918,7 +5909,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@ -6217,8 +6207,7 @@
"version": "4.1.17", "version": "4.1.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.3.0", "version": "2.3.0",
@ -6439,7 +6428,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",

View file

@ -0,0 +1,95 @@
import { FolderTreeResponse, FolderTreeNode } from "./folders";
export async function deriveKey(password: string) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveKey"],
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: enc.encode("your-app-salt"), // Store this somewhere consistent
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
);
}
export async function encryptString(
text: string,
key: CryptoKey,
): Promise<string> {
const enc = new TextEncoder();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
enc.encode(text),
);
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encrypted), iv.length);
return btoa(String.fromCharCode(...combined));
}
export async function decryptString(encrypted: string, key: CryptoKey) {
const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
const iv = combined.slice(0, 12);
const data = combined.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
data,
);
return new TextDecoder().decode(decrypted);
}
export async function decryptFolderTree(
tree: FolderTreeResponse,
encryptionKey: CryptoKey,
): Promise<FolderTreeResponse> {
const decryptFolder = async (
folder: FolderTreeNode,
): Promise<FolderTreeNode> => {
return {
...folder,
notes: await Promise.all(
folder.notes.map(async (note) => ({
...note,
title: await decryptString(note.title, encryptionKey),
content: await decryptString(note.content, encryptionKey),
})),
),
children: await Promise.all(
folder.children.map((child) => decryptFolder(child)),
),
};
};
return {
folders: await Promise.all(
tree.folders.map((folder) => decryptFolder(folder)),
),
orphaned_notes: await Promise.all(
tree.orphaned_notes.map(async (note) => ({
...note,
title: await decryptString(note.title, encryptionKey),
content: await decryptString(note.content, encryptionKey),
})),
),
};
}

View file

@ -1,4 +1,5 @@
import axios from "axios"; import axios from "axios";
import { decryptFolderTree, deriveKey } from "./encryption";
const API_URL = import.meta.env.PROD ? "/api" : "http://localhost:8000/api"; const API_URL = import.meta.env.PROD ? "/api" : "http://localhost:8000/api";
@ -35,8 +36,18 @@ export interface FolderCreate {
parent_id: number | null; parent_id: number | null;
} }
const getFolderTree = async () => {
const { data } = await axios.get<FolderTreeResponse>(
`${API_URL}/folders/tree`,
);
var key = await deriveKey("Test");
const decryptedFolderTree = await decryptFolderTree(data, key);
return decryptedFolderTree;
};
export const folderApi = { export const folderApi = {
tree: () => axios.get<FolderTreeResponse>(`${API_URL}/folders/tree`), tree: () => getFolderTree(),
list: () => axios.get<Folder[]>(`${API_URL}/folders`), list: () => axios.get<Folder[]>(`${API_URL}/folders`),
create: (folder: FolderCreate) => create: (folder: FolderCreate) =>
axios.post<Folder>(`${API_URL}/folders`, folder), axios.post<Folder>(`${API_URL}/folders`, folder),

View file

@ -1,4 +1,6 @@
import axios from "axios"; import axios from "axios";
import { NoteRead } from "./folders";
import { deriveKey, encryptString, decryptString } from "./encryption";
const API_URL = import.meta.env.PROD ? "/api" : "http://localhost:8000/api"; const API_URL = import.meta.env.PROD ? "/api" : "http://localhost:8000/api";
@ -18,82 +20,39 @@ export interface NoteCreate {
encrypted: boolean; encrypted: boolean;
} }
// Derive key from password
async function deriveKey(password: string) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveKey"],
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: enc.encode("your-app-salt"), // Store this somewhere consistent
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
);
}
// Encrypt content
async function encryptNote(content: string, key: CryptoKey) {
const enc = new TextEncoder();
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
enc.encode(content),
);
// Return IV + encrypted data as base64
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encrypted), iv.length);
return btoa(String.fromCharCode(...combined));
}
// Decrypt content
async function decryptNote(encrypted: string, key: CryptoKey) {
const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
const iv = combined.slice(0, 12);
const data = combined.slice(12);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
data,
);
return new TextDecoder().decode(decrypted);
}
const createNote = async (note: NoteCreate) => { const createNote = async (note: NoteCreate) => {
if (!note.encrypted) { var key = await deriveKey("Test");
return axios.post(`${API_URL}/notes`, note); var noteContent = await encryptString(note.content, key);
} else { var noteTitle = await encryptString(note.title, key);
var key = await deriveKey("Test");
var eNote = await encryptNote(note.content, key);
console.log(eNote); var encryptedNote = {
title: noteTitle,
content: noteContent,
folder_id: note.folder_id,
};
var unENote = await decryptNote(eNote, key); console.log(encryptedNote);
return axios.post(`${API_URL}/notes`, encryptedNote);
};
console.log(unENote); const fetchNotes = async () => {
} const { data } = await axios.get(`${API_URL}/notes`);
console.log(data);
var key = await deriveKey("Test");
const decryptedNotes = await Promise.all(
data.map(async (note: Note) => ({
...note,
title: await decryptString(note.title, key),
content: await decryptString(note.content, key),
})),
);
return decryptedNotes;
}; };
export const notesApi = { export const notesApi = {
list: () => axios.get(`${API_URL}/notes`), list: () => fetchNotes(),
get: (id: number) => axios.get(`${API_URL}/notes/${id}`), get: (id: number) => axios.get(`${API_URL}/notes/${id}`),
create: (note: NoteCreate) => createNote(note), create: (note: NoteCreate) => createNote(note),
update: (id: number, note: Partial<Note>) => update: (id: number, note: Partial<Note>) =>

View file

@ -27,7 +27,7 @@ export const DraggableNote = ({
<div <div
key={note.id} key={note.id}
onClick={() => selectNote(note)} onClick={() => selectNote(note)}
className={`ml-5 rounded-md px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${ className={` rounded-md px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${
selectedNote?.id === note.id selectedNote?.id === note.id
? "bg-ctp-mauve text-ctp-base" ? "bg-ctp-mauve text-ctp-base"
: "hover:bg-ctp-surface1" : "hover:bg-ctp-surface1"

View file

@ -68,7 +68,7 @@ function Home() {
}, []); }, []);
const loadFolderTree = async () => { const loadFolderTree = async () => {
const { data } = await folderApi.tree(); const data = await folderApi.tree();
setFolderTree(data); setFolderTree(data);
}; };
@ -113,7 +113,7 @@ function Home() {
}; };
const selectNote = (note: NoteRead) => { const selectNote = (note: NoteRead) => {
console.log("setting note.... " + note.id); console.log(note);
setSelectedNote(note); setSelectedNote(note);
setTitle(note.title); setTitle(note.title);
setContent(note.content); setContent(note.content);
@ -146,14 +146,16 @@ function Home() {
selectedFolder={selectedFolder} selectedFolder={selectedFolder}
selectedNote={selectedNote} selectedNote={selectedNote}
/> />
{folder.notes.map((note) => ( <div className="flex flex-col ml-5">
<DraggableNote {folder.notes.map((note) => (
key={note.id} <DraggableNote
note={note} key={note.id}
selectNote={selectNote} note={note}
selectedNote={selectedNote} selectNote={selectNote}
/> selectedNote={selectedNote}
))} />
))}
</div>
{folder.children.map((child) => renderFolder(child, depth + 1))} {folder.children.map((child) => renderFolder(child, depth + 1))}
</div> </div>
); );
@ -176,14 +178,9 @@ function Home() {
<DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}> <DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}>
<div className="flex bg-ctp-base min-h-screen text-ctp-text"> <div className="flex bg-ctp-base min-h-screen text-ctp-text">
<div <div
className="bg-ctp-mantle border-r-ctp-surface2 border-r overflow-hidden" className="bg-ctp-mantle border-r-ctp-surface2 border-r overflow-hidden w-[300px] p-4 overflow-y-auto sm:block hidden"
style={{ onDragOver={(e) => e.preventDefault()}
width: "300px", onTouchMove={(e) => e.preventDefault()}
padding: "1rem",
overflowY: "auto",
}}
onDragOver={(e) => e.preventDefault()} // Add this
onTouchMove={(e) => e.preventDefault()} // And this for touch devices
> >
<h2>Notes</h2> <h2>Notes</h2>
<button <button
@ -237,33 +234,22 @@ function Home() {
{/* Render orphaned notes */} {/* Render orphaned notes */}
{folderTree?.orphaned_notes && {folderTree?.orphaned_notes &&
folderTree.orphaned_notes.length > 0 && ( folderTree.orphaned_notes.length > 0 && (
<div className="mt-4"> <div className="mt-4 flex flex-col">
<div className="text-ctp-subtext0 text-sm mb-1">Unsorted</div> <div className="text-ctp-subtext0 text-sm mb-1">Unsorted</div>
{folderTree.orphaned_notes.map((note) => ( {folderTree.orphaned_notes.map((note) => (
<div <DraggableNote
key={note.id} key={note.id}
onClick={() => selectNote(note)} note={note}
className={`rounded-md px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${ selectNote={selectNote}
selectedNote?.id === note.id selectedNote={selectedNote}
? "bg-ctp-mauve text-ctp-base" />
: "hover:bg-ctp-surface1"
}`}
>
<i className="fadr fa-file text-xs"></i>
<span>{note.title}</span>
</div>
))} ))}
</div> </div>
)} )}
</div> </div>
<div
style={{ <div className="flex flex-col w-full">
flex: 1, <div className="w-full bg-ctp-crust h-4"></div>
padding: "1rem",
display: "flex",
flexDirection: "column",
}}
>
<input <input
type="text" type="text"
placeholder="Note title..." placeholder="Note title..."