Real-time collaboration (Yjs + WebSocket)
- Go WebSocket hub: rooms per document, broadcast updates, persist state - Yjs integration: connect/disconnect, sync document state - Collab toggle button in toolbar (Solo/Live) - When Live: edits broadcast to all connected users in real-time - Yjs state persisted to SQLite (survives server restart) - gorilla/websocket dependency added
This commit is contained in:
@@ -59,6 +59,9 @@
|
||||
<div class="toolbar-right">
|
||||
<span class="file-name">{{ currentFile || 'No file open' }}</span>
|
||||
<span class="file-meta" v-if="fileMeta">{{ fileMeta }}</span>
|
||||
<button v-if="currentFile" class="toolbar-btn" @click="toggleCollab" :title="collabActive ? 'Disconnect collab' : 'Enable collab'">
|
||||
{{ collabActive ? '👥 Live' : '👤 Solo' }}
|
||||
</button>
|
||||
<button v-if="currentFile" class="toolbar-btn" @click="showHistory = !showHistory" title="History">📜</button>
|
||||
<button v-if="currentFile" class="toolbar-btn" @click="showShareDialog = !showShareDialog" title="Share">🤝</button>
|
||||
<button v-if="currentFile" class="toolbar-btn" @click="aiVerify" title="Verify with AI">🤖 Verify</button>
|
||||
@@ -248,6 +251,7 @@ import MilkdownEditor from './components/MilkdownEditor.vue'
|
||||
import { api, setToken } from './lib/api.js'
|
||||
import { renderMarkdown } from './lib/markdown.js'
|
||||
import { cacheFile, getCachedFile, addPendingChange, getPendingChanges, clearAllPending } from './lib/offline.js'
|
||||
import { connectCollab, disconnectCollab, setCollabContent } from './lib/collab.js'
|
||||
|
||||
const authenticated = ref(false)
|
||||
const email = ref('')
|
||||
@@ -277,6 +281,7 @@ const shareMsg = ref('')
|
||||
const gitDirty = ref(0)
|
||||
const aiResult = ref('')
|
||||
const trashItems = ref([])
|
||||
const collabActive = ref(false)
|
||||
|
||||
// Preferences
|
||||
const prefs = ref({ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, defaultMode: 'split', theme: 'dark' })
|
||||
@@ -678,6 +683,23 @@ async function aiVerify() {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Collab ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function toggleCollab() {
|
||||
if (collabActive.value) {
|
||||
disconnectCollab()
|
||||
collabActive.value = false
|
||||
} else {
|
||||
if (!currentFile.value) return
|
||||
const fileId = currentFile.value.replace(/[^a-zA-Z0-9]/g, '-')
|
||||
connectCollab(fileId, (newContent) => {
|
||||
content.value = newContent
|
||||
})
|
||||
setCollabContent(content.value)
|
||||
collabActive.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Formatting ──────────────────────────────────────────────────────────────
|
||||
|
||||
function insertFormat(before, after) {
|
||||
|
||||
Reference in New Issue
Block a user