Knowledge base of ~80+ markdown files across 14 domains (00-13), Logseq graph, hardware design files (KiCAD), infrastructure configs, and talas-wiki static site. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
109 lines
2.1 KiB
Go
109 lines
2.1 KiB
Go
package wiki
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var upgrader = websocket.Upgrader{
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
}
|
|
|
|
type WSHub struct {
|
|
mu sync.RWMutex
|
|
clients map[*websocket.Conn]string // conn → page path being viewed
|
|
}
|
|
|
|
func NewWSHub() *WSHub {
|
|
return &WSHub{clients: make(map[*websocket.Conn]string)}
|
|
}
|
|
|
|
func (h *WSHub) HandleWS(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
pagePath := r.URL.Query().Get("page")
|
|
|
|
h.mu.Lock()
|
|
h.clients[conn] = pagePath
|
|
h.mu.Unlock()
|
|
|
|
// Read loop (keeps connection alive, handles page changes)
|
|
defer func() {
|
|
h.mu.Lock()
|
|
delete(h.clients, conn)
|
|
h.mu.Unlock()
|
|
conn.Close()
|
|
}()
|
|
|
|
for {
|
|
_, msg, err := conn.ReadMessage()
|
|
if err != nil {
|
|
break
|
|
}
|
|
// Client can update which page they're viewing
|
|
h.mu.Lock()
|
|
h.clients[conn] = string(msg)
|
|
h.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// NotifyPageChange sends a reload message to all clients viewing the given page
|
|
func (h *WSHub) NotifyPageChange(pagePath string) {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
|
|
for conn, viewing := range h.clients {
|
|
if viewing == pagePath || viewing == "" {
|
|
conn.WriteJSON(map[string]string{
|
|
"type": "reload",
|
|
"page": pagePath,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// NotifyAll sends a message to all connected clients
|
|
func (h *WSHub) NotifyAll(msgType string, data string) {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
|
|
for conn := range h.clients {
|
|
conn.WriteJSON(map[string]string{
|
|
"type": msgType,
|
|
"data": data,
|
|
})
|
|
}
|
|
}
|
|
|
|
// GetPresence returns a map of page paths to viewer counts
|
|
func (h *WSHub) GetPresence() map[string]int {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
|
|
presence := make(map[string]int)
|
|
for _, page := range h.clients {
|
|
if page != "" {
|
|
presence[page]++
|
|
}
|
|
}
|
|
return presence
|
|
}
|
|
|
|
// GetViewerCount returns how many users are viewing a specific page
|
|
func (h *WSHub) GetViewerCount(pagePath string) int {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
|
|
count := 0
|
|
for _, page := range h.clients {
|
|
if page == pagePath {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|