- React + TipTap editor with formatting toolbar (bold, italic, underline, strikethrough, alignment, highlight, scene breaks) - Custom image node view with resize and alignment controls; server-side WebP conversion via sharp - Express + SQLite backend with JWT auth and admin user management - Export to PDF, EPUB, and ODT - Five themes (Midnight, Gothic Night, Enchanted Forest, Aged Manuscript, Neon Noir); Lora body font for readability - Writing streak, daily word goal, milestones, and Ollama writing prompts - Docker Compose setup for self-hosted deployment behind NPMplus Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
27 lines
768 B
JavaScript
27 lines
768 B
JavaScript
import { createContext, useContext, useState, useCallback } from 'react'
|
|
|
|
const Ctx = createContext(null)
|
|
|
|
export function ToastProvider({ children }) {
|
|
const [toasts, setToasts] = useState([])
|
|
|
|
const addToast = useCallback((message, type = 'info') => {
|
|
const id = Date.now() + Math.random()
|
|
setToasts(p => [...p, { id, message, type }])
|
|
setTimeout(() => setToasts(p => p.filter(t => t.id !== id)), 4000)
|
|
}, [])
|
|
|
|
return (
|
|
<Ctx.Provider value={addToast}>
|
|
{children}
|
|
<div className="toast-container" aria-live="polite">
|
|
{toasts.map(t => (
|
|
<div key={t.id} className={`toast toast--${t.type}`}>{t.message}</div>
|
|
))}
|
|
</div>
|
|
</Ctx.Provider>
|
|
)
|
|
}
|
|
|
|
export function useToast() { return useContext(Ctx) }
|