Added theming and fix css.
Added unparsed view
This commit is contained in:
parent
e0a8e503b9
commit
eb52756c6d
24 changed files with 515 additions and 855 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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/*
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
842
frontend/package-lock.json
generated
842
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
frontend/src/assets/fontawesome/svg/gear.svg
Normal file
1
frontend/src/assets/fontawesome/svg/gear.svg
Normal 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 |
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
<>
|
||||||
type="text"
|
<input
|
||||||
id="noteTitle"
|
type="text"
|
||||||
name=""
|
id="noteTitle"
|
||||||
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">
|
||||||
<TiptapEditor
|
{" "}
|
||||||
key={editingNote?.id}
|
{editorView == "parsed" ? (
|
||||||
content={editingNote?.content || ""}
|
<TiptapEditor
|
||||||
onChange={setContent}
|
key={editingNote.id}
|
||||||
/>
|
content={editingNote.content || ""}
|
||||||
</div>
|
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 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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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..."
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue