Add Blocknote editor, Add theme switching

This commit is contained in:
2026-01-23 09:33:19 +00:00
parent 56a3105913
commit 946a2c5dbb
8 changed files with 3256 additions and 43 deletions

3185
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,9 @@
"vite": "^5.4.21"
},
"dependencies": {
"@blocknote/core": "^0.46.1",
"@blocknote/react": "^0.46.1",
"@blocknote/shadcn": "^0.46.1",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.8",

View File

@@ -2,16 +2,19 @@ import { NoteProvider } from "../contexts/ActiveNoteContext";
import { NotesStoreProvider } from "../contexts/NotesStore";
import NoteEditor from "./NoteEditor"
import AppLayout from "./AppLayout"
import { ThemeProvider } from "../contexts/ThemeContext";
function App() {
return (
<NotesStoreProvider>
<NoteProvider>
<AppLayout>
<NoteEditor />
</AppLayout>
</NoteProvider>
</NotesStoreProvider>
<ThemeProvider>
<NotesStoreProvider>
<NoteProvider>
<AppLayout>
<NoteEditor />
</AppLayout>
</NoteProvider>
</NotesStoreProvider>
</ThemeProvider>
)
}

View File

@@ -9,30 +9,21 @@ import ListItem from "./ListItem"
import { useNote } from "../contexts/ActiveNoteContext"
import { useNotesStore } from "../contexts/NotesStore"
import { useState } from "react"
import { useTheme } from "../contexts/ThemeContext"
function AppSidebar() {
const { notes, createNote } = useNotesStore()
const { setCurrentNote } = useNote()
const [isDark, setIsDark] = useState(
() => document.documentElement.classList.contains("dark")
)
const toggleTheme = () => {
const html = document.documentElement
html.classList.toggle("dark")
setIsDark(html.classList.contains("dark"))
}
const { theme, toggleTheme } = useTheme()
return (
<Sidebar>
<SidebarHeader className="justify-between px-4 py-3 border-b-1 gap-3.5">
<div className="flex items-center justify-between">
<span className="font-medium">Notes</span>
<Button variant="ghost" size="sm" onClick={toggleTheme} className="text-muted-foreground">
{isDark ? <Sun /> : <Moon />}
{theme === "dark" ? <Sun /> : <Moon />}
</Button>
</div>
<Input placeholder="Search Notes" className="shadow-none bg-background" />

View File

@@ -1,7 +1,17 @@
import "@blocknote/core/fonts/inter.css";
import "@blocknote/shadcn/style.css";
import { BlockNoteView } from "@blocknote/shadcn";
import { useCreateBlockNote } from "@blocknote/react"
import { useNote } from "../contexts/ActiveNoteContext"
import { useTheme } from "../contexts/ThemeContext";
function NoteEditor() {
const { currentNote, setCurrentNote } = useNote();
const { currentNote } = useNote();
const { theme } = useTheme();
const editor = useCreateBlockNote({
initialContent: currentNote?.body
});
if (!currentNote) return (
<div className="p-6">
@@ -12,7 +22,7 @@ function NoteEditor() {
return (
<div className="p-6">
<h1>{ currentNote.title }</h1>
<p>{ currentNote.body }</p>
<BlockNoteView editor={editor as any} theme={theme} />
</div>
)
}

View File

@@ -0,0 +1,37 @@
import { createContext, useContext, useState } from "react";
type Theme = "light" | "dark";
const ThemeContext = createContext<{
theme: Theme;
toggleTheme: () => void;
} | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() =>
document.documentElement.classList.contains("dark")
? "dark"
: "light"
);
const toggleTheme = () => {
const html = document.documentElement;
html.classList.toggle("dark");
setTheme(
html.classList.contains("dark") ? "dark" : "light"
);
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}

View File

@@ -1,12 +1,13 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@source "../node_modules/@blocknote/shadcn";
body {
font-family: "Fira Sans", sans-serif, monospace !important;
}
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
@@ -115,6 +116,14 @@ body {
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
/* Blocknote tweaks */
--bn-colors-editor-background: #0a0a0a !important;
--bn-colors-block-background: #0a0a0a !important;
.bn-add-file-button {
background-color: var(--primary-foreground) !important;
}
}
@layer base {

View File

@@ -1,12 +1,23 @@
import { PartialBlock } from "@blocknote/core";
export class Note {
id: string;
title: string;
body: string;
body: PartialBlock<any, any, any>[];
constructor(data: Partial<Note> = {}) {
this.id = data.id ?? this.randomNumber();
this.title = data.title ?? "Untitled Note";
this.body = data.body ?? "";
this.body = data.body ?? this.defaultBody();
}
private defaultBody(): any {
return [
{
type: "paragraph",
content: ""
}
]
}
// TODO: replace with UUID