feat: editor enriquecido en notas de Seguimiento

Barra tipo Docs (negrita, color, resaltado, listas, enlaces, tamaño) al crear notas.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 17:35:42 +02:00
parent 9f30aa6820
commit dd50251de2
3 changed files with 301 additions and 12 deletions

View File

@@ -14,6 +14,7 @@ import {
Send,
} from 'lucide-react';
import { PageHeader } from '@/components/ui';
import NoteRichEditor, { richTextHasContent } from '@/components/NoteRichEditor';
type Entry = {
id: string;
@@ -49,7 +50,9 @@ function typeTone(t: string) {
}
function looksLikeHtml(s: string) {
return /<\/?(?:ul|ol|li|p|a|strong|br)\b/i.test(s);
return /<\/?(?:ul|ol|li|p|a|strong|b|em|i|u|br|span|font|div|h[1-6])\b/i.test(
s
);
}
function escapeHtml(s: string) {
@@ -104,7 +107,7 @@ export default function SeguimientoBoard() {
}, [data, query]);
async function saveNote() {
if (!noteBody.trim()) return;
if (!richTextHasContent(noteBody)) return;
setNoteBusy(true);
setNoteMsg(null);
try {
@@ -113,7 +116,7 @@ export default function SeguimientoBoard() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: noteTitle.trim() || 'Nota de seguimiento',
body: noteBody.trim(),
body: noteBody,
occurredAt: new Date().toISOString(),
}),
});
@@ -209,8 +212,8 @@ export default function SeguimientoBoard() {
Nueva nota
</div>
<p className="text-xs text-gray-500">
Se guarda con fecha. El histórico también incluye entradas
sincronizadas desde Superhuman Docs (docs de seguimiento).
Formato enriquecido (negrita, color, resaltado, listas, enlaces). El
histórico también incluye entradas de Superhuman / Google Doc.
</p>
<input
value={noteTitle}
@@ -218,16 +221,16 @@ export default function SeguimientoBoard() {
placeholder="Título (opcional)"
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900"
/>
<textarea
<NoteRichEditor
value={noteBody}
onChange={(e) => setNoteBody(e.target.value)}
onChange={setNoteBody}
placeholder="Comentarios, hipótesis, bloqueos, siguientes pasos…"
className="w-full min-h-[120px] rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm text-gray-900"
minHeight="160px"
/>
<div className="flex flex-wrap items-center gap-3">
<button
type="button"
disabled={noteBusy || !noteBody.trim()}
disabled={noteBusy || !richTextHasContent(noteBody)}
onClick={() => void saveNote()}
className="inline-flex items-center gap-2 rounded-xl bg-brand-500 text-white px-4 py-2 text-sm font-semibold disabled:opacity-50"
>
@@ -332,7 +335,7 @@ export default function SeguimientoBoard() {
</div>
{open && e.body_md && (
<div
className="mt-3 text-sm text-gray-800 bg-gray-50 border border-gray-100 rounded-lg p-4 max-h-[36rem] overflow-y-auto [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:my-1.5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:my-0.5 [&_a]:text-brand-600 [&_a]:underline [&_strong]:font-semibold [&_p]:my-1.5"
className="mt-3 text-sm text-gray-800 bg-gray-50 border border-gray-100 rounded-lg p-4 max-h-[36rem] overflow-y-auto [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:my-1.5 [&_ol]:list-decimal [&_ol]:pl-5 [&_li]:my-0.5 [&_a]:text-brand-600 [&_a]:underline [&_strong]:font-semibold [&_b]:font-semibold [&_em]:italic [&_i]:italic [&_u]:underline [&_p]:my-1.5"
onClick={(ev) => {
const t = ev.target as HTMLElement;
if (t.closest('a')) ev.stopPropagation();