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" "vite": "^5.4.21"
}, },
"dependencies": { "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-dialog": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-separator": "^1.1.8",

View File

@@ -2,9 +2,11 @@ import { NoteProvider } from "../contexts/ActiveNoteContext";
import { NotesStoreProvider } from "../contexts/NotesStore"; import { NotesStoreProvider } from "../contexts/NotesStore";
import NoteEditor from "./NoteEditor" import NoteEditor from "./NoteEditor"
import AppLayout from "./AppLayout" import AppLayout from "./AppLayout"
import { ThemeProvider } from "../contexts/ThemeContext";
function App() { function App() {
return ( return (
<ThemeProvider>
<NotesStoreProvider> <NotesStoreProvider>
<NoteProvider> <NoteProvider>
<AppLayout> <AppLayout>
@@ -12,6 +14,7 @@ function App() {
</AppLayout> </AppLayout>
</NoteProvider> </NoteProvider>
</NotesStoreProvider> </NotesStoreProvider>
</ThemeProvider>
) )
} }

View File

@@ -9,30 +9,21 @@ import ListItem from "./ListItem"
import { useNote } from "../contexts/ActiveNoteContext" import { useNote } from "../contexts/ActiveNoteContext"
import { useNotesStore } from "../contexts/NotesStore" import { useNotesStore } from "../contexts/NotesStore"
import { useState } from "react" import { useTheme } from "../contexts/ThemeContext"
function AppSidebar() { function AppSidebar() {
const { notes, createNote } = useNotesStore() const { notes, createNote } = useNotesStore()
const { setCurrentNote } = useNote() const { setCurrentNote } = useNote()
const [isDark, setIsDark] = useState( const { theme, toggleTheme } = useTheme()
() => document.documentElement.classList.contains("dark")
)
const toggleTheme = () => {
const html = document.documentElement
html.classList.toggle("dark")
setIsDark(html.classList.contains("dark"))
}
return ( return (
<Sidebar> <Sidebar>
<SidebarHeader className="justify-between px-4 py-3 border-b-1 gap-3.5"> <SidebarHeader className="justify-between px-4 py-3 border-b-1 gap-3.5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="font-medium">Notes</span> <span className="font-medium">Notes</span>
<Button variant="ghost" size="sm" onClick={toggleTheme} className="text-muted-foreground"> <Button variant="ghost" size="sm" onClick={toggleTheme} className="text-muted-foreground">
{isDark ? <Sun /> : <Moon />} {theme === "dark" ? <Sun /> : <Moon />}
</Button> </Button>
</div> </div>
<Input placeholder="Search Notes" className="shadow-none bg-background" /> <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 { useNote } from "../contexts/ActiveNoteContext"
import { useTheme } from "../contexts/ThemeContext";
function NoteEditor() { function NoteEditor() {
const { currentNote, setCurrentNote } = useNote(); const { currentNote } = useNote();
const { theme } = useTheme();
const editor = useCreateBlockNote({
initialContent: currentNote?.body
});
if (!currentNote) return ( if (!currentNote) return (
<div className="p-6"> <div className="p-6">
@@ -12,7 +22,7 @@ function NoteEditor() {
return ( return (
<div className="p-6"> <div className="p-6">
<h1>{ currentNote.title }</h1> <h1>{ currentNote.title }</h1>
<p>{ currentNote.body }</p> <BlockNoteView editor={editor as any} theme={theme} />
</div> </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 "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@source "../node_modules/@blocknote/shadcn";
@custom-variant dark (&:is(.dark *));
body { body {
font-family: "Fira Sans", sans-serif, monospace !important; font-family: "Fira Sans", sans-serif, monospace !important;
} }
@custom-variant dark (&:is(.dark *));
@theme inline { @theme inline {
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
@@ -115,6 +116,14 @@ body {
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0); --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 { @layer base {

View File

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