Added theming and fix css.

Added unparsed view
This commit is contained in:
Jamitz 2026-01-15 22:26:08 +00:00
parent e0a8e503b9
commit eb52756c6d
24 changed files with 515 additions and 855 deletions

2
.gitignore vendored
View file

@ -23,3 +23,5 @@ frontend/src/assets/fontawesome/svg/0.svg
!square-check.svg !square-check.svg
!code-simple.svg !code-simple.svg
!quote-left.svg !quote-left.svg
!gear.svg
frontend/dist/*

View file

@ -1,7 +1,9 @@
import os import os
from dotenv import load_dotenv
from sqlmodel import Session, SQLModel, create_engine # type: ignore from sqlmodel import Session, SQLModel, create_engine # type: ignore
load_dotenv()
# Get database URL from environment, with proper fallback # Get database URL from environment, with proper fallback
DATABASE_URL = os.getenv("DATABASE_URL") DATABASE_URL = os.getenv("DATABASE_URL")

View file

@ -8,8 +8,7 @@ from app.routes import auth, folders, notes, tags
app = FastAPI(title="Notes API") app = FastAPI(title="Notes API")
# CORS - configure via environment variable cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:5173").split(",")
cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:80").split(",")
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=cors_origins, allow_origins=cors_origins,

View file

@ -1,13 +1,12 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
from sqlmodel import Session, SQLModel, select
from app.auth import create_session, hash_password, require_auth, verify_password from app.auth import create_session, hash_password, require_auth, verify_password
from app.database import get_session from app.database import get_session
from app.models import Session as SessionModel from app.models import Session as SessionModel
from app.models import User from app.models import User
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
from sqlmodel import Session, SQLModel, select
router = APIRouter(prefix="/auth", tags=["auth"]) router = APIRouter(prefix="/auth", tags=["auth"])
@ -30,8 +29,8 @@ class UserResponse(SQLModel):
id: int id: int
username: str username: str
email: str email: str
salt: str # Client needs this for key derivation salt: str
wrapped_master_key: str # Client needs this to unwrap the master key wrapped_master_key: str
@router.post("/register") @router.post("/register")
@ -72,7 +71,7 @@ def register(
key="session_id", key="session_id",
value=session_id, value=session_id,
httponly=True, httponly=True,
secure=True, # HTTPS only in production secure=True,
samesite="lax", samesite="lax",
max_age=30 * 24 * 60 * 60, # 30 days max_age=30 * 24 * 60 * 60, # 30 days
) )
@ -147,15 +146,15 @@ def list_sessions(
return {"sessions": sessions} return {"sessions": sessions}
@router.delete("/sessions/{session_token}") # Renamed from session_id @router.delete("/sessions/{session_token}")
def revoke_session( def revoke_session(
session_token: str, # Renamed to avoid conflict with Cookie parameter session_token: str,
current_user: User = Depends(require_auth), current_user: User = Depends(require_auth),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
session = db.exec( session = db.exec(
select(SessionModel) select(SessionModel)
.where(SessionModel.session_id == session_token) # Use renamed variable .where(SessionModel.session_id == session_token)
.where(SessionModel.user_id == current_user.id) .where(SessionModel.user_id == current_user.id)
).first() ).first()

File diff suppressed because it is too large Load diff

View file

@ -50,7 +50,7 @@
"openapi-typescript": "^7.10.1", "openapi-typescript": "^7.10.1",
"type-fest": "^5.3.1", "type-fest": "^5.3.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^5.4.21", "vite": "^7.3.1",
"vite-plugin-svgr": "^4.5.0", "vite-plugin-svgr": "^4.5.0",
"vitest": "^4.0.15" "vitest": "^4.0.15"
} }

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M59.9 186.6l26.2 24.9c24 22.8 24 66.2 0 89L59.9 325.4c8.5 24 21.5 46.4 38 65.7l34.6-10.2c31.8-9.4 69.4 12.3 77.2 44.6l8.5 35.1c24.6 4.5 51.3 4.5 75.9 0l8.5-35.1c7.8-32.3 45.3-53.9 77.2-44.6l34.6 10.2c16.5-19.3 29.5-41.7 38-65.7l-26.2-24.9c-24-22.8-24-66.2 0-89l26.2-24.9c-8.5-24-21.5-46.4-38-65.7l-34.6 10.2c-31.8 9.4-69.4-12.3-77.2-44.6l-8.5-35.1c-24.6-4.5-51.3-4.5-75.9 0l-8.5 35.1c-7.8 32.3-45.3 53.9-77.2 44.6L97.9 120.9c-16.5 19.3-29.5 41.7-38 65.7zM352 256a96 96 0 1 1 -192 0 96 96 0 1 1 192 0z"/><path class="fa-primary" d="M256 0c17 0 33.6 1.7 49.8 4.8c7.9 1.5 21.8 6.1 29.4 20.1c2 3.7 3.6 7.6 4.6 11.8l9.3 38.5C350.5 81 360.3 86.7 366 85l38-11.2c4-1.2 8.1-1.8 12.2-1.9c16.1-.5 27 9.4 32.3 15.4c22.1 25.1 39.1 54.6 49.9 86.3c2.6 7.6 5.6 21.8-2.7 35.4c-2.2 3.6-4.9 7-8 10L459 246.3c-4.2 4-4.2 15.5 0 19.5l28.7 27.3c3.1 3 5.8 6.4 8 10c8.2 13.6 5.2 27.8 2.7 35.4c-10.8 31.7-27.8 61.1-49.9 86.3c-5.3 6-16.3 15.9-32.3 15.4c-4.1-.1-8.2-.8-12.2-1.9L366 427c-5.7-1.7-15.5 4-16.9 9.8l-9.3 38.5c-1 4.2-2.6 8.2-4.6 11.8c-7.7 14-21.6 18.5-29.4 20.1C289.6 510.3 273 512 256 512s-33.6-1.7-49.8-4.8c-7.9-1.5-21.8-6.1-29.4-20.1c-2-3.7-3.6-7.6-4.6-11.8l-9.3-38.5c-1.4-5.8-11.2-11.5-16.9-9.8l-38 11.2c-4 1.2-8.1 1.8-12.2 1.9c-16.1 .5-27-9.4-32.3-15.4c-22-25.1-39.1-54.6-49.9-86.3c-2.6-7.6-5.6-21.8 2.7-35.4c2.2-3.6 4.9-7 8-10L53 265.7c4.2-4 4.2-15.5 0-19.5L24.2 218.9c-3.1-3-5.8-6.4-8-10C8 195.3 11 181.1 13.6 173.6c10.8-31.7 27.8-61.1 49.9-86.3c5.3-6 16.3-15.9 32.3-15.4c4.1 .1 8.2 .8 12.2 1.9L146 85c5.7 1.7 15.5-4 16.9-9.8l9.3-38.5c1-4.2 2.6-8.2 4.6-11.8c7.7-14 21.6-18.5 29.4-20.1C222.4 1.7 239 0 256 0zM218.1 51.4l-8.5 35.1c-7.8 32.3-45.3 53.9-77.2 44.6L97.9 120.9c-16.5 19.3-29.5 41.7-38 65.7l26.2 24.9c24 22.8 24 66.2 0 89L59.9 325.4c8.5 24 21.5 46.4 38 65.7l34.6-10.2c31.8-9.4 69.4 12.3 77.2 44.6l8.5 35.1c24.6 4.5 51.3 4.5 75.9 0l8.5-35.1c7.8-32.3 45.3-53.9 77.2-44.6l34.6 10.2c16.5-19.3 29.5-41.7 38-65.7l-26.2-24.9c-24-22.8-24-66.2 0-89l26.2-24.9c-8.5-24-21.5-46.4-38-65.7l-34.6 10.2c-31.8 9.4-69.4-12.3-77.2-44.6l-8.5-35.1c-24.6-4.5-51.3-4.5-75.9 0zM208 256a48 48 0 1 0 96 0 48 48 0 1 0 -96 0zm48 96a96 96 0 1 1 0-192 96 96 0 1 1 0 192z"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -57,7 +57,7 @@ export const FolderContextMenu: React.FC<FolderContextMenuProps> = ({
try { try {
await createFolderMutation.mutateAsync({ await createFolderMutation.mutateAsync({
name: "New Folder", name: "New Folder",
parent_id: folder.id, parentId: folder.id,
}); });
onClose(); onClose();
} catch (error) { } catch (error) {
@ -73,7 +73,7 @@ export const FolderContextMenu: React.FC<FolderContextMenuProps> = ({
top: y, top: y,
left: x, left: x,
}} }}
className="bg-ctp-surface0 border border-ctp-surface2 rounded-md shadow-lg p-2 min-w-[200px] z-50" className="bg-overlay0 border border-surface1 rounded-md shadow-lg p-2 min-w-[200px] z-50"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<input <input
@ -89,7 +89,7 @@ export const FolderContextMenu: React.FC<FolderContextMenuProps> = ({
}} }}
onBlur={handleRename} onBlur={handleRename}
autoFocus autoFocus
className="w-full px-2 py-1 bg-ctp-surface1 border border-ctp-surface2 rounded text-sm text-ctp-text focus:outline-none focus:border-ctp-mauve" className="w-full px-2 py-1 bg-surface1 border border-surface1 rounded text-sm text-text focus:outline-none focus:border-accent"
/> />
</div> </div>
); );
@ -102,25 +102,25 @@ export const FolderContextMenu: React.FC<FolderContextMenuProps> = ({
top: y, top: y,
left: x, left: x,
}} }}
className="bg-ctp-surface0 border border-ctp-surface2 rounded-md shadow-lg py-1 min-w-[160px] z-50" className="bg-overlay0 border border-surface1 rounded-md shadow-lg py-1 min-w-[160px] z-50"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<button <button
onClick={() => setIsRenaming(true)} onClick={() => setIsRenaming(true)}
className="w-full text-left px-3 py-1.5 hover:bg-ctp-surface1 text-sm text-ctp-text transition-colors" className="w-full text-left px-3 py-1.5 hover:bg-surface1 text-sm text-text transition-colors"
> >
Rename Rename
</button> </button>
<button <button
onClick={handleCreateSubfolder} onClick={handleCreateSubfolder}
className="w-full text-left px-3 py-1.5 hover:bg-ctp-surface1 text-sm text-ctp-text transition-colors" className="w-full text-left px-3 py-1.5 hover:bg-surface1 text-sm text-text transition-colors"
> >
New Subfolder New Subfolder
</button> </button>
<div className="border-t border-ctp-surface2 my-1" /> <div className="border-t border-surface1 my-1" />
<button <button
onClick={handleDelete} onClick={handleDelete}
className="w-full text-left px-3 py-1.5 hover:bg-ctp-red hover:text-ctp-base text-sm text-ctp-red transition-colors" className="w-full text-left px-3 py-1.5 hover:bg-danger hover:text-base text-sm text-danger transition-colors"
> >
Delete Delete
</button> </button>

View file

@ -36,7 +36,7 @@ export const NoteContextMenu: React.FC<NoteContextMenuProps> = ({
await createNoteMutation.mutateAsync({ await createNoteMutation.mutateAsync({
title: `${note.title} (Copy)`, title: `${note.title} (Copy)`,
content: note.content, content: note.content,
folder_id: note.folder_id || null, folderId: note.folderId || null,
}); });
onClose(); onClose();
} catch (error) { } catch (error) {
@ -57,25 +57,25 @@ export const NoteContextMenu: React.FC<NoteContextMenuProps> = ({
top: y, top: y,
left: x, left: x,
}} }}
className="bg-ctp-surface0 border border-ctp-surface2 rounded-md shadow-lg py-1 min-w-[160px] z-50" className="bg-overlay0 border border-surface1 rounded-md shadow-lg py-1 min-w-[160px] z-50"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<button <button
onClick={handleRename} onClick={handleRename}
className="w-full text-left px-3 py-1.5 hover:bg-ctp-surface1 text-sm text-ctp-text transition-colors" className="w-full text-left px-3 py-1.5 hover:bg-surface1 text-sm text-text transition-colors"
> >
Rename Rename
</button> </button>
<button <button
onClick={handleDuplicate} onClick={handleDuplicate}
className="w-full text-left px-3 py-1.5 hover:bg-ctp-surface1 text-sm text-ctp-text transition-colors" className="w-full text-left px-3 py-1.5 hover:bg-surface1 text-sm text-text transition-colors"
> >
Duplicate Duplicate
</button> </button>
<div className="border-t border-ctp-surface2 my-1" /> <div className="border-t border-surface1 my-1" />
<button <button
onClick={handleDelete} onClick={handleDelete}
className="w-full text-left px-3 py-1.5 hover:bg-ctp-red hover:text-ctp-base text-sm text-ctp-red transition-colors" className="w-full text-left px-3 py-1.5 hover:bg-danger hover:text-base text-sm text-danger transition-colors"
> >
Delete Delete
</button> </button>

View file

@ -105,7 +105,7 @@ export const ContextMenuProvider = ({
e.preventDefault(); e.preventDefault();
closeContextMenu(); closeContextMenu();
}} }}
className=" h-screen w-screen bg-ctp-crust/25 z-40 fixed top-0 left-0" className=" h-screen w-screen bg-surface1/25 z-40 fixed top-0 left-0"
></div> ></div>
)} )}
{children} {children}

View file

@ -2,44 +2,28 @@
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
@import "@catppuccin/tailwindcss/macchiato.css"; @import "@catppuccin/tailwindcss/macchiato.css";
@theme {
--color-base:
;
}
@theme {
/* Map Tailwind classes to CSS variables */
--color-ctp-base: var(--color-ctp-base);
--color-ctp-mantle: var(--color-ctp-mantle);
--color-ctp-crust: var(--color-ctp-crust);
--color-ctp-text: var(--color-ctp-text);
--color-ctp-subtext0: #a5adcb;
--color-ctp-overlay0: #6e738d;
--color-ctp-mauve: var(--color-ctp-mauve);
--color-ctp-blue: var(--color-ctp-blue);
--color-ctp-green: #a6da95;
--color-ctp-red: #ed8796;
--color-ctp-yellow: #eed49f;
--color-ctp-teal: #8bd5ca;
--color-ctp-sapphire: #7dc4e4;
--color-ctp-peach: #f5a97f;
/* Surface colors */
--color-ctp-surface0: #363a4f;
--color-ctp-surface1: #494d64;
--color-ctp-surface2: #5b6078;
}
/* Default values (Macchiato) - injected by JS, but good as fallback */
:root { :root {
--color-ctp-base: #24273a; --black: 15, 18, 25;
--color-ctp-mantle: #1e2030; --gray: 96, 115, 159;
--color-ctp-crust: #181926; --gray-light: 229, 233, 240;
--color-ctp-text: #cad3f5; --gray-dark: 34, 41, 57;
--color-ctp-mauve: #c6a0f6; --box-shadow:
--color-ctp-blue: #8aadf4; 0 2px 6px rgba(30, 32, 48, 0.4), 0 8px 24px rgba(30, 32, 48, 0.5),
0 16px 32px rgba(30, 32, 48, 0.6);
}
@theme {
--color-base: #24273a;
--color-surface0: #1e2030;
--color-surface1: #181926;
--color-overlay0: #363a4f;
--color-overlay1: #494d64;
--color-text: #cad3f5;
--color-subtext: #b8c0e0;
--color-accent: #e2a16f;
--color-danger: #e26f6f;
--color-success: #6fe29b;
--color-warn: #e2c56f;
} }
/* Override MDXEditor and all its children */ /* Override MDXEditor and all its children */
@ -47,19 +31,19 @@
._mdxeditor-root-content-editable, ._mdxeditor-root-content-editable,
.mdxeditor-root-contenteditable, .mdxeditor-root-contenteditable,
div[contenteditable="true"] { div[contenteditable="true"] {
color: var(--color-ctp-text) !important; color: var(--color-text) !important;
} }
._listItemChecked_1tncs_73::before { ._listItemChecked_1tncs_73::before {
--accentSolid: var(--color-ctp-mauve) !important; --accentSolid: var(--color-accent) !important;
border-color: var(--color-ctp-mauve-900) !important; border-color: var(--color-accent) !important;
border: 2px; border: 2px;
} }
._listItemChecked_1tncs_73::after { ._listItemChecked_1tncs_73::after {
border-color: var(--color-ctp-mauve-900) !important; border-color: var(--color-accent) !important;
} }
.standard-input { .standard-input {
@apply border border-ctp-mauve rounded-sm px-2 py-1 w-full focus:outline-none focus:ring-2 focus:ring-ctp-mauve bg-ctp-base text-ctp-text placeholder:text-ctp-overlay0; @apply border border-overlay0 rounded-sm px-2 py-1 w-full focus:outline-none focus:ring-2 focus:ring-accent bg-base text-text placeholder:text-overlay1;
} }

View file

@ -1,4 +1,10 @@
import { useEffect, useRef, useState } from "react"; import {
ChangeEvent,
ChangeEventHandler,
useEffect,
useRef,
useState,
} from "react";
import "../../main.css"; import "../../main.css";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { useAuthStore } from "@/stores/authStore"; import { useAuthStore } from "@/stores/authStore";
@ -26,7 +32,7 @@ function Home() {
} | null>(null); } | null>(null);
const { encryptionKey } = useAuthStore(); const { encryptionKey } = useAuthStore();
const { showModal, setUpdating, selectedNote } = useUIStore(); const { showModal, setUpdating, selectedNote, editorView } = useUIStore();
const newFolderRef = useRef<HTMLInputElement>(null); const newFolderRef = useRef<HTMLInputElement>(null);
const updateNoteMutation = useUpdateNote(); const updateNoteMutation = useUpdateNote();
@ -115,33 +121,60 @@ function Home() {
} }
}; };
const setUnparsedContent = (event: ChangeEvent<HTMLTextAreaElement>) => {
if (editingNote) {
setEditingNote({ ...editingNote, content: event.target.value });
}
};
return ( return (
<div className="flex bg-ctp-base h-screen text-ctp-text overflow-hidden"> <div className="flex bg-base h-screen text-text overflow-hidden">
{/* Sidebar */} {/* Sidebar */}
<AnimatePresence>{showModal && <Modal />}</AnimatePresence> <AnimatePresence>{showModal && <Modal />}</AnimatePresence>
<Sidebar /> <Sidebar />
{/* Main editor area */} {/* Main editor area */}
<div className="flex flex-col w-full h-screen overflow-y-auto items-center justify-center"> <div className="flex flex-col w-full h-screen overflow-hidden">
{/*<Editor />*/} {" "}
<div className="h-full lg:w-3xl w-full"> {editingNote ? (
<>
<input <input
type="text" type="text"
id="noteTitle" id="noteTitle"
name=""
placeholder="Untitled note..." placeholder="Untitled note..."
value={editingNote?.title || ""} value={editingNote.title || ""}
onChange={(e) => setTitle(e.target.value)} 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" className="w-full self-center p-4 pb-2 pt-2 text-3xl font-semibold focus:outline-none border-transparent focus:border-accent transition-colors placeholder:text-overlay0 text-text bg-surface1"
/> />
<div className="h-full lg:w-3xl w-full mx-auto overflow-y-hidden">
{" "}
{editorView == "parsed" ? (
<TiptapEditor <TiptapEditor
key={editingNote?.id} key={editingNote.id}
content={editingNote?.content || ""} content={editingNote.content || ""}
onChange={setContent} onChange={setContent}
/> />
) : (
<textarea
value={editingNote.content || ""}
className="w-full font-mono p-4 bg-transparent focus:outline-none resize-none text-text"
style={{
minHeight: "calc(100vh - 55px)",
}}
onChange={setUnparsedContent}
/>
)}
</div> </div>
</>
) : (
<div className="flex items-center justify-center h-full text-overlay0">
<div className="text-center">
<PlusIcon className="w-16 h-16 mx-auto mb-4 fill-current opacity-50" />
<p className="text-lg">Select a note or create a new one</p>
</div>
</div>
)}
</div> </div>
<StatusIndicator /> <StatusIndicator />
@ -152,14 +185,16 @@ function Home() {
export default Home; export default Home;
const Modal = () => { const Modal = () => {
const { setShowModal } = useUIStore(); const { setShowModal, modalContent, showModal } = useUIStore();
const ModalContent = modalContent;
if (!showModal || !ModalContent) return null;
return ( return (
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
onClick={() => setShowModal(false)} onClick={() => setShowModal(false)}
className="fixed inset-0 h-screen w-screen flex items-center justify-center bg-ctp-crust/70 backdrop-blur-sm z-50" className="fixed inset-0 h-screen w-screen flex items-center justify-center bg-crust/70 backdrop-blur-sm z-50"
> >
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
@ -167,16 +202,17 @@ const Modal = () => {
exit={{ opacity: 0, scale: 0.95, y: 20 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ type: "spring", duration: 0.3 }} transition={{ type: "spring", duration: 0.3 }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="relative w-full max-w-md mx-4 bg-ctp-base rounded-xl border-ctp-surface2 border p-8 shadow-2xl" className="relative w-full max-w-md mx-4 bg-base rounded-xl border-surface1 border p-8 shadow-2xl"
> >
<button <button
onClick={() => setShowModal(false)} onClick={() => setShowModal(false)}
className="absolute top-4 right-4 p-2 hover:bg-ctp-surface0 rounded-sm transition-colors group" className="absolute top-4 right-4 p-2 hover:bg-surface0 rounded-sm transition-colors group"
aria-label="Close modal" aria-label="Close modal"
> >
<XmarkIcon className="w-5 h-5 fill-ctp-overlay0 group-hover:fill-ctp-text transition-colors" /> <XmarkIcon className="w-5 h-5 fill-overlay0 group-hover:fill-text transition-colors" />
</button> </button>
<Login /> <ModalContent />
{/*<Login />*/}
</motion.div> </motion.div>
</motion.div> </motion.div>
); );

View file

@ -6,32 +6,43 @@ import CheckIcon from "../../../assets/fontawesome/svg/circle-check.svg?react";
import SpinnerIcon from "../../../assets/fontawesome/svg/rotate.svg?react"; import SpinnerIcon from "../../../assets/fontawesome/svg/rotate.svg?react";
// @ts-ignore // @ts-ignore
import WarningIcon from "../../../assets/fontawesome/svg/circle-exclamation.svg?react"; import WarningIcon from "../../../assets/fontawesome/svg/circle-exclamation.svg?react";
import { Login } from "@/pages/Login";
export const StatusIndicator = () => { export const StatusIndicator = () => {
const { encryptionKey } = useAuthStore(); const { encryptionKey } = useAuthStore();
const { updating, setShowModal } = useUIStore(); const { updating, setShowModal, editorView, setEditorView, setModalContent } =
useUIStore();
return ( return (
<div <div
className="fixed bottom-2 right-3 bg-ctp-surface0 border border-ctp-surface2 rounded-sm px-2 py-0.5 flex items-center gap-2.5 shadow-lg backdrop-blur-sm" className="fixed bottom-2 right-3 bg-surface0 border border-surface1 rounded-sm px-2 py-0.5 flex items-center gap-2.5 shadow-lg backdrop-blur-sm"
onClick={() => { onClick={() => {
if (!encryptionKey) { if (!encryptionKey) {
setModalContent(Login);
setShowModal(true); setShowModal(true);
} }
}} }}
> >
<div
className="select-none"
onClick={() =>
setEditorView(editorView == "parsed" ? "unparsed" : "parsed")
}
>
{editorView}
</div>
{!encryptionKey ? ( {!encryptionKey ? (
<WarningIcon className="h-4 w-4 my-1 [&_.fa-primary]:fill-ctp-yellow [&_.fa-secondary]:fill-ctp-orange" /> <WarningIcon className="h-4 w-4 my-1 [&_.fa-primary]:fill-warn [&_.fa-secondary]:fill-orange" />
) : updating ? ( ) : updating ? (
<> <>
<SpinnerIcon className="animate-spin h-4 w-4 [&_.fa-primary]:fill-ctp-blue [&_.fa-secondary]:fill-ctp-sapphire" /> <SpinnerIcon className="animate-spin h-4 w-4 [&_.fa-primary]:fill-warn [&_.fa-secondary]:fill-sapphire" />
{/*<span className="text-sm text-ctp-subtext0 font-medium"> {/*<span className="text-sm text-subtext font-medium">
Saving... Saving...
</span>*/} </span>*/}
</> </>
) : ( ) : (
<> <>
<CheckIcon className="h-4 w-4 [&_.fa-primary]:fill-ctp-green [&_.fa-secondary]:fill-ctp-teal" /> <CheckIcon className="h-4 w-4 [&_.fa-primary]:fill-success [&_.fa-secondary]:fill-teal" />
{/*<span className="text-sm text-ctp-subtext0 font-medium">Saved</span>*/} {/*<span className="text-sm text-subtext font-medium">Saved</span>*/}
</> </>
)} )}
</div> </div>

View file

@ -41,7 +41,7 @@ export const Sidebar = () => {
const { encryptionKey } = useAuthStore(); const { encryptionKey } = useAuthStore();
const { setSideBarResize, sideBarResize } = useUIStore(); const { setSideBarResize, sideBarResize, setColourScheme } = useUIStore();
useEffect(() => { useEffect(() => {
if (newFolder && newFolderRef.current) { if (newFolder && newFolderRef.current) {
newFolderRef.current.focus(); newFolderRef.current.focus();
@ -163,7 +163,7 @@ export const Sidebar = () => {
> >
<div className="flex-row-reverse flex h-screen"> <div className="flex-row-reverse flex h-screen">
<div <div
className="h-full bg-ctp-surface0 w-0.5 hover:cursor-ew-resize hover:bg-ctp-mauve transition-colors" className="h-full bg-surface1 w-0.5 hover:cursor-ew-resize hover:bg-accent/50 transition-colors"
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
></div> ></div>
<div <div
@ -171,7 +171,7 @@ export const Sidebar = () => {
style={{ width: `${sideBarResize}px` }} style={{ width: `${sideBarResize}px` }}
> >
<SidebarHeader setNewFolder={setNewFolder} /> <SidebarHeader setNewFolder={setNewFolder} />
<div className="flex-1 overflow-y-auto bg-ctp-mantle border-r border-ctp-surface2"> <div className="flex-1 overflow-y-auto bg-surface1 border-r border-surface1">
<> <>
<div <div
className="w-full p-4 sm:block hidden" className="w-full p-4 sm:block hidden"
@ -203,14 +203,14 @@ export const Sidebar = () => {
{/* Loading state */} {/* Loading state */}
{isLoading && ( {isLoading && (
<div className="flex items-center justify-center py-8 text-ctp-subtext0"> <div className="flex items-center justify-center py-8 text-subtext0">
<div className="text-sm">Loading folders...</div> <div className="text-sm">Loading folders...</div>
</div> </div>
)} )}
{/* Error state */} {/* Error state */}
{error && ( {error && (
<div className="flex items-center justify-center py-8 text-ctp-red"> <div className="flex items-center justify-center py-8 text-danger">
<div className="text-sm">Failed to load folders</div> <div className="text-sm">Failed to load folders</div>
</div> </div>
)} )}
@ -236,16 +236,19 @@ export const Sidebar = () => {
</> </>
)} )}
</div> </div>
{/*<div className="fixed bottom-1 left-2">
<button onClick={setColour}>purple</button>
</div>*/}
<DragOverlay> <DragOverlay>
{activeItem?.type === "note" && ( {activeItem?.type === "note" && (
<div className="bg-ctp-surface0 rounded-md px-2 py-1 shadow-lg border border-ctp-mauve"> <div className="bg-surface0 rounded-md px-2 py-1 shadow-lg border border-accent">
{activeItem.data.title} {activeItem.data.title}
</div> </div>
)} )}
{activeItem?.type === "folder" && ( {activeItem?.type === "folder" && (
<div className="bg-ctp-surface0 rounded-md px-1 py-0.5 shadow-lg flex items-center gap-1 text-sm"> <div className="bg-surface0 rounded-md px-1 py-0.5 shadow-lg flex items-center gap-1 text-sm">
<FolderIcon className="w-3 h-3 fill-ctp-mauve mr-1" /> <FolderIcon className="w-3 h-3 fill-accent mr-1" />
{activeItem.data.name} {activeItem.data.name}
</div> </div>
)} )}

View file

@ -39,8 +39,8 @@ export const DraggableNote = ({ note }: { note: NoteRead }) => {
}} }}
className={` rounded-sm px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${ className={` rounded-sm 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-accent text-base"
: "hover:bg-ctp-surface1" : "hover:bg-surface1"
}`} }`}
> >
<span className="truncate"> <span className="truncate">

View file

@ -65,10 +65,10 @@ export const DroppableFolder = ({
> >
{(folder.notes?.length ?? 0) > 0 && ( {(folder.notes?.length ?? 0) > 0 && (
<CaretRightIcon <CaretRightIcon
className={`w-4 h-4 min-h-4 min-w-4 mr-1 transition-all duration-200 ease-in-out ${collapse ? "rotate-90" : ""} fill-ctp-mauve`} className={`w-4 h-4 min-h-4 min-w-4 mr-1 transition-all duration-200 ease-in-out ${collapse ? "rotate-90" : ""} fill-accent`}
/> />
)} )}
<FolderIcon className="w-4 h-4 min-h-4 min-w-4 fill-ctp-mauve mr-1" /> <FolderIcon className="w-4 h-4 min-h-4 min-w-4 fill-accent mr-1" />
<span className="truncate">{folder.name}</span> <span className="truncate">{folder.name}</span>
</div> </div>
</div> </div>

View file

@ -29,7 +29,7 @@ export const FolderTree = ({ folder, depth = 0 }: FolderTreeProps) => {
className="overflow-hidden flex flex-col" className="overflow-hidden flex flex-col"
> >
{/* The line container */} {/* The line container */}
<div className="ml-2 pl-3 border-l border-ctp-surface2"> <div className="ml-2 pl-3 border-l border-surface1">
{/* Notes */} {/* Notes */}
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
{folder.notes.map((note) => ( {folder.notes.map((note) => (

View file

@ -5,16 +5,46 @@ import FolderPlusIcon from "@assets/fontawesome/svg/folder-plus.svg?react";
import TagsIcon from "@assets/fontawesome/svg/tags.svg?react"; import TagsIcon from "@assets/fontawesome/svg/tags.svg?react";
// @ts-ignore // @ts-ignore
import FileCirclePlusIcon from "@assets/fontawesome/svg/file-circle-plus.svg?react"; import FileCirclePlusIcon from "@assets/fontawesome/svg/file-circle-plus.svg?react";
// @ts-ignore
import GearIcon from "@assets/fontawesome/svg/gear.svg?react";
import { useUIStore } from "@/stores/uiStore"; import { useUIStore } from "@/stores/uiStore";
import { useCreateNote } from "@/hooks/useFolders"; import { useCreateNote } from "@/hooks/useFolders";
import { NoteCreate } from "@/api/notes"; import { NoteCreate } from "@/api/notes";
import { Login } from "@/pages/Login";
import { ColourState } from "@/stores/uiStore";
const Test = () => {
const { colourScheme, setColourScheme } = useUIStore();
const handleColor = (key: string, value: string) => {
setColourScheme({
...colourScheme,
[key]: value,
});
};
return (
<>
{Object.entries(colourScheme).map(([key, value]) => (
<div key={key}>
<label>{key}</label>
<input
type="color"
value={value}
onChange={(e) => handleColor(key, e.target.value)}
/>
</div>
))}
</>
);
};
export const SidebarHeader = ({ export const SidebarHeader = ({
setNewFolder, setNewFolder,
}: { }: {
setNewFolder: React.Dispatch<SetStateAction<boolean>>; setNewFolder: React.Dispatch<SetStateAction<boolean>>;
}) => { }) => {
const { selectedFolder } = useUIStore(); const { selectedFolder, setShowModal, setModalContent } = useUIStore();
const createNote = useCreateNote(); const createNote = useCreateNote();
const handleCreate = async () => { const handleCreate = async () => {
createNote.mutate({ createNote.mutate({
@ -23,22 +53,34 @@ export const SidebarHeader = ({
folder_id: selectedFolder, folder_id: selectedFolder,
} as NoteCreate); } as NoteCreate);
}; };
const handleSettings = () => {
setModalContent(Test);
setShowModal(true);
};
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-surface1 bg-surface1">
<div className="flex items-center justify-around bg-ctp-surface0 rounded-lg p-1 gap-1"> <div className="flex items-center justify-around bg-surface0 rounded-lg p-1 gap-1">
<button <button
onClick={() => setNewFolder(true)} onClick={() => setNewFolder(true)}
className="hover:bg-ctp-mauve active:scale-95 group transition-all duration-200 rounded-md p-2 hover:shadow-md" className="hover:bg-accent 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-all duration-200 fill-ctp-mauve" /> <FolderPlusIcon className="w-5 h-5 group-hover:fill-base transition-all duration-200 fill-accent" />
</button> </button>
<button <button
onClick={handleCreate} onClick={handleCreate}
className="hover:bg-ctp-mauve active:scale-95 group transition-all duration-200 rounded-md p-2 hover:shadow-md" className="hover:bg-accent 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 group-hover:fill-ctp-base transition-all duration-200 fill-ctp-mauve" /> <FileCirclePlusIcon className="w-5 h-5 group-hover:fill-base transition-all duration-200 fill-accent" />
</button>
<button
onClick={handleSettings}
className="hover:bg-accent active:scale-95 group transition-all duration-200 rounded-md p-2 hover:shadow-md"
title="New note"
>
<GearIcon className="w-5 h-5 group-hover:fill-base transition-all duration-200 fill-accent" />
</button> </button>
</div> </div>
</div> </div>

View file

@ -30,14 +30,10 @@ export const Login = () => {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="gap-4 flex flex-col max-w-md mx-auto" className="gap-4 flex flex-col max-w-md mx-auto"
> >
<h2 className="text-2xl font-semibold text-ctp-text mb-2"> <h2 className="text-2xl font-semibold text-text mb-2">Welcome Back</h2>
Welcome Back
</h2>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="text-sm font-medium text-ctp-subtext0"> <label className="text-sm font-medium text-subtext">Username</label>
Username
</label>
<input <input
type="text" type="text"
placeholder="Enter your username" placeholder="Enter your username"
@ -48,9 +44,7 @@ export const Login = () => {
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="text-sm font-medium text-ctp-subtext0"> <label className="text-sm font-medium text-subtext">Password</label>
Password
</label>
<input <input
type="password" type="password"
className="standard-input" className="standard-input"
@ -61,7 +55,7 @@ export const Login = () => {
</div> </div>
{error && ( {error && (
<div className="bg-ctp-red/10 border border-ctp-red text-ctp-red px-3 py-2 rounded-sm text-sm"> <div className="bg-danger/10 border border-danger text-danger px-3 py-2 rounded-sm text-sm">
{error} {error}
</div> </div>
)} )}
@ -72,11 +66,11 @@ export const Login = () => {
id="remember" id="remember"
checked={remember} checked={remember}
onChange={(e) => setRemember(e.target.checked)} onChange={(e) => setRemember(e.target.checked)}
className="accent-ctp-mauve cursor-pointer" className="accent-accent cursor-pointer"
/> />
<label <label
htmlFor="remember" htmlFor="remember"
className="text-sm text-ctp-subtext0 cursor-pointer" className="text-sm text-subtext cursor-pointer"
> >
Remember me Remember me
</label> </label>
@ -84,7 +78,7 @@ export const Login = () => {
<button <button
type="submit" 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" className="bg-accent hover:bg-accent/90 text-base font-semibold px-4 py-2.5 rounded-sm transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base"
> >
Login Login
</button> </button>

View file

@ -27,14 +27,10 @@ export const Register = () => {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="gap-4 flex flex-col max-w-md mx-auto" className="gap-4 flex flex-col max-w-md mx-auto"
> >
<h2 className="text-2xl font-semibold text-ctp-text mb-2"> <h2 className="text-2xl font-semibold text-text mb-2">Create Account</h2>
Create Account
</h2>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="text-sm font-medium text-ctp-subtext0"> <label className="text-sm font-medium text-subtext">Username</label>
Username
</label>
<input <input
type="text" type="text"
placeholder="Choose a username" placeholder="Choose a username"
@ -45,7 +41,7 @@ export const Register = () => {
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="text-sm font-medium text-ctp-subtext0">Email</label> <label className="text-sm font-medium text-subtext">Email</label>
<input <input
type="email" type="email"
placeholder="Enter your email" placeholder="Enter your email"
@ -56,9 +52,7 @@ export const Register = () => {
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="text-sm font-medium text-ctp-subtext0"> <label className="text-sm font-medium text-subtext">Password</label>
Password
</label>
<input <input
type="password" type="password"
className="standard-input" className="standard-input"
@ -69,14 +63,14 @@ export const Register = () => {
</div> </div>
{error && ( {error && (
<div className="bg-ctp-red/10 border border-ctp-red text-ctp-red px-3 py-2 rounded-sm text-sm"> <div className="bg-danger/10 border border-danger text-danger px-3 py-2 rounded-sm text-sm">
{error} {error}
</div> </div>
)} )}
<button <button
type="submit" 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" className="bg-accent hover:bg-accent/90 text-base font-semibold px-4 py-2.5 rounded-sm transition-colors focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base"
> >
Register Register
</button> </button>

View file

@ -1,6 +1,6 @@
export const Test = () => { export const Test = () => {
return ( return (
<div className="h-screen w-screen flex items-center justify-center bg-ctp-base p-4"> <div className="h-screen w-screen flex items-center justify-center bg-base p-4">
<input <input
type="text" type="text"
placeholder="Folder name..." placeholder="Folder name..."

View file

@ -79,7 +79,12 @@ export const TiptapEditor = ({
} }
return ( return (
<div className="tiptap-editor pt-0!"> <div
className="tiptap-editor pt-0! overflow-y-scroll"
style={{
minHeight: "calc(100vh - 55px)",
}}
>
{/* Toolbar */} {/* Toolbar */}
{/*<div className="editor-toolbar"> {/*<div className="editor-toolbar">
<div className="toolbar-group"> <div className="toolbar-group">
@ -88,28 +93,28 @@ export const TiptapEditor = ({
className={editor.isActive("bold") ? "active" : ""} className={editor.isActive("bold") ? "active" : ""}
title="Bold (Ctrl+B)" title="Bold (Ctrl+B)"
> >
<BoldIcon className="w-4 h-4 fill-ctp-text" /> <BoldIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive("italic") ? "active" : ""} className={editor.isActive("italic") ? "active" : ""}
title="Italic (Ctrl+I)" title="Italic (Ctrl+I)"
> >
<ItalicIcon className="w-4 h-4 fill-ctp-text" /> <ItalicIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleStrike().run()} onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive("strike") ? "active" : ""} className={editor.isActive("strike") ? "active" : ""}
title="Strikethrough" title="Strikethrough"
> >
<StrikethroughIcon className="w-4 h-4 fill-ctp-text" /> <StrikethroughIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCode().run()} onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive("code") ? "active" : ""} className={editor.isActive("code") ? "active" : ""}
title="Inline code" title="Inline code"
> >
<CodeIcon className="w-4 h-4 fill-ctp-text" /> <CodeIcon className="w-4 h-4 fill-text" />
</button> </button>
</div> </div>
@ -153,35 +158,35 @@ export const TiptapEditor = ({
className={editor.isActive("bulletList") ? "active" : ""} className={editor.isActive("bulletList") ? "active" : ""}
title="Bullet list" title="Bullet list"
> >
<ListUlIcon className="w-4 h-4 fill-ctp-text" /> <ListUlIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive("orderedList") ? "active" : ""} className={editor.isActive("orderedList") ? "active" : ""}
title="Numbered list" title="Numbered list"
> >
<ListOlIcon className="w-4 h-4 fill-ctp-text" /> <ListOlIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleTaskList().run()} onClick={() => editor.chain().focus().toggleTaskList().run()}
className={editor.isActive("taskList") ? "active" : ""} className={editor.isActive("taskList") ? "active" : ""}
title="Task list" title="Task list"
> >
<SquareCheckIcon className="w-4 h-4 fill-ctp-text" /> <SquareCheckIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive("codeBlock") ? "active" : ""} className={editor.isActive("codeBlock") ? "active" : ""}
title="Code block" title="Code block"
> >
<CodeBracketIcon className="w-4 h-4 fill-ctp-text" /> <CodeBracketIcon className="w-4 h-4 fill-text" />
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive("blockquote") ? "active" : ""} className={editor.isActive("blockquote") ? "active" : ""}
title="Quote" title="Quote"
> >
<QuoteLeftIcon className="w-4 h-4 fill-ctp-text" /> <QuoteLeftIcon className="w-4 h-4 fill-text" />
</button> </button>
</div> </div>

View file

@ -7,33 +7,37 @@
} }
*::-webkit-scrollbar-track { *::-webkit-scrollbar-track {
@apply bg-ctp-mantle rounded-full; @apply bg-surface0 rounded-full;
} }
*::-webkit-scrollbar-thumb { *::-webkit-scrollbar-thumb {
@apply bg-ctp-surface2 rounded-full; @apply bg-surface1 rounded-full;
} }
*::-webkit-scrollbar-thumb:hover { *::-webkit-scrollbar-thumb:hover {
@apply bg-ctp-overlay0; @apply bg-overlay0;
} }
/* Firefox scrollbar */ /* Firefox scrollbar */
* { * {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--color-ctp-surface2) var(--color-ctp-mantle); scrollbar-color: var(--color-surface1) var(--color-surface0);
} }
.tiptap-editor { .tiptap-editor {
@apply flex flex-col h-full bg-ctp-base; @apply flex flex-col h-full bg-base;
} }
.ProseMirror { .ProseMirror {
@apply text-ctp-text; @apply text-text;
} }
.editor-toolbar { .editor-toolbar {
@apply flex gap-2 px-4 bg-ctp-mantle border-b border-ctp-surface2 flex-wrap items-center; @apply flex gap-2 px-4 bg-surface0 border-b border-surface1 flex-wrap items-center;
}
.editor-content {
@apply h-full;
} }
.toolbar-group { .toolbar-group {
@ -41,19 +45,19 @@
} }
.toolbar-divider { .toolbar-divider {
@apply w-px h-6 bg-ctp-surface2; @apply w-px h-6 bg-surface1;
} }
.editor-toolbar button { .editor-toolbar button {
@apply p-2 bg-transparent border-none rounded-sm text-ctp-text cursor-pointer transition-all duration-150 text-sm font-semibold min-w-8 flex items-center justify-center; @apply p-2 bg-transparent border-none rounded-sm text-text cursor-pointer transition-all duration-150 text-sm font-semibold min-w-8 flex items-center justify-center;
} }
.editor-toolbar button:hover:not(:disabled) { .editor-toolbar button:hover:not(:disabled) {
@apply bg-ctp-surface0; @apply bg-surface0;
} }
.editor-toolbar button.active { .editor-toolbar button.active {
@apply bg-ctp-mauve text-ctp-base; @apply bg-accent text-base;
} }
.editor-toolbar button:disabled { .editor-toolbar button:disabled {
@ -66,59 +70,59 @@
.ProseMirror p.is-editor-empty:first-child::before { .ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder); content: attr(data-placeholder);
@apply float-left text-ctp-overlay0 pointer-events-none h-0; @apply float-left text-overlay0 pointer-events-none h-0;
} }
.ProseMirror ul { .ProseMirror ul {
@apply mb-0!; @apply mb-0!;
} }
.ProseMirror h1 { .ProseMirror h1 {
@apply text-3xl font-bold text-ctp-mauve mt-8 mb-4; @apply text-3xl font-bold mt-6 mb-4 text-accent;
} }
.ProseMirror h2 { .ProseMirror h2 {
@apply text-2xl font-semibold text-ctp-blue mt-6 mb-3; @apply text-2xl font-semibold text-accent mt-4 mb-3;
} }
.ProseMirror h3 { .ProseMirror h3 {
@apply text-xl font-semibold text-ctp-mauve mt-5 mb-2; @apply text-xl font-semibold text-accent mt-5 mb-2;
} }
.ProseMirror code { .ProseMirror code {
@apply bg-ctp-surface0 text-ctp-peach px-1.5 py-0.5 rounded text-sm; @apply bg-surface0 text-accent px-1.5 py-0.5 rounded text-sm;
font-family: "JetBrains Mono", "Fira Code", monospace; font-family: "JetBrains Mono", "Fira Code", monospace;
} }
.ProseMirror .code-block { .ProseMirror .code-block {
@apply bg-ctp-surface0 border border-ctp-surface2 rounded-sm p-4 my-4 overflow-x-auto; @apply bg-surface0 border border-surface1 rounded-sm p-4 my-4 overflow-x-auto;
font-family: "JetBrains Mono", "Fira Code", monospace; font-family: "JetBrains Mono", "Fira Code", monospace;
} }
.ProseMirror .code-block code { .ProseMirror .code-block code {
@apply bg-transparent p-0 text-ctp-text; @apply bg-transparent p-0 text-text;
} }
.ProseMirror blockquote { .ProseMirror blockquote {
@apply border-l-4 border-ctp-mauve pl-4 ml-0 text-ctp-subtext0 italic; @apply border-l-4 border-accent pl-4 ml-0 text-subtext italic;
} }
.ProseMirror hr { .ProseMirror hr {
@apply border-none border-t-2 border-ctp-surface2 my-8; @apply border-none border-t-2 border-surface1 my-8;
} }
.ProseMirror a { .ProseMirror a {
@apply text-ctp-blue underline; @apply text-accent underline;
} }
.ProseMirror a:hover { .ProseMirror a:hover {
@apply text-ctp-sapphire; @apply text-accent;
} }
.ProseMirror strong { .ProseMirror strong {
@apply text-ctp-peach font-semibold; @apply text-accent font-semibold;
} }
.ProseMirror em { .ProseMirror em {
@apply text-ctp-yellow; @apply text-accent;
} }
/* Task List (Checkboxes) */ /* Task List (Checkboxes) */
@ -135,7 +139,7 @@
} }
.ProseMirror ul[data-type="taskList"] > li > label input[type="checkbox"] { .ProseMirror ul[data-type="taskList"] > li > label input[type="checkbox"] {
@apply cursor-pointer m-0 accent-ctp-mauve; @apply cursor-pointer m-0 accent-accent;
} }
.ProseMirror ul[data-type="taskList"] > li > div { .ProseMirror ul[data-type="taskList"] > li > div {
@ -147,18 +151,18 @@
} }
.ProseMirror li[data-checked="true"] > div > p { .ProseMirror li[data-checked="true"] > div > p {
@apply line-through text-ctp-overlay0; @apply line-through text-text/40;
text-decoration-style: wavy; text-decoration-style: wavy;
text-decoration-thickness: 1px; text-decoration-thickness: 1px;
} }
.ProseMirror u { .ProseMirror u {
@apply decoration-ctp-mauve; @apply decoration-accent;
/*text-decoration-style: wavy;*/ /*text-decoration-style: wavy;*/
} }
.ProseMirror li::marker { .ProseMirror li::marker {
@apply text-ctp-mauve; @apply text-accent;
} }
/* tiptap.css */ /* tiptap.css */
@ -172,3 +176,19 @@
margin-top: 0 !important; margin-top: 0 !important;
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
hr {
border: none;
height: 3px;
background: repeating-linear-gradient(
90deg,
var(--color-accent) 0px,
var(--color-accent) 8px,
var(--color-accent) 16px
);
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 6'%3E%3Cpath d='M0 3 Q 3 0, 6 3 T 12 3 T 18 3 T 24 3' stroke='black' stroke-width='2' fill='none'/%3E%3C/svg%3E")
repeat-x;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 6'%3E%3Cpath d='M0 3 Q 3 0, 6 3 T 12 3 T 18 3 T 24 3' stroke='black' stroke-width='2' fill='none'/%3E%3C/svg%3E")
repeat-x;
margin: 2em 0;
}

View file

@ -1,6 +1,27 @@
import { Note, NoteRead } 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";
import { Login } from "@/pages/Login";
interface HSL {
H: Number;
S: Number;
L: Number;
}
export interface ColourState {
base: string;
surface0: string;
surface1: string;
overlay0: string;
overlay1: string;
text: string;
subtext: string;
accent: string;
warn: string;
success: string;
danger: string;
}
interface UIState { interface UIState {
updating: boolean; updating: boolean;
@ -9,17 +30,26 @@ interface UIState {
showModal: boolean; showModal: boolean;
setShowModal: (show: boolean) => void; setShowModal: (show: boolean) => void;
modalContent: React.ComponentType | null;
setModalContent: (content: React.ComponentType) => void;
sideBarResize: number; sideBarResize: number;
setSideBarResize: (size: number) => void; setSideBarResize: (size: number) => void;
sideBarView: string; sideBarView: string;
setSideBarView: (view: string) => void; setSideBarView: (view: string) => void;
editorView: string;
setEditorView: (view: string) => void;
selectedNote: NoteRead | null; selectedNote: NoteRead | null;
setSelectedNote: (note: NoteRead | null) => void; setSelectedNote: (note: NoteRead | null) => void;
selectedFolder: number | null; selectedFolder: number | null;
setSelectedFolder: (id: number | null) => void; setSelectedFolder: (id: number | null) => void;
colourScheme: ColourState;
setColourScheme: (colors: ColourState) => void;
} }
export const useUIStore = create<UIState>()( export const useUIStore = create<UIState>()(
@ -33,6 +63,10 @@ export const useUIStore = create<UIState>()(
setShowModal: (show) => { setShowModal: (show) => {
set({ showModal: show }); set({ showModal: show });
}, },
modalContent: null,
setModalContent: (content) => {
set({ modalContent: content });
},
sideBarResize: 300, sideBarResize: 300,
setSideBarResize: (size) => { setSideBarResize: (size) => {
set({ sideBarResize: size }); set({ sideBarResize: size });
@ -41,6 +75,10 @@ export const useUIStore = create<UIState>()(
setSideBarView: (view) => { setSideBarView: (view) => {
set({ sideBarView: view }); set({ sideBarView: view });
}, },
editorView: "parsed",
setEditorView: (view) => {
set({ editorView: view });
},
selectedNote: null, selectedNote: null,
setSelectedNote: (id: NoteRead | null) => { setSelectedNote: (id: NoteRead | null) => {
@ -51,6 +89,28 @@ export const useUIStore = create<UIState>()(
setSelectedFolder: (id: number | null) => { setSelectedFolder: (id: number | null) => {
set({ selectedFolder: id }); set({ selectedFolder: id });
}, },
colourScheme: {
base: "#24273a",
surface0: "#1e2030",
surface1: "#181926",
overlay0: "#363a4f",
overlay1: "#494d64",
text: "#cad3f5",
subtext: "#b8c0e0",
accent: "#e2a16f",
danger: "#e26f6f",
success: "#6fe29b",
warn: "#e2c56f",
},
setColourScheme: (colors: ColourState) => {
set({ colourScheme: colors });
Object.entries(colors).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--color-${key}`, value);
});
},
}), }),
{ {