- Custom Project Card block: Insert via slash menu and edit via a global modal
- Local image upload: Pick an image from your device; the image is embedded as base64
- Short description editing: Rich text using BlockNote in the modal
- Content persistence: Saves to an API endpoint with localStorage fallback and autosave
- Export to PDF: Capture the editor content and export as multipage PDF
- Next.js 15, React 18
- BlockNote (
@blocknote/core,@blocknote/react,@blocknote/mantine) - Tailwind CSS
html2canvas+jspdffor PDF export
src/app/blocknote-portfolio/components/BlockNoteEditor.tsx- Main editor shell, Save button, Export to PDF, and global Project Card modal
src/app/blocknote-portfolio/components/ProjectCardBlock.tsx- Custom BlockNote block spec for a Project Card
src/app/blocknote-portfolio/components/GlobalProjectCardModal.tsx- Listens for a custom DOM event to open the modal for the selected card
src/app/blocknote-portfolio/components/ProjectCardModal.tsx- Modal UI with local image upload and BlockNote mini-editor for short description
src/app/blocknote-portfolio/hooks/useContentManager.ts- Persistence: serialize/deserialize, save/load via API + localStorage, autosave
src/app/api/save-content/route.ts- Demo API route; logs saved content and returns stubbed responses
- Install dependencies
pnpm install- Run the dev server
pnpm dev- Open the editor
- Go to
/blocknote-portfolio - Use the slash menu (
/) and select "Project Card" to insert a card
- In the editor, type
/project cardor use the slash menu to insert
- Click the card in the editor to open the modal
- In the modal:
- Project Image: Use the file input to upload from your computer. The image is embedded as base64 and will persist with the document
- Project Title: Update text field
- Short Description: Rich text powered by BlockNote inside the modal
- Click "Save Project Card" to apply changes back to the block
- Click the "Save Content" button at the top of the editor
- Autosave runs every 30 seconds by default
- Persistence behavior:
- Attempts to save to
/api/save-content(POST) - On failure, falls back to
localStorageunder keyblocknote-content-portfolio-content
- Attempts to save to
- Click "Export to PDF" in the editor header
- The visible editor area is captured with
html2canvasand exported viajspdf - Long documents paginate across multiple PDF pages
- Defined in
ProjectCardBlock.tsxusingcreateBlockSpec - Props:
title,imageUrl,shortDescription - Renders a card with image, title, and a preview of the first paragraph from the short description
- Preview derived from the stored BlockNote blocks
- Clicking the card dispatches a custom event
openProjectCardModalwithblockandeditor
GlobalProjectCardModal.tsxsubscribes toopenProjectCardModal- When fired, it opens
ProjectCardModalwith the block’s current props - On save, it updates the block via
editor.updateBlock(blockId, { props: newProps })
ProjectCardModal.tsxhosts a mini BlockNote editor to editshortDescription- Local image upload reads the file as base64 (via
FileReader) and setsimageUrl - On submit, the modal passes updated props back to the editor and closes
- Serializes
editor.documentto JSON for saving saveContent()tries API first; on failure, writes tolocalStorageloadContent()loads fromlocalStoragefirst; falls back to API- Autosave: interval (default 30s) triggers
saveContent() - State exposed via
saveStatefor UI (loading/success/error/lastSaved)
POSTaccepts{ content: string, id?: string }- Demo implementation logs and returns success
GETaccepts?id=...- Demo implementation returns
{ content: null }(no DB wired yet)
- Demo implementation returns
- Tailwind is pre-configured in
tailwind.config.jsandglobals.css - The editor schema (with custom
projectCard) is created inBlockNoteEditor.tsx - Autosave interval and content id can be changed via
useContentManageroptions
- Change autosave interval: update
autoSaveIntervalinBlockNoteEditor.tsx - Disable autosave: set
autoSave: falseinuseContentManageroptions - Persist to a real database: replace logic in
src/app/api/save-content/route.tsand inuseContentManager.saveToAPI
- "Image not showing": ensure uploaded file is a supported image and not too large; base64 embedding increases document size
- "Export to PDF cuts content": PDF export uses a canvas snapshot; very long or wide content may need margin/scale tweaks
- "Edits don’t save": check browser console for API errors; verify localStorage isn’t disabled and the key
blocknote-content-portfolio-contentexists
pnpm dev # Start Next.js in development
pnpm build # Build for production
pnpm start # Start production server
pnpm lint # Run ESLint
pnpm type-check# TypeScript type checkMIT