feat: collaboration temps reel - edition simultanee (#62)
CI / lint (push) Successful in 1m1s
CI / security (push) Successful in 41s
CI / test (push) Successful in 1m47s
CI / build (push) Successful in 1m20s
CI / e2e (push) Successful in 10m36s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

WebSocket /ws/collab/{vault}/{path} (rooms par fichier), relais Yjs/CRDT, awareness (curseurs colores + presence), persistance serveur debounce 2s, auth WS + check_vault_access, reconnexion automatique. Frontend frontend/js/collab.js, backend backend/collab.py. Tests: 17 backend (5 clients simultanes) + 10 frontend. Docs: CHANGELOG, ROADMAP, fiche features/collaboration.md, README FR/EN.
This commit is contained in:
2026-09-11 23:27:22 -04:00
parent 3f7b7847a5
commit 063b02e996
19 changed files with 1741 additions and 33 deletions
+9
View File
@@ -3,6 +3,7 @@ import { api } from './auth.js';
import { openFile, showWelcome } from './viewer.js';
import { refreshSidebarForContext, refreshTagsForContext } from './sidebar.js';
import { createAIToolbar } from './ai.js';
import { startCollab, stopCollab } from './collab.js';
// ---------------------------------------------------------------------------
// File extension → Lucide icon mapping
@@ -409,6 +410,13 @@ async function openEditor(vaultName, filePath) {
bodyEl.appendChild(state.fallbackEditorEl);
}
// Real-time collaboration (ROADMAP #62) — bind Yjs to the CodeMirror view.
if (state.editorView) {
startCollab({ view: state.editorView, vault: vaultName, path: filePath }).catch((err) => {
console.warn("Collab start failed:", err);
});
}
// Set up AI toolbar (recreate each time editor opens)
var container = document.getElementById('ai-toolbar-container');
if (container && !container.querySelector('.ai-toolbar') && !container.querySelector('div')) {
@@ -480,6 +488,7 @@ function closeEditor() {
// Restore brand text
var brand = modal.querySelector('.editor-brand');
if (brand) brand.textContent = 'ObsiGate';
stopCollab();
if (state.editorView) {
state.editorView.destroy();
state.editorView = null;