Saltar al contenido
EspañolEnglish

Editor de texto enriquecido

RichTextEditor es el editor de texto de @amset/ui, hecho aquí y sin dependencias de terceros. Vive en una subruta, @amset/ui/rich-text, así que quien no lo usa no carga ni un byte. Guarda el documento como JSON, lo exporta a HTML, Markdown y Word, lo pinta en el servidor con RichTextView y deja meter dentro del texto los propios componentes de la biblioteca.

Pruébalo aquí

No son capturas: es el editor de la biblioteca funcionando en esta página. Escribe, pega, pulsa «/» y mira qué se guarda.

El editor y lo que guarda

Escribe, pega algo de Word o de una web, o pulsa «/» al principio de una línea para insertar una tabla, un cuadro destacado o un desplegable. Debajo, el mismo documento en cada formato, al momento.

Editor de texto enriquecido. Alt+F10 lleva a la barra de herramientas.
Código
<h2>Notas de la reunión</h2><p>Escribe aquí con <strong>negrita</strong>, <em>cursiva</em> o un <a href="https://amset.tech">enlace</a>. Prueba también <code>/</code> para insertar bloques.</p><ul><li><p>Revisar el presupuesto</p></li><li><p>Enviar la propuesta al cliente</p></li></ul><table><tbody><tr><th><p>Tarea</p></th><th><p>Quién</p></th></tr><tr><td><p>Diseño</p></td><td><p>Ana</p></td></tr><tr><td><p>Desarrollo</p></td><td><p>Luis</p></td></tr></tbody></table>

Con los componentes de la biblioteca dentro

La tarjeta y la rejilla llevan texto del documento dentro: escribe en ellas. Escribe «/» y el nombre de un componente (Insignia, Progreso, Alerta…) para insertarlo; al poner el cursor en uno, la barra enseña sus propiedades. La gráfica toma sus cifras de los datos de esta página y el botón llama a una acción de la página.

Editor de texto enriquecido. Alt+F10 lleva a la barra de herramientas.

Así lo ve quien lo lee

Resumen del trimestre

Antes de empezar

Los datos se actualizan cada noche. Escribe aquí dentro como en cualquier otro sitio.

Pedidos

342

12 pendientes

Facturado

18.420 €

este mes

Pulsa el botón del informe: dentro del editor no hace nada, en la vista llama a la acción de la página.

El mismo documento como código React

Código
import { BarChart, Button, Card, Grid, Stat } from '@amset/ui' export function Report({ actions, data }: { actions: Record<string, () => void>; data: Record<string, any> }) {  return (    <>      <h2>Resumen del trimestre</h2>      <Card title="Antes de empezar">        <p>Los datos se actualizan <strong>cada noche</strong>. Escribe aquí dentro como en cualquier otro sitio.</p>      </Card>      <Grid columns={2}>        <Stat label="Pedidos" value="342" detail="12 pendientes" />        <Stat label="Facturado" value="18.420 €" detail="este mes" />      </Grid>      <BarChart label="Ventas por mes" data={data.sales} />      <Button onPress={actions.save}>        Guardar borrador      </Button>    </>  )}

El visor de HTML seguro

Este HTML trae un script, una imagen con código, un enlace con JavaScript y un formulario que pide la contraseña. Cámbialo como quieras: el visor lo pinta sin ejecutar nada y dice qué ha quitado.

Cómo se pinta

Novedades de otoño

Chaquetas de lana con el 20 % de descuento.

Ver la colección

Pulsa aquí

Entrar

No ha hecho falta quitar nada.

Qué trae

Un campo como los demás

Etiqueta, ayuda, error, contador, límite opcional, solo lectura y deshabilitado, como cualquier otro campo de la biblioteca. Lleva barra de herramientas, barra flotante sobre la selección y el menú «/» para insertar bloques.

Texto y estructura

Títulos, negrita, cursiva, subrayado, tachado, código, enlaces, color de texto, resaltado, sub y superíndice, alineación, listas con niveles, listas de tareas, citas, bloques de código, imágenes, tablas con celdas combinadas, texto en columnas y fórmulas escritas en TeX.

Bloques

Cuadros destacados (nota, consejo, aviso, peligro), desplegables y menciones con @. Y bloques propios: registras un tipo con su render y el documento solo guarda su nombre y sus datos.

Avisa mientras escribes

Una imagen sin descripción, una tabla sin cabecera, un título que se salta un nivel o un enlace que dice «aquí» salen en una tira bajo el texto, con un botón para ir y, donde es seguro, para arreglarlo. Además: deshacer y rehacer, buscar en el texto y pantalla completa.

Revisión en equipo

Corrector ortográfico, comentarios sobre un trozo de texto, sugerencias que se aceptan o se rechazan e historial de versiones con comparación. Cada pieza se descarga solo cuando se abre.

Pegado fiel

Lo que llega de Word, Google Docs o una web se limpia y se conserva lo que significa algo (títulos, listas, tablas, enlaces) sin arrastrar estilos ajenos.

Para agentes

Una API para que un asistente lea el documento por bloques, proponga cambios y una persona los acepte o los rechace antes de que se apliquen.

Empezar

El editor funciona controlado o no, con el documento en JSON o en HTML. El JSON es lo que conviene guardar: no pierde nada. RichTextView pinta el mismo documento sin cargar el editor.

Código
import { RichTextEditor, RichTextView } from '@amset/ui/rich-text' export function Notes({ html, onChange }: { html: string; onChange: (html: string) => void }) {  return <RichTextEditor label="Notes" format="html" defaultValue={html} onChange={onChange} rows={8} />} // Solo lectura, sin cargar el editor (también en el servidor)export function Preview({ html }: { html: string }) {  return <RichTextView format="html" value={html} />}

Formatos

Cada conversor se importa solo si lo usas; el editor no carga ninguno.

  • HTML: toHtml y fromHtml. Todo lo que el editor sabe hacer sale como HTML válido y se lee de vuelta.
  • Markdown: toMarkdown y fromMarkdown (CommonMark, más las tablas, tareas y tachado de GFM). Lo que Markdown no sabe decir (subrayado, color, alineación, celdas combinadas) se escribe como HTML dentro del Markdown, así que el documento sobrevive al viaje de ida y vuelta. Las fórmulas se guardan entre $$. Una prueba lo comprueba con 1500 documentos aleatorios.
  • Word: toDocx y fromDocx generan y leen ficheros .docx con títulos, listas anidadas, tablas con celdas combinadas, enlaces, imágenes y menciones. Las sugerencias salen como cambios con control de cambios y los comentarios como comentarios de Word, y se leen de vuelta. Las columnas salen en una sola y las fórmulas como el TeX, en estilo de código.
  • Código React: toTsx convierte el documento en un componente de React que importa de @amset/ui.
Código
import { toMarkdown, fromMarkdown, toDocx } from '@amset/ui/rich-text' const markdown = toMarkdown(doc)const back = fromMarkdown(markdown)const bytes = await toDocx(doc)

Bloques propios

Un bloque es una pieza tuya (una tarjeta de producto, una gráfica, un formulario) dentro del texto. El documento guarda solo su tipo y unos datos JSON con límites de tamaño y profundidad; lo que se ve lo decide tu render, que se usa igual en el editor, en RichTextView y en el servidor. Un bloque que nadie sabe pintar se conserva tal cual, no se pierde.

Código
const blocks = {  'product-card': {    label: 'Product card',    defaultProps: { name: 'Product', price: '0 €' },    render: (props) => <ProductCard {...props} />,  },} <RichTextEditor label="Page" blocks={blocks} />

Revisar y corregir en equipo

Lo que se necesita para que varias personas trabajen sobre un mismo texto. Todo se activa con una propiedad, y lo que no se activa no pesa.

Corrector ortográfico

Los fallos se subrayan con una onda; con el botón derecho salen las sugerencias, ignorar y añadir al diccionario. La palabra que estás escribiendo no se marca hasta que sigues. El editor no trae diccionario, porque pesaría megas y hay uno por idioma: tú enchufas el tuyo con createWordSpellchecker o createLanguageToolSpellchecker.

Comentarios

Seleccionas un trozo y comentas. Cada comentario abre un hilo donde se responde, se resuelve o se elimina. Los hilos los guardas tú, donde quieras (threads y onThreadsChange); en el documento solo queda la marca de a qué texto se refieren.

Sugerencias

En modo «Sugerir cambios» lo que escribes se subraya y lo que borras se tacha, con autor y fecha, en vez de cambiar el texto. Quien tenga permiso acepta o rechaza cada una, o todas a la vez. Los cambios de formato y de estructura no se registran como sugerencias.

Historial de versiones

Una lista de versiones guardadas con su nombre. «Ver cambios» enseña lo añadido subrayado y lo quitado tachado desde esa versión hasta ahora, y restaurar se puede deshacer. diffDocuments(antes, despues) da la misma comparación fuera del editor.

Columnas

Un bloque de dos a cuatro columnas por donde fluye el texto, como en un periódico. En una pantalla estrecha pasa a una sola.

Fórmulas

Se escriben en TeX en una caja con vista previa y se pintan como MathML, con un conversor propio y sin dependencias. Si el TeX no se entiende, se ve tal cual y la caja dice por qué.
Código
import { RichTextEditor, createLanguageToolSpellchecker, diffDocuments } from '@amset/ui/rich-text' <RichTextEditor  label="Contract"  spellchecker={createLanguageToolSpellchecker({ url: '/api/spellcheck', language: 'en' })}  review={{ author: 'Ana', threads, onThreadsChange: setThreads, suggesting, onSuggestingChange: setSuggesting }}  versions={{ versions, load: loadVersion, onSave: saveVersion, onRestore: restoreVersion }}/> // The same comparison outside the editorconst changes = diffDocuments(before, after)

La edición simultánea en tiempo real, con varias personas escribiendo a la vez y sus cursores a la vista, no está todavía. Necesita un servidor de sincronización y credenciales por usuario, y llegará más adelante. Mientras tanto, los comentarios, las sugerencias y las versiones funcionan sobre un documento que se guarda y se vuelve a abrir.

Los componentes de AMSET UI dentro del documento

Con amsetBlocks() los componentes de la biblioteca (159 hoy) se convierten en bloques: escribes «/» y el nombre, y se inserta el componente de verdad.

Catálogo generado

Sale del compilador de TypeScript: de cada componente se saca el esquema JSON de sus props. Un test falla si el catálogo se queda atrás de la biblioteca, así que un componente nuevo entra solo.

Carga bajo demanda

Cada componente se descarga la primera vez que un documento lo usa. Un documento sin gráficas no descarga las gráficas.

Qué guarda el documento

El documento guarda el nombre del componente y sus props en JSON, validadas contra su esquema antes de pintarlo: lo que no encaja se descarta y un bloque incompleto dice qué le falta en vez de romperse. Nunca se guardan className, style, ref, id, HTML crudo ni funciones, y los enlaces e imágenes pasan por las mismas comprobaciones de URL que el resto del editor.

Componentes con contenido

Tarjeta, Alerta, Rejilla, Pila, Centrado, Plegable y Contenedor llevan dentro bloques del documento, que se escriben como cualquier otro texto. En el editor se ven con un marco y su nombre; la barra ofrece sus propiedades, un formulario generado del esquema, y «Quitar y dejar el contenido».

Acciones y datos de tu aplicación

Un botón del documento no lleva una función: lleva un identificador (save). Una gráfica no lleva sus cifras: lleva el nombre de un origen de datos (sales). Tu aplicación los resuelve con amsetBlocks({ actions, data }). En el editor no se ejecuta nada, y la ficha deja elegir de dónde sale cada lista.
Código
import { RichTextEditor, amsetBlocks } from '@amset/ui/rich-text' const blocks = amsetBlocks({  actions: { save: { label: 'Save draft', run: () => saveDraft() } },  data: { sales: { label: 'Monthly sales', value: sales } },}) <RichTextEditor label="Report" blocks={blocks} />
document
{  "type": "component",  "kind": "amset-card",  "props": { "title": "Summary" },  "content": [{ "type": "paragraph", "inline": [{ "type": "text", "text": "Quarter figures", "marks": [] }] }]}

Un visor de HTML seguro

HtmlViewer pinta HTML que no controlas: una plantilla de correo, el cuerpo de un mensaje, el HTML que devuelve un modelo.

Modo documento

El HTML se lee, se queda solo con lo permitido (etiquetas, atributos, esquemas de URL y propiedades de CSS) y se vuelve a escribir desde el árbol. Lo que el lector pudiera malinterpretar no se copia nunca. Lo quitado se cuenta en onReport.

Modo página

Respeta el diseño y los estilos de quien escribió el HTML, dentro de un <iframe sandbox> sin allow-same-origin y con una política de seguridad que no permite red, formularios, marcos ni scripts ajenos. Solo se ejecuta un pequeño script nuestro, con nonce, que avisa de la altura y de los clics en enlaces.
Código
import { HtmlViewer } from '@amset/ui/rich-text' <HtmlViewer html={untrustedHtml} images="https" onReport={(report) => log(report)} /> // El diseño del autor, aislado en un iframe<HtmlViewer html={newsletter} mode="page" images="https" />

A código y a agentes

toTsx(doc) devuelve el código de un componente de React con los componentes de la biblioteca, las acciones como actions.save y los datos como data.sales, más una lista de avisos de lo que no se pudo llevar tal cual. Para un asistente, listComponents(), describeComponent(kind) y createComponent(kind, props) dan el catálogo, la ficha de un componente y un bloque ya validado, con los errores explicados en vez de un bloque roto.

Código
import { toTsx, listComponents, createComponent } from '@amset/ui/rich-text' const { code, warnings } = toTsx(doc, { componentName: 'Report' }) listComponents()                       // the catalogueawait createComponent('amset-stat', { label: 'Orders', value: '342' })

Seguridad

  • Lo que se pega, se importa y se carga pasa por listas de lo permitido; nada de lo no listado llega al documento.
  • El documento nunca contiene funciones, scripts ni estilos arbitrarios. Los bloques propios y los de la biblioteca se validan contra su esquema y con límites de tamaño.
  • Los enlaces aceptan http, https, mailto, tel y rutas relativas; las imágenes, http, https y rutas propias, nunca data:.
  • El visor de HTML se prueba con más de 50 vectores de ataque y 600 documentos mutados al azar.

Accesibilidad y peso

El campo es un role="textbox" con su etiqueta, la barra de herramientas se maneja con teclado, los cambios importantes se anuncian y cada historia pasa axe. El editor completo pesa unos 99 KB comprimidos y RichTextView, que solo lee, unos 21 KB. Los paneles de revisión y de versiones, el conversor de fórmulas, los componentes de AMSET UI y los conversores a Markdown o Word se cargan solo si se usan.

Seguir leyendo