Plugins de collaboration et de relecture
Six plugins qui transforment l’éditeur en véritable workflow documentaire : commandes slash, @mentions, Track Changes, commentaires en fil de discussion, historique des révisions et présence reposant sur Yjs. Tous sont inclus dans la licence standard — aucune offre premium n’est nécessaire.
Commandes slash
Tapez / sur une ligne vide (ou après une espace) pour ouvrir un sélecteur de commandes en ligne. 15 blocs intégrés (H1–H3, listes à puces / numérotées, citation, code, séparateur, tableau, image, lien, emoji, date du jour), plus chaque action AI Toolkit enregistrée.
Configuration
{
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();@Mentions
Un mécanisme de caractères déclencheurs pour @, #, [[wiki]] ou n’importe quel préfixe personnalisé. Sources de données asynchrones (avec debounce et état de chargement). Pastilles atomiques non modifiables — la touche Retour arrière supprime toute la pastille.
Enregistrer un déclencheur
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; }
});Track Changes (mode suggestion humain)
Des suggestions à la manière de Word. Les insertions apparaissent soulignées dans la couleur de l’utilisateur courant ; les suppressions sont barrées (et ne sont retirées qu’une fois acceptées). Des suppressions successives fusionnent en une seule plage ; revenir en arrière à l’intérieur de votre propre insertion qui vient d’être tapée la réduit.
Configuration
{
trackChangesEnabled: true,
currentUser: { id: "maya", name: "Maya Patel", color: "#9333ea" },
reviewCanDecide: (entry, action, user) => user.role === "editor"
}Acceptation / rejet par auteur depuis le panneau Review. Utilisez reviewCanDecide pour appliquer la politique de relecture de votre application ; les décisions refusées laissent la suggestion en attente et déclenchent review_decision_denied. Les suggestions acceptées et rejetées enregistrent decidedBy pour une piste d’audit. Fonctionne avec les suggestions de l’IA — les deux aboutissent dans le même stockage editor.reviewLedger.
Historique des révisions
Un navigateur d’instantanés avec une iframe d’aperçu en bac à sable et une comparaison ligne à ligne fondée sur le LCS. Instantanés manuels ou automatiques avec debounce. localStorage par défaut, ou POST vers un point de terminaison du serveur.
Configuration
{
revisionHistoryEnabled: true,
revisionHistoryMaxEntries: 50,
revisionHistoryAutoSnapshotMs: 10000,
revisionHistoryUrl: "/api/revisions", // optional
revisionHistoryRequestHeaders: () => ({
"X-CSRF-Token": readCsrfToken()
}),
onRevisionHistorySyncError: ({ reason, status }) => {
reportSyncProblem(reason, status);
}
}Utilisez un point de terminaison de même origine avec votre session existante et votre protection CSRF. Les valeurs d’en-tête peuvent être fournies par une fonction, pour qu’un jeton antifalsification de courte durée soit lu au moment de la requête. Ne placez pas de clés d’API de longue durée dans la configuration de l’éditeur.
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();Présence Yjs et saisie simultanée
Curseurs en direct, avatars de présence, registre de relecture partagé. Yjs est une peer dependency — le plugin ne l’inclut jamais. Vous chargez Yjs et un provider (WebSocket / WebRTC / Hocuspocus) et vous les lui transmettez.
Attacher
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". La liaison par nœud reflète le DOM modifiable dans un Y.XmlFragment, en préservant les modifications simultanées de texte et de mise en forme sans l’ancienne réinitialisation de tout le body. Appelez editor.collab.getStatus() après l’attachement pour consigner le mode actif et l’éventuelle raison d’un repli.
Frontière WebSocket en production
Le serveur de collaboration inclus prend en charge des jetons signés limités à une salle, un état LevelDB durable, une liste d’origines de navigateur autorisées et un plafond de connexions par salle. Utilisez ces quatre contrôles en production ; terminez le TLS au niveau de votre proxy et n’exposez pas de point de terminaison WebSocket ouvert.
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.jsLe point de terminaison de jetons cloud vérifie la propriété du document avant d’émettre un identifiant de courte durée pour une salle. Le serveur rejette les origines de navigateur absentes ou non listées avec un HTTP 403, et rejette les pairs en surnombre dans une salle très active avec un HTTP 429. Définissez la même URL Redis sur chaque réplique pour relayer les mises à jour de document et d’awareness sans sessions persistantes ; une réplique qui rejoint une salle active demande un instantané complet de l’état CRDT avant de synchroniser son premier client. Si le Redis configuré est indisponible, /healthz renvoie HTTP 503 et status: "degraded". Après la reconnexion, la disponibilité reste dégradée jusqu’à ce qu’un aller-retour Pub/Sub interne prouve que l’abonnement reçoit de nouveau.
Chaque pair est une entrée non fiable
Un CRDT applique les modifications distantes en écrivant des nœuds directement dans le document actif. Il ne passe ni par la gestion du collage ni par setHTMLCode : un éditeur qui ne filtre que ces deux points d’entrée exécutera donc sans broncher l'<iframe srcdoc> d’un collaborateur dans le navigateur de chaque autre participant — puis la répliquera plus loin. Traitez un co-auteur d’un document partagé exactement comme du HTML collé.
sanitizer.js ferme cette voie : il surveille les nœuds et attributs insérés dans la zone modifiable et filtre tout ce qui est exécutable avant que cela ne puisse s’exécuter, quelle que soit l’origine. La saisie ordinaire ne coûte rien, car les nœuds de texte sont ignorés et le filtre n’intervient que lorsque quelque chose de dangereux apparaît. Désactivez-le avec sanitizerGuardLiveDom: false si votre couche de synchronisation nettoie déjà en amont. Vous pouvez faire cette vérification vous-même sur la page de vérification en direct.
La suite de tests de publication démarre deux processus de serveur WebSocket indépendants face à un processus réel compatible Redis isolé, et vérifie l’arrivée tardive, le contenu et la présence dans les deux sens, l’isolement des salles et des jetons, la perte du backend, le retour à la disponibilité et la première modification après le rétablissement. Il s’agit de preuves d’intégration produites par l’éditeur du logiciel, et non d’un SLA de service géré ni d’une certification de fiabilité externe.
Registre de relecture partagé
Les suggestions de l’IA, les suggestions humaines du suivi des modifications et les commentaires passent tous par un seul stockage : editor.reviewLedger. C’est ce qui permet au panneau Review de les afficher entremêlés, et à l’acceptation / au rejet de fonctionner de la même façon quelle que soit la source.
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();
Commentaires en fil de discussion
Les commentaires s’ancrent sur des plages de sélection et s’affichent sous forme de surlignage et de fil dans la barre latérale. Rédaction, réponses, résolution, suppression.
Configuration
API