Refactor frontend to use store for note selection
- Migrate note and folder selection state to a Zustand store - and wire the Sidebar to consume it - Extract Sidebar-related UI into a new Sidebar/RenderFolder component - and remove inline sidebar from Home - Update Home to render Sidebar and drive editor from selectedNote - Extend notesStore with selectedNote and setters - add setSelectedFolder and setSelectedNote - Update prop types for DroppableFolder/RenderFolder - to use store-based callbacks
This commit is contained in:
parent
16313b961b
commit
fb461df550
6 changed files with 137 additions and 135 deletions
BIN
backend/notes.db
BIN
backend/notes.db
Binary file not shown.
19
frontend/package-lock.json
generated
19
frontend/package-lock.json
generated
|
|
@ -62,7 +62,6 @@
|
|||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
|
|
@ -639,7 +638,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
|
|
@ -729,7 +727,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
|
|
@ -739,7 +736,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
|
||||
"integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
|
|
@ -1276,7 +1272,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz",
|
||||
"integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "7.1.0"
|
||||
},
|
||||
|
|
@ -1641,7 +1636,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.3.0"
|
||||
}
|
||||
|
|
@ -3163,7 +3157,6 @@
|
|||
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@svgr/babel-preset": "8.1.0",
|
||||
|
|
@ -3588,7 +3581,6 @@
|
|||
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
|
|
@ -3599,7 +3591,6 @@
|
|||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
|
|
@ -3636,7 +3627,6 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -3744,7 +3734,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
|
|
@ -4742,6 +4731,7 @@
|
|||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
|
|
@ -4839,6 +4829,7 @@
|
|||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz",
|
||||
"integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
},
|
||||
|
|
@ -6410,7 +6401,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
|
|
@ -6432,7 +6422,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
|
|
@ -6795,8 +6784,7 @@
|
|||
"version": "4.1.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
|
||||
"integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
|
|
@ -7017,7 +7005,6 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const DroppableFolder = ({
|
|||
collapse,
|
||||
}: {
|
||||
folder: Partial<Folder>;
|
||||
setSelectedFolder: React.Dispatch<React.SetStateAction<number | null>>;
|
||||
setSelectedFolder: (id: number | null) => void;
|
||||
selectedFolder: number | null;
|
||||
selectedNote: NoteRead | null;
|
||||
setCollapse: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,30 @@
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { FolderCreate, FolderTreeResponse, folderApi } from "../../api/folders";
|
||||
import React, { useState, useRef, useEffect, SetStateAction } from "react";
|
||||
import {
|
||||
FolderCreate,
|
||||
FolderTreeNode,
|
||||
FolderTreeResponse,
|
||||
NoteRead,
|
||||
folderApi,
|
||||
} from "../../api/folders";
|
||||
import { DraggableNote } from "./DraggableNote";
|
||||
import { DroppableFolder } from "./DroppableFolder";
|
||||
import { useNoteStore } from "../../stores/notesStore";
|
||||
|
||||
export const Sidebar = () => {
|
||||
const [folderTree, setFolderTree] = useState<FolderTreeResponse | null>(null);
|
||||
export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||
// const [folderTree, setFolderTree] = useState<FolderTreeResponse | null>(null);
|
||||
const [newFolder, setNewFolder] = useState(false);
|
||||
const [newFolderText, setNewFolderText] = useState("");
|
||||
const newFolderRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const {
|
||||
setSelectedFolder,
|
||||
selectedFolder,
|
||||
folderTree,
|
||||
loadFolderTree,
|
||||
selectedNote,
|
||||
setSelectedNote,
|
||||
} = useNoteStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (newFolder && newFolderRef.current) {
|
||||
newFolderRef.current.focus();
|
||||
|
|
@ -30,18 +47,16 @@ export const Sidebar = () => {
|
|||
setNewFolder(false);
|
||||
};
|
||||
|
||||
const loadFolderTree = async () => {
|
||||
const data = await folderApi.tree();
|
||||
setFolderTree(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-ctp-mantle border-r border-ctp-surface2 w-[300px] p-4 overflow-y-auto sm:block hidden flex-col gap-3"
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onTouchMove={(e) => e.preventDefault()}
|
||||
>
|
||||
<SidebarHeader />
|
||||
<SidebarHeader
|
||||
clearSelection={clearSelection}
|
||||
setNewFolder={setNewFolder}
|
||||
/>
|
||||
{/* New folder input */}
|
||||
{newFolder && (
|
||||
<div className="mb-2">
|
||||
|
|
@ -75,7 +90,7 @@ export const Sidebar = () => {
|
|||
setSelectedFolder={setSelectedFolder}
|
||||
selectedFolder={selectedFolder}
|
||||
selectedNote={selectedNote}
|
||||
selectNote={selectNote}
|
||||
selectNote={setSelectedNote}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -90,7 +105,7 @@ export const Sidebar = () => {
|
|||
<DraggableNote
|
||||
key={note.id}
|
||||
note={note}
|
||||
selectNote={selectNote}
|
||||
selectNote={setSelectedNote}
|
||||
selectedNote={selectedNote}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -100,7 +115,13 @@ export const Sidebar = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const SidebarHeader = () => {
|
||||
export const SidebarHeader = ({
|
||||
clearSelection,
|
||||
setNewFolder,
|
||||
}: {
|
||||
clearSelection: () => void;
|
||||
setNewFolder: React.Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h2 className="text-lg font-semibold text-ctp-text">FastNotes</h2>
|
||||
|
|
@ -123,3 +144,65 @@ export const SidebarHeader = () => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface RenderFolderProps {
|
||||
folder: FolderTreeNode;
|
||||
depth?: number;
|
||||
setSelectedFolder: (id: number | null) => void;
|
||||
selectedFolder: number | null;
|
||||
selectedNote: NoteRead | null;
|
||||
selectNote: (note: NoteRead) => void;
|
||||
}
|
||||
|
||||
const RenderFolder = ({
|
||||
folder,
|
||||
depth = 0,
|
||||
setSelectedFolder,
|
||||
selectedFolder,
|
||||
selectedNote,
|
||||
selectNote,
|
||||
}: RenderFolderProps) => {
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={folder.id}
|
||||
className="flex flex-col"
|
||||
style={{ marginLeft: depth > 0 ? "1.5rem" : "0" }}
|
||||
>
|
||||
<DroppableFolder
|
||||
folder={folder}
|
||||
setSelectedFolder={setSelectedFolder}
|
||||
selectedFolder={selectedFolder}
|
||||
selectedNote={selectedNote}
|
||||
setCollapse={setCollapse}
|
||||
collapse={collapse}
|
||||
/>
|
||||
{collapse && (
|
||||
<>
|
||||
<div className="flex flex-col gap-0.5 ml-6">
|
||||
{folder.notes.map((note) => (
|
||||
<DraggableNote
|
||||
key={note.id}
|
||||
note={note}
|
||||
selectNote={selectNote}
|
||||
selectedNote={selectedNote}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{folder.children.map((child) => (
|
||||
<RenderFolder
|
||||
key={child.id}
|
||||
folder={child}
|
||||
depth={depth + 1}
|
||||
setSelectedFolder={setSelectedFolder}
|
||||
selectedFolder={selectedFolder}
|
||||
selectedNote={selectedNote}
|
||||
selectNote={selectNote}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import CheckIcon from "../assets/fontawesome/svg/circle-check.svg?react";
|
|||
import SpinnerIcon from "../assets/fontawesome/svg/rotate.svg?react";
|
||||
import { useNoteStore } from "../stores/notesStore";
|
||||
import { create } from "zustand";
|
||||
import { Sidebar } from "../components/sidebar/SideBar";
|
||||
|
||||
const simpleSandpackConfig: SandpackConfig = {
|
||||
defaultPreset: "react",
|
||||
|
|
@ -61,17 +62,26 @@ const simpleSandpackConfig: SandpackConfig = {
|
|||
|
||||
function Home() {
|
||||
// const [folderTree, setFolderTree] = useState<FolderTreeResponse | null>(null);
|
||||
const [selectedNote, setSelectedNote] = useState<NoteRead | null>(null);
|
||||
// const [selectedNote, setSelectedNote] = useState<NoteRead | null>(null);
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [newFolder, setNewFolder] = useState(false);
|
||||
const [newFolderText, setNewFolderText] = useState("");
|
||||
const [selectedFolder, setSelectedFolder] = useState<number | null>(null);
|
||||
// const [selectedFolder, setSelectedFolder] = useState<number | null>(null);
|
||||
const [encrypted, setEncrypted] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
|
||||
const { folderTree, loadFolderTree, createNote, createFolder, updateNote } =
|
||||
useNoteStore();
|
||||
const {
|
||||
setSelectedFolder,
|
||||
selectedFolder,
|
||||
folderTree,
|
||||
loadFolderTree,
|
||||
createNote,
|
||||
createFolder,
|
||||
updateNote,
|
||||
setSelectedNote,
|
||||
selectedNote,
|
||||
} = useNoteStore();
|
||||
|
||||
const pointer = useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
|
|
@ -103,17 +113,11 @@ function Home() {
|
|||
return () => clearTimeout(timer);
|
||||
}, [content, title]);
|
||||
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!title.trim()) return;
|
||||
await createNote({ title, content, folder_id: null });
|
||||
};
|
||||
|
||||
const handleCreateFolder = async () => {
|
||||
if (!newFolderText.trim()) return;
|
||||
await createFolder({ name: newFolderText, parent_id: null });
|
||||
};
|
||||
|
||||
const handleUpdate = async () => {
|
||||
if (!selectedNote) return;
|
||||
await updateNote(selectedNote.id, { title, content });
|
||||
|
|
@ -129,15 +133,6 @@ function Home() {
|
|||
clearSelection();
|
||||
};
|
||||
|
||||
const selectNote = (note: NoteRead) => {
|
||||
setSelectedNote(note);
|
||||
setTitle(note.title);
|
||||
|
||||
let cleanContent = note.content.replace(/\\([_\-\[\]\(\)])/g, "$1");
|
||||
cleanContent = cleanContent.replace(/^```\s*$/gm, "");
|
||||
setContent(cleanContent);
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
setSelectedNote(null);
|
||||
setTitle("");
|
||||
|
|
@ -159,88 +154,8 @@ function Home() {
|
|||
<DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}>
|
||||
<div className="flex bg-ctp-base h-screen text-ctp-text overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className="bg-ctp-mantle border-r border-ctp-surface2 w-[300px] p-4 overflow-y-auto sm:block hidden flex-shrink-0 flex flex-col gap-3"
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onTouchMove={(e) => e.preventDefault()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h2 className="text-lg font-semibold text-ctp-text">FastNotes</h2>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setNewFolder(true)}
|
||||
className="hover:bg-ctp-mauve group transition-colors rounded-md p-1.5"
|
||||
title="New folder"
|
||||
>
|
||||
<i className="fadr fa-folder-plus text-base text-ctp-mauve group-hover:text-ctp-base transition-colors"></i>
|
||||
</button>
|
||||
<button
|
||||
onClick={clearSelection}
|
||||
className="hover:bg-ctp-mauve group transition-colors rounded-md p-1.5"
|
||||
title="New note"
|
||||
>
|
||||
<i className="fadr fa-file-circle-plus text-base text-ctp-mauve group-hover:text-ctp-base transition-colors"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New folder input */}
|
||||
{newFolder && (
|
||||
<div className="mb-2">
|
||||
<input
|
||||
onBlur={() => setNewFolder(false)}
|
||||
onChange={(e) => setNewFolderText(e.target.value)}
|
||||
value={newFolderText}
|
||||
type="text"
|
||||
placeholder="Folder name..."
|
||||
className="border border-ctp-mauve rounded-md px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-ctp-mauve bg-ctp-base text-ctp-text placeholder:text-ctp-overlay0"
|
||||
ref={newFolderRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleCreateFolder();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setNewFolder(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Folder tree */}
|
||||
<div className="flex flex-col gap-1">
|
||||
{folderTree?.folders.map((folder) => (
|
||||
<RenderFolder
|
||||
key={folder.id}
|
||||
folder={folder}
|
||||
depth={0}
|
||||
setSelectedFolder={setSelectedFolder}
|
||||
selectedFolder={selectedFolder}
|
||||
selectedNote={selectedNote}
|
||||
selectNote={selectNote}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Orphaned notes */}
|
||||
{folderTree?.orphaned_notes &&
|
||||
folderTree.orphaned_notes.length > 0 && (
|
||||
<div className="mt-4 flex flex-col gap-1">
|
||||
{/*<div className="text-ctp-subtext0 text-sm font-medium mb-1 px-2">
|
||||
Unsorted
|
||||
</div>*/}
|
||||
{folderTree.orphaned_notes.map((note) => (
|
||||
<DraggableNote
|
||||
key={note.id}
|
||||
note={note}
|
||||
selectNote={selectNote}
|
||||
selectedNote={selectedNote}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Sidebar clearSelection={clearSelection} />
|
||||
|
||||
{/* Main editor area */}
|
||||
<div className="flex flex-col w-full h-screen overflow-hidden">
|
||||
|
|
@ -253,7 +168,7 @@ function Home() {
|
|||
<input
|
||||
type="text"
|
||||
placeholder="Untitled note..."
|
||||
value={title}
|
||||
value={selectedNote?.title || ""}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
className="w-full px-0 py-3 mb-4 text-3xl font-semibold bg-transparent border-b border-ctp-surface2 focus:outline-none focus:border-ctp-mauve transition-colors placeholder:text-ctp-overlay0 text-ctp-text"
|
||||
/>
|
||||
|
|
@ -261,7 +176,7 @@ function Home() {
|
|||
{/* Editor */}
|
||||
<div className="flex-1">
|
||||
<MDXEditor
|
||||
markdown={content}
|
||||
markdown={selectedNote?.content || ""}
|
||||
key={selectedNote?.id || "new"}
|
||||
onChange={setContent}
|
||||
className="prose prose-invert max-w-none text-ctp-text h-full dark-editor dark-mode"
|
||||
|
|
@ -380,7 +295,7 @@ export default Home;
|
|||
interface RenderFolderProps {
|
||||
folder: FolderTreeNode;
|
||||
depth?: number;
|
||||
setSelectedFolder: React.Dispatch<SetStateAction<number | null>>;
|
||||
setSelectedFolder: (id: number | null) => void;
|
||||
selectedFolder: number | null;
|
||||
selectedNote: NoteRead | null;
|
||||
selectNote: (note: NoteRead) => void;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,30 @@
|
|||
import { create } from "zustand";
|
||||
import { devtools, persist } from "zustand/middleware";
|
||||
import { folderApi, FolderCreate, FolderTreeResponse } from "../api/folders";
|
||||
import {
|
||||
folderApi,
|
||||
FolderCreate,
|
||||
FolderTreeResponse,
|
||||
NoteRead,
|
||||
} from "../api/folders";
|
||||
import { Note, NoteCreate, notesApi } from "../api/notes";
|
||||
|
||||
interface NoteState {
|
||||
folderTree: FolderTreeResponse | null;
|
||||
selectedFolder: number | null;
|
||||
selectedNote: NoteRead | null;
|
||||
|
||||
loadFolderTree: () => Promise<void>;
|
||||
createNote: (note: NoteCreate) => Promise<void>;
|
||||
createFolder: (folder: FolderCreate) => Promise<void>;
|
||||
updateNote: (id: number, note: Partial<Note>) => Promise<void>;
|
||||
createFolder: (folder: FolderCreate) => Promise<void>;
|
||||
setSelectedFolder: (id: number | null) => void;
|
||||
setSelectedNote: (id: NoteRead | null) => void;
|
||||
}
|
||||
|
||||
export const useNoteStore = create<NoteState>()((set, get) => ({
|
||||
folderTree: null,
|
||||
selectedFolder: null,
|
||||
selectedNote: null,
|
||||
|
||||
loadFolderTree: async () => {
|
||||
const data = await folderApi.tree();
|
||||
|
|
@ -36,4 +45,12 @@ export const useNoteStore = create<NoteState>()((set, get) => ({
|
|||
await notesApi.update(id, note);
|
||||
await get().loadFolderTree();
|
||||
},
|
||||
|
||||
setSelectedFolder: (id: number | null) => {
|
||||
set({ selectedFolder: id });
|
||||
},
|
||||
|
||||
setSelectedNote: (id: NoteRead | null) => {
|
||||
set({ selectedNote: id });
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
Loading…
Reference in a new issue