// ============================================================ // EDITOR — Rich text editor with preview, cover, tags, schedule // ============================================================ function Editor({ id, onDone }) { const [doc, setDoc] = useState(null); const [categories, setCategories] = useState([]); const [saving, setSaving] = useState(false); const [toast, setToast] = useState(''); const [previewOn, setPreviewOn] = useState(false); const bodyRef = useRef(null); const bodyInitialized = useRef(false); useEffect(() => { api.listCategories().then(setCategories); if (id) { api.getArticle(id).then(a => setDoc(a || newDoc())); } else { setDoc(newDoc()); } }, [id]); // Inject html into contenteditable once on load (avoid React reconciling on every keystroke) useEffect(() => { if (doc && bodyRef.current && !bodyInitialized.current) { bodyRef.current.innerHTML = doc.body || ''; bodyInitialized.current = true; } }, [doc]); if (!doc) return
Carregando…
; const set = (patch) => setDoc(d => ({ ...d, ...patch })); const onBodyInput = () => { setDoc(d => ({ ...d, body: bodyRef.current.innerHTML })); }; const save = async (status) => { setSaving(true); const body = bodyRef.current ? bodyRef.current.innerHTML : doc.body; const payload = { ...doc, body, status, slug: doc.slug || slugify(doc.title) || api.uid(), author: doc.author || 'Dr. Angelo Santos', }; if (status === 'published' && !payload.publishedAt) payload.publishedAt = new Date().toISOString(); if (status === 'scheduled' && doc.scheduledFor) payload.publishedAt = doc.scheduledFor; if (status === 'draft') {} // leave publishedAt as-is const saved = await api.upsertArticle(payload); setSaving(false); setToast(status === 'published' ? 'Artigo publicado.' : status === 'scheduled' ? 'Artigo agendado.' : 'Rascunho salvo.'); setDoc(saved); if (!id) setTimeout(() => onDone && onDone(), 600); }; return (
← Voltar à redação

{id ? 'Editar artigo' : 'Novo artigo'}

{doc.status === 'published' ? 'Publicado' : doc.status === 'scheduled' ? 'Agendado' : 'Rascunho'} {readingTime(doc.body)} min de leitura · {(doc.body || '').replace(/<[^>]+>/g, ' ').split(/\s+/).filter(Boolean).length} palavras

{doc.status === 'scheduled' || (doc.scheduledFor && doc.status !== 'published') ? ( ) : null}
set({ title: e.target.value, slug: doc.slug || slugify(e.target.value) })} />