Update notes response model and implement error handling
This commit is contained in:
parent
3fe4b9ea88
commit
ffbf485935
12 changed files with 213 additions and 126 deletions
|
|
@ -2,14 +2,14 @@ from datetime import datetime
|
||||||
|
|
||||||
from app.auth import require_auth
|
from app.auth import require_auth
|
||||||
from app.database import get_session
|
from app.database import get_session
|
||||||
from app.models import Note, NoteCreate, NoteUpdate, User
|
from app.models import Note, NoteCreate, NoteRead, NoteUpdate, User
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
router = APIRouter(prefix="/notes", tags=["notes"])
|
router = APIRouter(prefix="/notes", tags=["notes"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/", response_model=list[NoteRead])
|
||||||
def list_notes(session: Session = Depends(get_session)):
|
def list_notes(session: Session = Depends(get_session)):
|
||||||
notes = session.exec(select(Note).order_by(Note.updated_at.desc())).all() # pyright: ignore[reportAttributeAccessIssue]
|
notes = session.exec(select(Note).order_by(Note.updated_at.desc())).all() # pyright: ignore[reportAttributeAccessIssue]
|
||||||
return notes
|
return notes
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export type NoteRead = CamelCasedPropertiesDeep<
|
||||||
export type NoteCreate = CamelCasedPropertiesDeep<
|
export type NoteCreate = CamelCasedPropertiesDeep<
|
||||||
components["schemas"]["NoteCreate"]
|
components["schemas"]["NoteCreate"]
|
||||||
>;
|
>;
|
||||||
|
export type Note = CamelCasedPropertiesDeep<components["schemas"]["Note"]>;
|
||||||
|
|
||||||
const createNote = async (note: NoteCreate) => {
|
const createNote = async (note: NoteCreate) => {
|
||||||
const encryptionKey = useAuthStore.getState().encryptionKey;
|
const encryptionKey = useAuthStore.getState().encryptionKey;
|
||||||
|
|
@ -31,23 +32,31 @@ const fetchNotes = async () => {
|
||||||
const encryptionKey = useAuthStore.getState().encryptionKey;
|
const encryptionKey = useAuthStore.getState().encryptionKey;
|
||||||
if (!encryptionKey) throw new Error("Not authenticated");
|
if (!encryptionKey) throw new Error("Not authenticated");
|
||||||
|
|
||||||
const { data } = await client.GET(`/api/notes/`);
|
const { data, error } = await client.GET(`/api/notes/`);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
console.log(data);
|
console.log(data);
|
||||||
const decryptedNotes = await Promise.all(
|
|
||||||
data.map(async (note: NoteRead) => ({
|
if (data) {
|
||||||
...note,
|
const decryptedNotes = await Promise.all(
|
||||||
title: await decryptString(note.title, encryptionKey),
|
data.map(async (note) => ({
|
||||||
content: await decryptString(note.content, encryptionKey),
|
...note,
|
||||||
tags: await Promise.all(
|
title: await decryptString(note.title, encryptionKey),
|
||||||
note.tags.map(async (tag) => ({
|
content: await decryptString(note.content, encryptionKey),
|
||||||
...tag,
|
tags: note.tags
|
||||||
name: await decryptString(tag.name, encryptionKey),
|
? await Promise.all(
|
||||||
})),
|
note.tags.map(async (tag) => ({
|
||||||
),
|
...tag,
|
||||||
})),
|
name: await decryptString(tag.name, encryptionKey),
|
||||||
);
|
})),
|
||||||
return decryptedNotes;
|
)
|
||||||
|
: [],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
return decryptedNotes;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNote = async (id: number, note: Partial<NoteRead>) => {
|
const updateNote = async (id: number, note: Partial<NoteRead>) => {
|
||||||
|
|
@ -64,9 +73,7 @@ const updateNote = async (id: number, note: Partial<NoteRead>) => {
|
||||||
if (note.folderId) {
|
if (note.folderId) {
|
||||||
encryptedNote.folderId = note.folderId;
|
encryptedNote.folderId = note.folderId;
|
||||||
}
|
}
|
||||||
// if (!note.folderId){
|
|
||||||
// throw new Error("Folder id missing from note.")
|
|
||||||
// }
|
|
||||||
const { data, error } = await client.PATCH(`/api/notes/{note_id}`, {
|
const { data, error } = await client.PATCH(`/api/notes/{note_id}`, {
|
||||||
body: encryptedNote,
|
body: encryptedNote,
|
||||||
params: {
|
params: {
|
||||||
|
|
|
||||||
|
|
@ -104,10 +104,14 @@ export const useUpdateFolder = () => {
|
||||||
folders: FolderTreeNode[],
|
folders: FolderTreeNode[],
|
||||||
): FolderTreeNode[] => {
|
): FolderTreeNode[] => {
|
||||||
return folders.map((f) => {
|
return folders.map((f) => {
|
||||||
if (f.id == folderId) {
|
if (f.id === folderId) {
|
||||||
return {
|
return {
|
||||||
...f,
|
...f,
|
||||||
...folder,
|
...(folder.name !== undefined &&
|
||||||
|
folder.name !== null && { name: folder.name }),
|
||||||
|
...(folder.parentId !== undefined && {
|
||||||
|
parentId: folder.parentId,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,16 @@ import { Sidebar } from "./components/sidebar/SideBar";
|
||||||
import { StatusIndicator } from "./components/StatusIndicator";
|
import { StatusIndicator } from "./components/StatusIndicator";
|
||||||
import { useCreateTag, useTagTree } from "@/hooks/useTags";
|
import { useCreateTag, useTagTree } from "@/hooks/useTags";
|
||||||
import { useFolderTree, useUpdateNote } from "@/hooks/useFolders";
|
import { useFolderTree, useUpdateNote } from "@/hooks/useFolders";
|
||||||
import { Note } from "@/api/notes";
|
import { Note, NoteRead } from "@/api/notes";
|
||||||
import { DecryptedTagNode } from "@/api/encryption";
|
import { DecryptedTagNode } from "@/api/encryption";
|
||||||
|
// @ts-ignore
|
||||||
|
import XmarkIcon from "@/assets/fontawesome/svg/xmark.svg?react";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const [newFolder] = useState(false);
|
const [newFolder] = useState(false);
|
||||||
|
|
||||||
// Local state for editing the current note
|
// Local state for editing the current note
|
||||||
const [editingNote, setEditingNote] = useState<Note | null>(null);
|
const [editingNote, setEditingNote] = useState<NoteRead | null>(null);
|
||||||
const [lastSavedNote, setLastSavedNote] = useState<{
|
const [lastSavedNote, setLastSavedNote] = useState<{
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -85,6 +87,7 @@ function Home() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!editingNote.id) throw new Error("Editing note has no id.");
|
||||||
await updateNoteMutation.mutateAsync({
|
await updateNoteMutation.mutateAsync({
|
||||||
noteId: editingNote.id,
|
noteId: editingNote.id,
|
||||||
note: {
|
note: {
|
||||||
|
|
@ -116,51 +119,41 @@ function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="flex bg-ctp-base h-screen text-ctp-text overflow-hidden">
|
<div className="flex bg-ctp-base h-screen text-ctp-text overflow-hidden">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
{showModal && <Modal />}
|
<AnimatePresence>{showModal && <Modal />}</AnimatePresence>
|
||||||
|
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
{/*<div className="flex flex-col">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={tagName}
|
|
||||||
onChange={(e) => setTagName(e.target.value)}
|
|
||||||
/>
|
|
||||||
{tags.map((tag) => (
|
|
||||||
<button onClick={() => deleteTag(tag.id)} key={tag.id}>
|
|
||||||
{tag.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>*/}
|
|
||||||
|
|
||||||
{/* Main editor area */}
|
{/* Main editor area */}
|
||||||
<div className="flex flex-col w-full h-screen overflow-hidden">
|
<div className="flex flex-col w-full h-screen overflow-y-auto items-center justify-center">
|
||||||
{/*<Editor />*/}
|
{/*<Editor />*/}
|
||||||
<input
|
<div className="h-full lg:w-3xl w-full">
|
||||||
type="text"
|
<input
|
||||||
placeholder="Untitled note..."
|
type="text"
|
||||||
value={editingNote?.title || ""}
|
placeholder="Untitled note..."
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
value={editingNote?.title || ""}
|
||||||
className="w-full px-4 py-3 text-3xl font-semibold bg-transparentfocus:outline-none focus:border-ctp-mauve transition-colors placeholder:text-ctp-overlay0 text-ctp-text"
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
/>
|
className="w-full p-4 pb-0 text-3xl font-semibold bg-transparent focus:outline-none border-transparent focus:border-ctp-mauve transition-colors placeholder:text-ctp-overlay0 text-ctp-text"
|
||||||
<div className="px-4 py-2 border-b border-ctp-surface2 flex items-center gap-2 flex-wrap">
|
/>
|
||||||
{editingNote?.tags &&
|
{/*<div className="px-4 py-2 border-ctp-surface2 flex items-center gap-2 flex-wrap">
|
||||||
editingNote.tags.map((tag) => (
|
{editingNote?.tags &&
|
||||||
<button
|
editingNote.tags.map((tag) => (
|
||||||
onClick={() => null}
|
<button
|
||||||
key={tag.id}
|
onClick={() => null}
|
||||||
className="bg-ctp-surface0 px-1.5 text-sm rounded-full"
|
key={tag.id}
|
||||||
>
|
className="bg-ctp-surface0 hover:bg-ctp-surface1 px-2 py-0.5 text-sm rounded-full transition-colors"
|
||||||
{tag.parentId && "..."}
|
>
|
||||||
{tag.name}
|
{tag.parentId && "..."}
|
||||||
</button>
|
{tag.name}
|
||||||
))}
|
</button>
|
||||||
</div>
|
))}
|
||||||
|
</div>*/}
|
||||||
|
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
key={editingNote?.id}
|
key={editingNote?.id}
|
||||||
content={editingNote?.content || ""}
|
content={editingNote?.content || ""}
|
||||||
onChange={setContent}
|
onChange={setContent}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusIndicator />
|
<StatusIndicator />
|
||||||
|
|
@ -176,16 +169,28 @@ const Modal = () => {
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
onClick={() => setShowModal(false)}
|
onClick={() => setShowModal(false)}
|
||||||
className="absolute h-screen w-screen flex items-center justify-center bg-ctp-crust/60 z-50"
|
className="fixed inset-0 h-screen w-screen flex items-center justify-center bg-ctp-crust/70 backdrop-blur-sm z-50"
|
||||||
>
|
>
|
||||||
<div
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||||
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||||
|
transition={{ type: "spring", duration: 0.3 }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
className="w-2/3 h-2/3 bg-ctp-base rounded-xl border-ctp-surface2 border p-5"
|
className="relative w-full max-w-md mx-4 bg-ctp-base rounded-xl border-ctp-surface2 border p-8 shadow-2xl"
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowModal(false)}
|
||||||
|
className="absolute top-4 right-4 p-2 hover:bg-ctp-surface0 rounded-sm transition-colors group"
|
||||||
|
aria-label="Close modal"
|
||||||
|
>
|
||||||
|
<XmarkIcon className="w-5 h-5 fill-ctp-overlay0 group-hover:fill-ctp-text transition-colors" />
|
||||||
|
</button>
|
||||||
<Login />
|
<Login />
|
||||||
{/*<TagSelector />*/}
|
{/*<TagSelector />*/}
|
||||||
</div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,29 @@ export const SidebarHeader = ({
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-2 border-b border-ctp-surface2 bg-ctp-mantle">
|
<div className="w-full p-2 border-b border-ctp-surface2 bg-ctp-mantle">
|
||||||
<div className="flex items-center justify-around bg-ctp-surface0 rounded-lg p-0.5">
|
<div className="flex items-center justify-around bg-ctp-surface0 rounded-lg p-1 gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setNewFolder(true)}
|
onClick={() => setNewFolder(true)}
|
||||||
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-1 m-1"
|
className="hover:bg-ctp-mauve active:scale-95 group transition-all duration-200 rounded-md p-2 hover:shadow-md"
|
||||||
title="New folder"
|
title="New folder"
|
||||||
>
|
>
|
||||||
<FolderPlusIcon className="w-5 h-5 group-hover:fill-ctp-base transition-colors fill-ctp-mauve" />
|
<FolderPlusIcon className="w-5 h-5 group-hover:fill-ctp-base transition-all duration-200 fill-ctp-mauve" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSideBarView(sideBarView == "tags" ? "folders" : "tags")
|
setSideBarView(sideBarView == "tags" ? "folders" : "tags")
|
||||||
}
|
}
|
||||||
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-1 m-1"
|
className={`${sideBarView === "tags" ? "bg-ctp-mauve/20" : ""} hover:bg-ctp-mauve active:scale-95 group transition-all duration-200 rounded-md p-2 hover:shadow-md`}
|
||||||
title="Tags"
|
title="Tags"
|
||||||
>
|
>
|
||||||
<TagsIcon className="w-5 h-5 group-hover:fill-ctp-base transition-colors fill-ctp-mauve" />
|
<TagsIcon className="w-5 h-5 group-hover:fill-ctp-base transition-all duration-200 fill-ctp-mauve" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-1 m-1 fill-ctp-mauve hover:fill-ctp-base"
|
className="hover:bg-ctp-mauve active:scale-95 group transition-all duration-200 rounded-md p-2 hover:shadow-md"
|
||||||
title="New note"
|
title="New note"
|
||||||
>
|
>
|
||||||
<FileCirclePlusIcon className="w-5 h-5 text-ctp-mauve group-hover:text-ctp-base transition-colors" />
|
<FileCirclePlusIcon className="w-5 h-5 group-hover:fill-ctp-base transition-all duration-200 fill-ctp-mauve" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -26,31 +26,68 @@ export const Login = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="gap-2 flex flex-col">
|
<form
|
||||||
<input
|
onSubmit={handleSubmit}
|
||||||
type="text"
|
className="gap-4 flex flex-col max-w-md mx-auto"
|
||||||
placeholder="Username"
|
>
|
||||||
className="standard-input"
|
<h2 className="text-2xl font-semibold text-ctp-text mb-2">
|
||||||
value={username}
|
Welcome Back
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
</h2>
|
||||||
/>
|
|
||||||
<input
|
<div className="flex flex-col gap-2">
|
||||||
type="password"
|
<label className="text-sm font-medium text-ctp-subtext0">
|
||||||
className="standard-input"
|
Username
|
||||||
placeholder="Password"
|
</label>
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
/>
|
|
||||||
{error && <div>{error}</div>}
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<input
|
<input
|
||||||
type="check box"
|
type="text"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
className="standard-input"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium text-ctp-subtext0">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="standard-input"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-ctp-red/10 border border-ctp-red text-ctp-red px-3 py-2 rounded-sm text-sm">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="remember"
|
||||||
checked={remember}
|
checked={remember}
|
||||||
onChange={(e) => setRemember(e.target.checked)}
|
onChange={(e) => setRemember(e.target.checked)}
|
||||||
|
className="accent-ctp-mauve cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<div>Remember me?</div>
|
<label
|
||||||
|
htmlFor="remember"
|
||||||
|
className="text-sm text-ctp-subtext0 cursor-pointer"
|
||||||
|
>
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="bg-ctp-mauve hover:bg-ctp-mauve/90 text-ctp-base font-semibold px-4 py-2.5 rounded-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ctp-mauve focus:ring-offset-2 focus:ring-offset-ctp-base"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -23,29 +23,63 @@ export const Register = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form
|
||||||
<input
|
onSubmit={handleSubmit}
|
||||||
type="text"
|
className="gap-4 flex flex-col max-w-md mx-auto"
|
||||||
placeholder="Username"
|
>
|
||||||
value={username}
|
<h2 className="text-2xl font-semibold text-ctp-text mb-2">
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
Create Account
|
||||||
/>
|
</h2>
|
||||||
<input
|
|
||||||
type="email"
|
<div className="flex flex-col gap-2">
|
||||||
placeholder="Email"
|
<label className="text-sm font-medium text-ctp-subtext0">
|
||||||
value={email}
|
Username
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
</label>
|
||||||
/>
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="password"
|
placeholder="Choose a username"
|
||||||
placeholder="Password"
|
className="standard-input"
|
||||||
value={password}
|
value={username}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{error && <div>{error}</div>}
|
</div>
|
||||||
<button type="submit">Login</button>
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium text-ctp-subtext0">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
className="standard-input"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-sm font-medium text-ctp-subtext0">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="standard-input"
|
||||||
|
placeholder="Create a password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="bg-ctp-red/10 border border-ctp-red text-ctp-red px-3 py-2 rounded-sm text-sm">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="bg-ctp-mauve hover:bg-ctp-mauve/90 text-ctp-base font-semibold px-4 py-2.5 rounded-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ctp-mauve focus:ring-offset-2 focus:ring-offset-ctp-base"
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Similar pattern for Register.tsx
|
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export const TiptapEditor = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tiptap-editor h-full">
|
<div className="tiptap-editor pt-0">
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
{/*<div className="editor-toolbar">
|
{/*<div className="editor-toolbar">
|
||||||
<div className="toolbar-group">
|
<div className="toolbar-group">
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror h3 {
|
.ProseMirror h3 {
|
||||||
@apply text-xl font-semibold text-ctp-teal mt-5 mb-2;
|
@apply text-xl font-semibold text-ctp-mauve mt-5 mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror code {
|
.ProseMirror code {
|
||||||
|
|
@ -149,11 +149,12 @@
|
||||||
.ProseMirror li[data-checked="true"] > div > p {
|
.ProseMirror li[data-checked="true"] > div > p {
|
||||||
@apply line-through text-ctp-overlay0;
|
@apply line-through text-ctp-overlay0;
|
||||||
text-decoration-style: wavy;
|
text-decoration-style: wavy;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror u {
|
.ProseMirror u {
|
||||||
@apply decoration-ctp-mauve;
|
@apply decoration-ctp-mauve;
|
||||||
text-decoration-style: wavy;
|
/*text-decoration-style: wavy;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.ProseMirror li::marker {
|
.ProseMirror li::marker {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { useNoteStore } from "./notesStore";
|
|
||||||
import {
|
import {
|
||||||
deriveKey,
|
deriveKey,
|
||||||
generateMasterKey,
|
generateMasterKey,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Note } from "@/api/notes";
|
import { Note, NoteRead } from "@/api/notes";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
|
@ -15,8 +15,8 @@ interface UIState {
|
||||||
sideBarView: string;
|
sideBarView: string;
|
||||||
setSideBarView: (view: string) => void;
|
setSideBarView: (view: string) => void;
|
||||||
|
|
||||||
selectedNote: Note | null;
|
selectedNote: NoteRead | null;
|
||||||
setSelectedNote: (note: Note | null) => void;
|
setSelectedNote: (note: NoteRead | null) => void;
|
||||||
|
|
||||||
selectedFolder: number | null;
|
selectedFolder: number | null;
|
||||||
setSelectedFolder: (id: number | null) => void;
|
setSelectedFolder: (id: number | null) => void;
|
||||||
|
|
@ -43,7 +43,7 @@ export const useUIStore = create<UIState>()(
|
||||||
},
|
},
|
||||||
selectedNote: null,
|
selectedNote: null,
|
||||||
|
|
||||||
setSelectedNote: (id: Note | null) => {
|
setSelectedNote: (id: NoteRead | null) => {
|
||||||
set({ selectedNote: id });
|
set({ selectedNote: id });
|
||||||
},
|
},
|
||||||
selectedFolder: null,
|
selectedFolder: null,
|
||||||
|
|
|
||||||
2
frontend/src/types/api.d.ts
vendored
2
frontend/src/types/api.d.ts
vendored
|
|
@ -548,7 +548,7 @@ export interface operations {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": unknown;
|
"application/json": components["schemas"]["NoteRead"][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue