- 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 to- openProjectCardModal
- 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 edit- shortDescription
- 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 to- localStorage
- loadContent()loads from- localStoragefirst; 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