← Toda la documentación
Colaboración y revisión

Plugins de colaboración y revisión

Seis plugins que convierten el editor en un flujo de trabajo documental real: comandos de barra, @menciones, control de cambios, comentarios en hilos, historial de revisiones y presencia basada en Yjs. Todos se incluyen en la licencia estándar; no hace falta ninguna edición premium.

Comandos de barra

Escribe / en una línea vacía (o después de un espacio) para abrir un selector de comandos en línea. 15 bloques integrados (H1–H3, listas con viñetas o numeradas, cita, código, separador, tabla, imagen, enlace, emoji, fecha de hoy) más todas las acciones registradas del AI Toolkit.

Configuración

{
    slashCommandEnabled: true,          // default when this plugin is loaded
    slashCommandIncludeAi: true         // pull aiToolkitActions into the menu
}

API

editor.slashCommands.register({ id, label, keywords, run });
editor.slashCommands.remove(id);
editor.slashCommands.list();
editor.slashCommands.open(); / .close();

@Menciones

Un marco de caracteres de activación para @, #, [[wiki]] o cualquier prefijo personalizado. Fuentes de datos asíncronas (con debounce y estado de carga). Etiquetas atómicas no editables: la tecla Retroceso elimina la etiqueta entera.

Registrar un activador

editor.mentions.register({
    trigger: "@",
    insertClass: "rte-mention-person",
    search: function (query, done) {
        fetch("/api/users?q=" + encodeURIComponent(query))
            .then(r => r.json())
            .then(done);                // Array<{ id, name, color? }>
    },
    renderItem: function (user) { return user.name; },
    renderInsert: function (user) { return "@" + user.name; }
});

Control de cambios (modo de sugerencias humanas)

Sugerencias al estilo de Word. Las inserciones aparecen subrayadas con el color del usuario actual; las eliminaciones se tachan (no se eliminan hasta que se aceptan). Las eliminaciones consecutivas se unen en un solo span; borrar con Retroceso dentro de una inserción que acabas de escribir la reduce.

Configuración

{
    trackChangesEnabled: true,
    currentUser: { id: "maya", name: "Maya Patel", color: "#9333ea" },
    reviewCanDecide: (entry, action, user) => user.role === "editor"
}

Aceptar o rechazar por autor desde el panel Review. Usa reviewCanDecide para aplicar la política de revisores de tu aplicación; las decisiones denegadas dejan la sugerencia pendiente y lanzan review_decision_denied. Las sugerencias aceptadas y rechazadas guardan decidedBy como registro de auditoría. Funciona junto con las sugerencias de la IA: ambas acaban en el mismo almacén editor.reviewLedger.

Comentarios en hilos

Los comentarios se anclan a rangos de selección y se muestran como un resaltado más un hilo en la barra lateral. Redactar, responder, resolver, borrar.

Configuración

{
    commentsEnabled: true,
    currentUser: { id: "alice", name: "Alice Lee", color: "#2563eb" }
}

API

editor.comments.add(range, text);
editor.comments.reply(threadId, text);
editor.comments.resolve(threadId);
editor.comments.delete(threadId);
editor.comments.list();

Historial de revisiones

Un explorador de instantáneas con un iframe de vista previa en sandbox y un diff por líneas basado en LCS. Instantáneas manuales o automáticas con debounce. localStorage de forma predeterminada, o POST a un endpoint del servidor.

Configuración

{
    revisionHistoryEnabled: true,
    revisionHistoryMaxEntries: 50,
    revisionHistoryAutoSnapshotMs: 10000,
    revisionHistoryUrl: "/api/revisions",       // optional
    revisionHistoryRequestHeaders: () => ({
        "X-CSRF-Token": readCsrfToken()
    }),
    onRevisionHistorySyncError: ({ reason, status }) => {
        reportSyncProblem(reason, status);
    }
}

Usa un endpoint del mismo origen con tu sesión y tu protección CSRF existentes. Los valores de los encabezados se pueden proporcionar con una función, para que un token antifalsificación de corta duración se lea en el momento de la solicitud. No pongas claves de API de larga duración en la configuración del editor.

API

editor.revisionHistory.snapshot(label?, metadata?);
editor.revisionHistory.promptAndSnapshot(defaultLabel?);   // native prompt + snapshot
editor.revisionHistory.rename(id, label);
editor.revisionHistory.list();                             // all snapshots
editor.revisionHistory.listNamed();                        // user-labeled only
editor.revisionHistory.diff(idA, idB);                     // { lines, oldText, newText }
editor.revisionHistory.restore(id);
editor.revisionHistory.delete(id);
editor.revisionHistory.openBrowser();

Presencia con Yjs y escritura simultánea

Cursores en vivo, avatares de presencia y registro de revisión compartido. Yjs es una dependencia peer: el plugin nunca la incluye. Tú cargas Yjs y un proveedor (WebSocket / WebRTC / Hocuspocus) y se los pasas.

Conectar

import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";

const ydoc = new Y.Doc();
const provider = new WebsocketProvider("wss://collab.example.com", docId, ydoc);

editor.collab.attach({
    doc: ydoc,
    provider: provider,
    user: { id: "alice", name: "Alice", color: "#2563eb" },
    textSync: "crdt",      // per-node Yjs CRDT; load crdt-engine.min.js first
    requireCrdt: true       // do not silently fall back to legacy snapshots
});

textSync: "crdt". El binding por nodo refleja el DOM editable en un Y.XmlFragment y conserva los cambios simultáneos de texto y de formato sin el reinicio de todo el cuerpo del modo heredado. Llama a editor.collab.getStatus() después de conectar para registrar el modo activo y el motivo de cualquier alternativa.

Borde WebSocket en producción

El servidor de colaboración incluido admite tokens firmados limitados a una sala, estado duradero en LevelDB, una lista de orígenes de navegador permitidos y un límite de conexiones por sala. Usa los cuatro controles en producción; termina TLS en tu proxy y no expongas un endpoint WebSocket abierto.

RTE_COLLAB_SECRET="use-a-long-random-secret"
RTE_COLLAB_PERSIST="/var/lib/rte-collab"
RTE_COLLAB_ALLOWED_ORIGINS="https://app.example.com,https://admin.example.com"
RTE_COLLAB_MAX_CONNECTIONS_PER_ROOM="100"
RTE_COLLAB_REDIS_URL="redis://redis:6379"
node server.js

El endpoint de tokens en la nube comprueba la propiedad del documento antes de emitir una credencial de corta duración para una sala. El servidor rechaza con HTTP 403 los orígenes de navegador ausentes o no incluidos en la lista, y con HTTP 429 los pares que sobran en una sala muy concurrida. Configura la misma URL de Redis en todas las réplicas para retransmitir las actualizaciones del documento y de presencia sin sesiones persistentes; una réplica que se une a una sala activa solicita una instantánea completa del estado CRDT antes de sincronizar a su primer cliente. Si el Redis configurado no está disponible, /healthz devuelve HTTP 503 y status: "degraded". Después de reconectar, el estado de disponibilidad sigue degradado hasta que un ida y vuelta interno de Pub/Sub demuestre que la suscripción vuelve a recibir.

Cada par es una entrada no confiable

Un CRDT aplica las ediciones remotas escribiendo nodos directamente en el documento real. No pasa por la gestión del pegado ni por setHTMLCode, así que un editor que solo filtra esos dos puntos de entrada ejecutará sin problemas el <iframe srcdoc> de un colaborador en el navegador de todos los demás participantes, y luego lo replicará. Trata a un coautor de un documento compartido exactamente igual que tratas el HTML pegado.

sanitizer.js cierra ese camino: vigila el área editable en busca de nodos y atributos insertados y filtra todo lo ejecutable antes de que pueda ejecutarse, sea cual sea su origen. La escritura normal no cuesta nada, porque los nodos de texto se omiten y el filtro solo actúa cuando aparece algo peligroso. Desactívalo con sanitizerGuardLiveDom: false si tu capa de sincronización ya sanea antes. Puedes hacer tú mismo esta comprobación en la página de verificación en vivo.

La batería de pruebas de cada versión inicia dos procesos de servidor WebSocket independientes contra un proceso real aislado compatible con Redis y verifica la incorporación tardía, el contenido y la presencia en ambas direcciones, el aislamiento de salas y tokens, la pérdida del backend, la recuperación de la disponibilidad y la primera edición después de la recuperación. Son pruebas de integración realizadas por el proveedor, no un SLA de servicio gestionado ni una certificación externa de fiabilidad.

Registro de revisión compartido

Las sugerencias de la IA, las sugerencias humanas del control de cambios y los comentarios pasan todos por un único almacén: editor.reviewLedger. Así es como el panel Review puede mostrarlos intercalados, y aceptar o rechazar funciona igual sea cual sea el origen.

API

editor.reviewLedger.add(entry);            // { id, changeType, author, text, replies[], ... }
editor.reviewLedger.update(id, patch);
editor.reviewLedger.remove(id);
editor.reviewLedger.get(id);
editor.reviewLedger.list();
editor.reviewLedger.refreshPanel();