From 89fecc5c08c0658c999fd2c46b832dd5aafce04b Mon Sep 17 00:00:00 2001 From: james fitzsimons Date: Sun, 30 Nov 2025 20:32:22 +0000 Subject: [PATCH] Refactor frontend: extract Editor and wire stores --- backend/app/routes/folders.py | 27 -- backend/notes.db | Bin 110592 -> 110592 bytes frontend/src/components/editor/Editor.tsx | 119 ++++++++ .../src/components/sidebar/DraggableNote.tsx | 19 +- .../components/sidebar/DroppableFolder.tsx | 9 +- .../components/sidebar/RecursiveFolder.tsx | 42 +++ frontend/src/components/sidebar/SideBar.tsx | 80 +----- frontend/src/pages/Home.tsx | 269 ++++-------------- frontend/src/stores/notesStore.ts | 22 +- frontend/src/stores/uiStore.ts | 13 + 10 files changed, 265 insertions(+), 335 deletions(-) create mode 100644 frontend/src/components/editor/Editor.tsx create mode 100644 frontend/src/components/sidebar/RecursiveFolder.tsx create mode 100644 frontend/src/stores/uiStore.ts diff --git a/backend/app/routes/folders.py b/backend/app/routes/folders.py index 9b4f976..02e1882 100644 --- a/backend/app/routes/folders.py +++ b/backend/app/routes/folders.py @@ -82,45 +82,18 @@ def update_folder( folder_id: int, folder_update: FolderUpdate, session: Session = Depends(get_session) ): """Update a folder""" - print(f"=== UPDATE FOLDER CALLED ===") - print(f"Folder ID: {folder_id}") - print(f"Update data received: {folder_update}") - folder = session.get(Folder, folder_id) if not folder: raise HTTPException(status_code=404, detail="Folder not found") - print( - f"Found folder: id={folder.id}, name={folder.name}, parent_id={folder.parent_id}" - ) - - # Update folder attributes from the request body update_data = folder_update.model_dump(exclude_unset=True) - print(f"Update data dict (exclude_unset): {update_data}") - print(f"Update data keys: {list(update_data.keys())}") for key, value in update_data.items(): - print(f"Setting {key} = {value}") setattr(folder, key, value) - print( - f"After setattr: id={folder.id}, name={folder.name}, parent_id={folder.parent_id}" - ) - session.add(folder) session.commit() - print(f"Committed changes to database") session.refresh(folder) - print( - f"After refresh: id={folder.id}, name={folder.name}, parent_id={folder.parent_id}" - ) - - # Verify the change persisted - verification = session.get(Folder, folder_id) - print( - f"Verification query: id={verification.id}, name={verification.name}, parent_id={verification.parent_id}" - ) - print(f"=== UPDATE COMPLETE ===") return folder diff --git a/backend/notes.db b/backend/notes.db index fd73d29b5cbc9a77ca27d3c5254fec26aed16bff..19604f6efb1f75c956443a79d57fe6579b5ab717 100644 GIT binary patch delta 456 zcmWO2O>dfD003ZE*V@{eCB5u0m-Rfd`T`AnLNEA3N-{ zWX%pc@3MQdL(faL+b;VDz4kZkvi^c6dCqUQ&TqFqKS717Jc|m~ufDx}iXg}?q9D1y zd%qE7@7eCB>_7C~&i8G8^Up^0D#Fh$pFi9|E;cU%6vAOl0MTyZu{dc2y3TuMq234# z4Feswq$!KgqF|76TxisNPsY{O(hhr25M$Cfrd=iIq_P3#WuEJG`>PUL94`5^457=_ zB6BJ^38VU|;rTG>QtQxnN^ffTSp-*I=B+SO+7n4_7Xh#bII|!);D=(cE(a_(o{VUV z_NTxehozM!;TmiuE9bSlPcy8Fd+>+=6`Zb+%ppTEph*AFtH00>RraRJqCfMw>?yL5 z5s~a7bC(gXqoWMG$d@w6`_uceW<44xL#53KBg{DomTc^5eQe67iB-^{JqPA&L0WLC pS|gVUu&xaXCsv|;{pg|cJ_!!)WrD60%ZE5lkrZ`v=b?`q{{hPti&g*t delta 460 zcmWO2K~K|A007`uHYpA`nE;6x@w|ZT>wD|EK-jab?aJD<(3N!wiDerd>$YyL?YhMT z#G^5hc<}7SJMqBTL~maF2V6Yx8@%ZE6TZv4#ml?JkB=$gdQ+r?n^#}{wj3KYiQLlS2Z#PWb)x4VqNkXD(*Giz1mxw;pQ7sH`@%1!FYel|k*$4H0I3-4?cl=g-R`=DT z25cyXd(K%-2u-=Zw*VQ4w^?=@;w=bEpB%FOh%J^_R6_g?LcD-503iFWEZ&wy>igy@ z{Rd=d4di0_p4M*0yY$}MjRFnglmBD!DN|##p6_c_t(RwGdya%IRQtZ!c5J5-CU%+} rE2L&7Foku~j7aeLy-!uwps>YqCCHZ;w8L<0p~&P;K=$_Dr`FYf)mV%g diff --git a/frontend/src/components/editor/Editor.tsx b/frontend/src/components/editor/Editor.tsx new file mode 100644 index 0000000..2edb707 --- /dev/null +++ b/frontend/src/components/editor/Editor.tsx @@ -0,0 +1,119 @@ +import { + BoldItalicUnderlineToggles, + codeBlockPlugin, + codeMirrorPlugin, + diffSourcePlugin, + headingsPlugin, + imagePlugin, + linkPlugin, + listsPlugin, + markdownShortcutPlugin, + MDXEditor, + quotePlugin, + SandpackConfig, + sandpackPlugin, + tablePlugin, + thematicBreakPlugin, + toolbarPlugin, + UndoRedo, +} from "@mdxeditor/editor"; +import { useNoteStore } from "../../stores/notesStore"; +import { useEffect } from "react"; +import { useUIStore } from "../../stores/uiStore"; + +const simpleSandpackConfig: SandpackConfig = { + defaultPreset: "react", + presets: [ + { + label: "React", + name: "react", + meta: "live react", + sandpackTemplate: "react", + sandpackTheme: "dark", + snippetFileName: "/App.js", + snippetLanguage: "jsx", + }, + ], +}; + +export const Editor = () => { + const { selectedNote, setContent, setTitle, updateNote } = useNoteStore(); + const { updating, setUpdating } = useUIStore(); + + useEffect(() => { + if (!selectedNote) return; + + const timer = setTimeout(async () => { + setUpdating(true); + handleUpdate(); + }, 2000); + + return () => clearTimeout(timer); + }, [selectedNote]); + + const handleUpdate = async () => { + if (!selectedNote) return; + await updateNote(selectedNote.id); + console.log(selectedNote.id); + + setTimeout(() => { + setUpdating(false); + }, 1000); + }; + + return ( +
+ {/* Title input */} + 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" + /> +
+ ( + <> + + + + ), + }), + + tablePlugin(), + listsPlugin(), + quotePlugin(), + thematicBreakPlugin(), + linkPlugin(), + codeBlockPlugin({ defaultCodeBlockLanguage: "js" }), + sandpackPlugin({ sandpackConfig: simpleSandpackConfig }), + codeMirrorPlugin({ + codeBlockLanguages: { + js: "JavaScript", + css: "CSS", + python: "Python", + typescript: "TypeScript", + html: "HTML", + }, + }), + imagePlugin(), + markdownShortcutPlugin(), + diffSourcePlugin({ + viewMode: "rich-text", + diffMarkdown: "boo", + }), + ]} + /> +
+
+ ); +}; diff --git a/frontend/src/components/sidebar/DraggableNote.tsx b/frontend/src/components/sidebar/DraggableNote.tsx index 0bf33d3..6516443 100644 --- a/frontend/src/components/sidebar/DraggableNote.tsx +++ b/frontend/src/components/sidebar/DraggableNote.tsx @@ -2,16 +2,11 @@ import React from "react"; import { useDraggable } from "@dnd-kit/core"; import { Note } from "../../api/notes"; import { NoteRead } from "../../api/folders"; +import { useNoteStore } from "../../stores/notesStore"; + +export const DraggableNote = ({ note }: { note: NoteRead }) => { + const { selectedNote, setSelectedNote } = useNoteStore(); -export const DraggableNote = ({ - note, - selectNote, - selectedNote, -}: { - note: NoteRead; - selectNote: (note: NoteRead) => void; - selectedNote: NoteRead | null; -}) => { const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: note.id, data: { type: "note", note }, @@ -26,14 +21,16 @@ export const DraggableNote = ({ ); diff --git a/frontend/src/components/sidebar/DroppableFolder.tsx b/frontend/src/components/sidebar/DroppableFolder.tsx index 35cfba2..4819271 100644 --- a/frontend/src/components/sidebar/DroppableFolder.tsx +++ b/frontend/src/components/sidebar/DroppableFolder.tsx @@ -1,22 +1,19 @@ import React from "react"; import { useDroppable, useDraggable } from "@dnd-kit/core"; import { Folder, NoteRead } from "../../api/folders"; +import { useNoteStore } from "../../stores/notesStore"; export const DroppableFolder = ({ folder, - setSelectedFolder, - selectedFolder, - selectedNote, setCollapse, collapse, }: { folder: Partial; - setSelectedFolder: (id: number | null) => void; - selectedFolder: number | null; - selectedNote: NoteRead | null; setCollapse: React.Dispatch>; collapse: boolean; }) => { + const { setSelectedFolder, selectedFolder, selectedNote } = useNoteStore(); + const { isOver, setNodeRef: setDroppableRef } = useDroppable({ id: folder.id!, data: { type: "folder", folder }, diff --git a/frontend/src/components/sidebar/RecursiveFolder.tsx b/frontend/src/components/sidebar/RecursiveFolder.tsx new file mode 100644 index 0000000..c70a1de --- /dev/null +++ b/frontend/src/components/sidebar/RecursiveFolder.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { FolderTreeNode } from "../../api/folders"; +import { DraggableNote } from "./DraggableNote"; +import { DroppableFolder } from "./DroppableFolder"; + +interface RecursiveFolderProps { + folder: FolderTreeNode; + depth?: number; +} + +export const RecursiveFolder = ({ + folder, + depth = 0, +}: RecursiveFolderProps) => { + const [collapse, setCollapse] = useState(false); + + return ( +
0 ? "1.5rem" : "0" }} + > + + {collapse && ( + <> +
+ {folder.notes.map((note) => ( + + ))} +
+ {folder.children.map((child) => ( + + ))} + + )} +
+ ); +}; diff --git a/frontend/src/components/sidebar/SideBar.tsx b/frontend/src/components/sidebar/SideBar.tsx index c3165a0..7d0e150 100644 --- a/frontend/src/components/sidebar/SideBar.tsx +++ b/frontend/src/components/sidebar/SideBar.tsx @@ -17,6 +17,7 @@ import { useSensors, } from "@dnd-kit/core"; import { notesApi } from "../../api/notes"; +import { RecursiveFolder } from "./RecursiveFolder"; export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { // const [folderTree, setFolderTree] = useState(null); @@ -143,15 +144,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { {/* Folder tree */}
{folderTree?.folders.map((folder) => ( - + ))}
@@ -162,12 +155,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { Unsorted */} {folderTree.orphaned_notes.map((note) => ( - + ))} )} @@ -205,65 +193,3 @@ export const SidebarHeader = ({ ); }; - -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 ( -
0 ? "1.5rem" : "0" }} - > - - {collapse && ( - <> -
- {folder.notes.map((note) => ( - - ))} -
- {folder.children.map((child) => ( - - ))} - - )} -
- ); -}; diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 097423a..dd8b4d7 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -46,21 +46,8 @@ 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", - presets: [ - { - label: "React", - name: "react", - meta: "live react", - sandpackTemplate: "react", - sandpackTheme: "dark", - snippetFileName: "/App.js", - snippetLanguage: "jsx", - }, - ], -}; +import { Editor } from "../components/editor/Editor"; +import { useUIStore } from "../stores/uiStore"; function Home() { // const [folderTree, setFolderTree] = useState(null); @@ -71,7 +58,7 @@ function Home() { const [newFolderText, setNewFolderText] = useState(""); // const [selectedFolder, setSelectedFolder] = useState(null); const [encrypted, setEncrypted] = useState(false); - const [updating, setUpdating] = useState(false); + // const [updating, setUpdating] = useState(false); const { setSelectedFolder, @@ -85,7 +72,7 @@ function Home() { selectedNote, } = useNoteStore(); - + const { updating } = useUIStore(); const newFolderRef = useRef(null); @@ -99,31 +86,11 @@ function Home() { } }, [newFolder]); - useEffect(() => { - if (!selectedNote) return; - - const timer = setTimeout(async () => { - setUpdating(true); - handleUpdate(); - }, 2000); - - return () => clearTimeout(timer); - }, [content, title]); - const handleCreate = async () => { if (!title.trim()) return; await createNote({ title, content, folder_id: null }); }; - const handleUpdate = async () => { - if (!selectedNote) return; - await updateNote(selectedNote.id, { title, content }); - - setTimeout(() => { - setUpdating(false); - }, 1000); - }; - const handleDelete = async (id: number) => { await notesApi.delete(id); loadFolderTree(); @@ -136,193 +103,71 @@ function Home() { setContent(""); }; - - return ( +
+ {/* Sidebar */} -
- {/* Sidebar */} + - + {/* Main editor area */} +
+ {/* Top accent bar */} +
- {/* Main editor area */} -
- {/* Top accent bar */} -
+ - {/* Content area with padding */} -
- {/* Title input */} - 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" - /> - - {/* Editor */} -
- ( - <> - - - - ), - }), - - tablePlugin(), - listsPlugin(), - quotePlugin(), - thematicBreakPlugin(), - linkPlugin(), - codeBlockPlugin({ defaultCodeBlockLanguage: "js" }), - sandpackPlugin({ sandpackConfig: simpleSandpackConfig }), - codeMirrorPlugin({ - codeBlockLanguages: { - js: "JavaScript", - css: "CSS", - python: "Python", - typescript: "TypeScript", - html: "HTML", - }, - }), - imagePlugin(), - markdownShortcutPlugin(), - diffSourcePlugin({ - viewMode: "rich-text", - diffMarkdown: "boo", - }), - ]} - /> -
-
- - {/* Action bar */} -
- {selectedNote ? ( - <> - - - - - ) : ( - - )} -
-
- - {/* Status indicator */} -
- {updating ? ( + {/* Action bar */} +
+ {selectedNote ? ( <> - - - Saving... - + {/**/} + + ) : ( - <> - - - Saved - - + )}
+ + {/* Status indicator */} +
+ {updating ? ( + <> + + + Saving... + + + ) : ( + <> + + Saved + + )} +
+
); } export default Home; - -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 ( -
0 ? "1.5rem" : "0" }} - > - - {collapse && ( - <> -
- {folder.notes.map((note) => ( - - ))} -
- {folder.children.map((child) => ( - - ))} - - )} -
- ); -}; diff --git a/frontend/src/stores/notesStore.ts b/frontend/src/stores/notesStore.ts index 7f407fc..7e9135b 100644 --- a/frontend/src/stores/notesStore.ts +++ b/frontend/src/stores/notesStore.ts @@ -7,15 +7,18 @@ import { NoteRead, } from "../api/folders"; import { Note, NoteCreate, notesApi } from "../api/notes"; +import { getSelectedNode } from "@mdxeditor/editor"; interface NoteState { folderTree: FolderTreeResponse | null; selectedFolder: number | null; selectedNote: NoteRead | null; + setContent: (content: string) => void; + setTitle: (title: string) => void; loadFolderTree: () => Promise; createNote: (note: NoteCreate) => Promise; - updateNote: (id: number, note: Partial) => Promise; + updateNote: (id: number) => Promise; createFolder: (folder: FolderCreate) => Promise; setSelectedFolder: (id: number | null) => void; setSelectedNote: (id: NoteRead | null) => void; @@ -26,6 +29,20 @@ export const useNoteStore = create()((set, get) => ({ selectedFolder: null, selectedNote: null, + setContent: (content) => { + const currentNote = get().selectedNote; + if (currentNote) { + set({ selectedNote: { ...currentNote, content: content } }); + } + }, + + setTitle: (title) => { + const currentNote = get().selectedNote; + if (currentNote) { + set({ selectedNote: { ...currentNote, title: title } }); + } + }, + loadFolderTree: async () => { const data = await folderApi.tree(); set({ folderTree: data }); @@ -41,7 +58,8 @@ export const useNoteStore = create()((set, get) => ({ await get().loadFolderTree(); }, - updateNote: async (id: number, note: Partial) => { + updateNote: async (id: number) => { + const note = get().selectedNote as Partial; await notesApi.update(id, note); await get().loadFolderTree(); }, diff --git a/frontend/src/stores/uiStore.ts b/frontend/src/stores/uiStore.ts new file mode 100644 index 0000000..6d9cca9 --- /dev/null +++ b/frontend/src/stores/uiStore.ts @@ -0,0 +1,13 @@ +import { create } from "zustand"; + +interface UIState { + updating: boolean; + setUpdating: (update: boolean) => void; +} + +export const useUIStore = create()((set, get) => ({ + updating: false, + setUpdating: (update) => { + set({ updating: update }); + }, +}));