All: Migrate boards from global to board specific
Massive commit. Rewrites some of the chat logic to work individually rather than a global chat.jocadbz
parent
5370611265
commit
3a82e2a0d1
|
@ -33,6 +33,11 @@ func BoardHandler(app *App) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
if board.Type == "chat" {
|
||||
http.Redirect(w, r, app.Config.ThreadrDir+"/chat/?id="+boardIDStr, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
if board.Private {
|
||||
if !loggedIn {
|
||||
http.Redirect(w, r, app.Config.ThreadrDir+"/login/", http.StatusFound)
|
||||
|
|
|
@ -28,25 +28,39 @@ func BoardsHandler(app *App) http.HandlerFunc {
|
|||
if r.Method == http.MethodPost && loggedIn && isAdmin {
|
||||
name := r.FormValue("name")
|
||||
description := r.FormValue("description")
|
||||
boardType := r.FormValue("type")
|
||||
|
||||
if name == "" {
|
||||
http.Error(w, "Board name is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if boardType != "classic" && boardType != "chat" {
|
||||
boardType = "classic"
|
||||
}
|
||||
|
||||
board := models.Board{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Private: false,
|
||||
PublicVisible: true,
|
||||
Type: boardType,
|
||||
}
|
||||
query := "INSERT INTO boards (name, description, private, public_visible) VALUES (?, ?, ?, ?)"
|
||||
result, err := app.DB.Exec(query, board.Name, board.Description, board.Private, board.PublicVisible)
|
||||
query := "INSERT INTO boards (name, description, private, public_visible, type) VALUES (?, ?, ?, ?, ?)"
|
||||
result, err := app.DB.Exec(query, board.Name, board.Description, board.Private, board.PublicVisible, board.Type)
|
||||
if err != nil {
|
||||
log.Printf("Error creating board: %v", err)
|
||||
http.Error(w, "Failed to create board", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
boardID, _ := result.LastInsertId()
|
||||
http.Redirect(w, r, app.Config.ThreadrDir+"/board/?id="+strconv.FormatInt(boardID, 10), http.StatusFound)
|
||||
|
||||
var redirectURL string
|
||||
if boardType == "chat" {
|
||||
redirectURL = app.Config.ThreadrDir + "/chat/?id=" + strconv.FormatInt(boardID, 10)
|
||||
} else {
|
||||
redirectURL = app.Config.ThreadrDir + "/board/?id=" + strconv.FormatInt(boardID, 10)
|
||||
}
|
||||
http.Redirect(w, r, redirectURL, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
110
handlers/chat.go
110
handlers/chat.go
|
@ -5,6 +5,7 @@ import (
|
|||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"threadr/models"
|
||||
|
||||
|
@ -20,21 +21,26 @@ var upgrader = websocket.Upgrader{
|
|||
},
|
||||
}
|
||||
|
||||
// ChatHub manages WebSocket connections and broadcasts messages
|
||||
type Client struct {
|
||||
conn *websocket.Conn
|
||||
userID int
|
||||
boardID int
|
||||
}
|
||||
|
||||
type ChatHub struct {
|
||||
clients map[*websocket.Conn]int // Map of connections to user IDs
|
||||
broadcast chan []byte
|
||||
register chan *websocket.Conn
|
||||
unregister chan *websocket.Conn
|
||||
clients map[*Client]bool
|
||||
broadcast chan models.ChatMessage
|
||||
register chan *Client
|
||||
unregister chan *Client
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewChatHub() *ChatHub {
|
||||
return &ChatHub{
|
||||
clients: make(map[*websocket.Conn]int),
|
||||
broadcast: make(chan []byte),
|
||||
register: make(chan *websocket.Conn),
|
||||
unregister: make(chan *websocket.Conn),
|
||||
clients: make(map[*Client]bool),
|
||||
broadcast: make(chan models.ChatMessage),
|
||||
register: make(chan *Client),
|
||||
unregister: make(chan *Client),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,21 +49,26 @@ func (h *ChatHub) Run() {
|
|||
select {
|
||||
case client := <-h.register:
|
||||
h.mutex.Lock()
|
||||
h.clients[client] = 0 // UserID set later
|
||||
h.clients[client] = true
|
||||
h.mutex.Unlock()
|
||||
case client := <-h.unregister:
|
||||
h.mutex.Lock()
|
||||
delete(h.clients, client)
|
||||
if _, ok := h.clients[client]; ok {
|
||||
delete(h.clients, client)
|
||||
client.conn.Close()
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
client.Close()
|
||||
case message := <-h.broadcast:
|
||||
h.mutex.Lock()
|
||||
for client := range h.clients {
|
||||
err := client.WriteMessage(websocket.TextMessage, message)
|
||||
if err != nil {
|
||||
log.Printf("Error broadcasting message: %v", err)
|
||||
client.Close()
|
||||
delete(h.clients, client)
|
||||
if client.boardID == message.BoardID {
|
||||
response, _ := json.Marshal(message)
|
||||
err := client.conn.WriteMessage(websocket.TextMessage, response)
|
||||
if err != nil {
|
||||
log.Printf("Error broadcasting message: %v", err)
|
||||
client.conn.Close()
|
||||
delete(h.clients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
h.mutex.Unlock()
|
||||
|
@ -81,20 +92,52 @@ func ChatHandler(app *App) http.HandlerFunc {
|
|||
}
|
||||
cookie, _ := r.Cookie("threadr_cookie_banner")
|
||||
|
||||
boardIDStr := r.URL.Query().Get("id")
|
||||
boardID, err := strconv.Atoi(boardIDStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid board ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
board, err := models.GetBoardByID(app.DB, boardID)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching board: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if board == nil {
|
||||
http.Error(w, "Chat board not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if board.Type != "chat" {
|
||||
http.Error(w, "This is not a chat board", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if board.Private {
|
||||
hasPerm, err := models.HasBoardPermission(app.DB, userID, boardID, models.PermViewBoard)
|
||||
if err != nil {
|
||||
log.Printf("Error checking permission: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !hasPerm {
|
||||
http.Error(w, "You do not have permission to view this chat", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("ws") == "true" {
|
||||
// Handle WebSocket connection
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("Error upgrading to WebSocket: %v", err)
|
||||
return
|
||||
}
|
||||
hub.register <- ws
|
||||
hub.mutex.Lock()
|
||||
hub.clients[ws] = userID
|
||||
hub.mutex.Unlock()
|
||||
client := &Client{conn: ws, userID: userID, boardID: boardID}
|
||||
hub.register <- client
|
||||
|
||||
defer func() {
|
||||
hub.unregister <- ws
|
||||
hub.unregister <- client
|
||||
}()
|
||||
|
||||
for {
|
||||
|
@ -115,6 +158,7 @@ func ChatHandler(app *App) http.HandlerFunc {
|
|||
|
||||
if chatMsg.Type == "message" {
|
||||
msgObj := models.ChatMessage{
|
||||
BoardID: boardID,
|
||||
UserID: userID,
|
||||
Content: chatMsg.Content,
|
||||
ReplyTo: chatMsg.ReplyTo,
|
||||
|
@ -123,7 +167,6 @@ func ChatHandler(app *App) http.HandlerFunc {
|
|||
log.Printf("Error saving chat message: %v", err)
|
||||
continue
|
||||
}
|
||||
// Fetch the saved message with timestamp and user details
|
||||
var msgID int
|
||||
app.DB.QueryRow("SELECT LAST_INSERT_ID()").Scan(&msgID)
|
||||
savedMsg, err := models.GetChatMessageByID(app.DB, msgID)
|
||||
|
@ -131,47 +174,46 @@ func ChatHandler(app *App) http.HandlerFunc {
|
|||
log.Printf("Error fetching saved message: %v", err)
|
||||
continue
|
||||
}
|
||||
response, _ := json.Marshal(savedMsg)
|
||||
hub.broadcast <- response
|
||||
hub.broadcast <- *savedMsg
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Render chat page
|
||||
messages, err := models.GetRecentChatMessages(app.DB, 50)
|
||||
messages, err := models.GetRecentChatMessages(app.DB, boardID, 50)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching chat messages: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Reverse messages to show oldest first
|
||||
for i, j := 0, len(messages)-1; i < j; i, j = i+1, j-1 {
|
||||
messages[i], messages[j] = messages[j], messages[i]
|
||||
}
|
||||
|
||||
allUsernames, err := models.GetAllUsernames(app.DB)
|
||||
allUsernames, err := models.GetUsernamesInBoard(app.DB, boardID)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching all usernames: %v", err)
|
||||
allUsernames = []string{} // Proceed without autocomplete on error
|
||||
log.Printf("Error fetching usernames for board: %v", err)
|
||||
allUsernames = []string{}
|
||||
}
|
||||
allUsernamesJSON, _ := json.Marshal(allUsernames)
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
Board models.Board
|
||||
Messages []models.ChatMessage
|
||||
AllUsernames template.JS
|
||||
}{
|
||||
PageData: PageData{
|
||||
Title: "ThreadR - Chat",
|
||||
Navbar: "chat",
|
||||
Title: "ThreadR Chat - " + board.Name,
|
||||
Navbar: "boards",
|
||||
LoggedIn: true,
|
||||
ShowCookieBanner: cookie == nil || cookie.Value != "accepted",
|
||||
BasePath: app.Config.ThreadrDir,
|
||||
StaticPath: app.Config.ThreadrDir + "/static",
|
||||
CurrentURL: r.URL.Path,
|
||||
},
|
||||
Board: *board,
|
||||
Messages: messages,
|
||||
AllUsernames: template.JS(allUsernamesJSON),
|
||||
}
|
||||
|
|
11
main.go
11
main.go
|
@ -43,13 +43,14 @@ func createTablesIfNotExist(db *sql.DB) error {
|
|||
public_visible BOOLEAN DEFAULT TRUE,
|
||||
pinned_threads TEXT,
|
||||
custom_landing_page TEXT,
|
||||
color_scheme VARCHAR(255)
|
||||
color_scheme VARCHAR(255),
|
||||
type VARCHAR(20) DEFAULT 'classic' NOT NULL
|
||||
)`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating boards table: %v", err)
|
||||
}
|
||||
|
||||
// Create threads table (without type field)
|
||||
// Create threads table
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE threads (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
|
@ -170,10 +171,12 @@ func createTablesIfNotExist(db *sql.DB) error {
|
|||
_, err = db.Exec(`
|
||||
CREATE TABLE chat_messages (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
board_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
reply_to INT DEFAULT -1,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE
|
||||
)`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating chat_messages table: %v", err)
|
||||
|
@ -387,4 +390,4 @@ func main() {
|
|||
|
||||
log.Println("Server starting on :8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
}
|
|
@ -14,17 +14,18 @@ type Board struct {
|
|||
PinnedThreads []int // Stored as JSON
|
||||
CustomLandingPage string
|
||||
ColorScheme string
|
||||
Type string
|
||||
}
|
||||
|
||||
func GetBoardByID(db *sql.DB, id int) (*Board, error) {
|
||||
query := "SELECT id, name, description, private, public_visible, pinned_threads, custom_landing_page, color_scheme FROM boards WHERE id = ?"
|
||||
query := "SELECT id, name, description, private, public_visible, pinned_threads, custom_landing_page, color_scheme, type FROM boards WHERE id = ?"
|
||||
row := db.QueryRow(query, id)
|
||||
board := &Board{}
|
||||
var pinnedThreadsJSON sql.NullString
|
||||
var customLandingPage sql.NullString
|
||||
var colorScheme sql.NullString
|
||||
var description sql.NullString
|
||||
err := row.Scan(&board.ID, &board.Name, &description, &board.Private, &board.PublicVisible, &pinnedThreadsJSON, &customLandingPage, &colorScheme)
|
||||
err := row.Scan(&board.ID, &board.Name, &description, &board.Private, &board.PublicVisible, &pinnedThreadsJSON, &customLandingPage, &colorScheme, &board.Type)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -56,7 +57,7 @@ func GetBoardByID(db *sql.DB, id int) (*Board, error) {
|
|||
}
|
||||
|
||||
func GetAllBoards(db *sql.DB, private bool) ([]Board, error) {
|
||||
query := "SELECT id, name, description, private, public_visible, pinned_threads, custom_landing_page, color_scheme FROM boards WHERE private = ? ORDER BY id ASC"
|
||||
query := "SELECT id, name, description, private, public_visible, pinned_threads, custom_landing_page, color_scheme, type FROM boards WHERE private = ? ORDER BY id ASC"
|
||||
rows, err := db.Query(query, private)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -70,7 +71,7 @@ func GetAllBoards(db *sql.DB, private bool) ([]Board, error) {
|
|||
var customLandingPage sql.NullString
|
||||
var colorScheme sql.NullString
|
||||
var description sql.NullString
|
||||
err := rows.Scan(&board.ID, &board.Name, &description, &board.Private, &board.PublicVisible, &pinnedThreadsJSON, &customLandingPage, &colorScheme)
|
||||
err := rows.Scan(&board.ID, &board.Name, &description, &board.Private, &board.PublicVisible, &pinnedThreadsJSON, &customLandingPage, &colorScheme, &board.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
type ChatMessage struct {
|
||||
ID int `json:"id"`
|
||||
BoardID int `json:"boardId"`
|
||||
UserID int `json:"userId"`
|
||||
Content string `json:"content"`
|
||||
ReplyTo int `json:"replyTo"`
|
||||
|
@ -18,19 +19,20 @@ type ChatMessage struct {
|
|||
}
|
||||
|
||||
func CreateChatMessage(db *sql.DB, msg ChatMessage) error {
|
||||
query := "INSERT INTO chat_messages (user_id, content, reply_to, timestamp) VALUES (?, ?, ?, NOW())"
|
||||
_, err := db.Exec(query, msg.UserID, msg.Content, msg.ReplyTo)
|
||||
query := "INSERT INTO chat_messages (board_id, user_id, content, reply_to, timestamp) VALUES (?, ?, ?, ?, NOW())"
|
||||
_, err := db.Exec(query, msg.BoardID, msg.UserID, msg.Content, msg.ReplyTo)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetRecentChatMessages(db *sql.DB, limit int) ([]ChatMessage, error) {
|
||||
func GetRecentChatMessages(db *sql.DB, boardID int, limit int) ([]ChatMessage, error) {
|
||||
query := `
|
||||
SELECT cm.id, cm.user_id, cm.content, cm.reply_to, cm.timestamp, u.username, u.pfp_file_id
|
||||
FROM chat_messages cm
|
||||
JOIN users u ON cm.user_id = u.id
|
||||
WHERE cm.board_id = ?
|
||||
ORDER BY cm.timestamp DESC
|
||||
LIMIT ?`
|
||||
rows, err := db.Query(query, limit)
|
||||
rows, err := db.Query(query, boardID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -44,6 +46,7 @@ func GetRecentChatMessages(db *sql.DB, limit int) ([]ChatMessage, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.BoardID = boardID
|
||||
msg.Timestamp, err = time.Parse("2006-01-02 15:04:05", timestampStr)
|
||||
if err != nil {
|
||||
msg.Timestamp = time.Time{}
|
||||
|
@ -56,14 +59,14 @@ func GetRecentChatMessages(db *sql.DB, limit int) ([]ChatMessage, error) {
|
|||
|
||||
func GetChatMessageByID(db *sql.DB, id int) (*ChatMessage, error) {
|
||||
query := `
|
||||
SELECT cm.id, cm.user_id, cm.content, cm.reply_to, cm.timestamp, u.username, u.pfp_file_id
|
||||
SELECT cm.id, cm.board_id, cm.user_id, cm.content, cm.reply_to, cm.timestamp, u.username, u.pfp_file_id
|
||||
FROM chat_messages cm
|
||||
JOIN users u ON cm.user_id = u.id
|
||||
WHERE cm.id = ?`
|
||||
row := db.QueryRow(query, id)
|
||||
var msg ChatMessage
|
||||
var timestampStr string
|
||||
err := row.Scan(&msg.ID, &msg.UserID, &msg.Content, &msg.ReplyTo, ×tampStr, &msg.Username, &msg.PfpFileID)
|
||||
err := row.Scan(&msg.ID, &msg.BoardID, &msg.UserID, &msg.Content, &msg.ReplyTo, ×tampStr, &msg.Username, &msg.PfpFileID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -154,9 +154,14 @@ func HasGlobalPermission(user *User, perm int64) bool {
|
|||
return user.Permissions&perm != 0
|
||||
}
|
||||
|
||||
func GetAllUsernames(db *sql.DB) ([]string, error) {
|
||||
query := "SELECT username FROM users ORDER BY username ASC"
|
||||
rows, err := db.Query(query)
|
||||
func GetUsernamesInBoard(db *sql.DB, boardID int) ([]string, error) {
|
||||
query := `
|
||||
SELECT DISTINCT u.username
|
||||
FROM users u
|
||||
JOIN chat_messages cm ON u.id = cm.user_id
|
||||
WHERE cm.board_id = ?
|
||||
ORDER BY u.username ASC`
|
||||
rows, err := db.Query(query, boardID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
<ul class="board-list">
|
||||
{{range .PublicBoards}}
|
||||
<li class="board-item">
|
||||
{{if eq .Type "chat"}}
|
||||
<a href="{{$.BasePath}}/chat/?id={{.ID}}">{{.Name}} (Chat)</a>
|
||||
{{else}}
|
||||
<a href="{{$.BasePath}}/board/?id={{.ID}}">{{.Name}}</a>
|
||||
{{end}}
|
||||
<p class="board-desc">{{.Description}}</p>
|
||||
</li>
|
||||
{{end}}
|
||||
|
@ -33,7 +37,11 @@
|
|||
<ul class="board-list">
|
||||
{{range .PrivateBoards}}
|
||||
<li class="board-item">
|
||||
{{if eq .Type "chat"}}
|
||||
<a href="{{$.BasePath}}/chat/?id={{.ID}}">{{.Name}} (Chat)</a>
|
||||
{{else}}
|
||||
<a href="{{$.BasePath}}/board/?id={{.ID}}">{{.Name}}</a>
|
||||
{{end}}
|
||||
<p class="board-desc">{{.Description}}</p>
|
||||
</li>
|
||||
{{end}}
|
||||
|
@ -51,6 +59,11 @@
|
|||
<input type="text" id="name" name="name" required><br>
|
||||
<label for="description">Description:</label>
|
||||
<textarea id="description" name="description"></textarea><br>
|
||||
<label for="type">Board Type:</label>
|
||||
<select id="type" name="type">
|
||||
<option value="classic">Classic Board</option>
|
||||
<option value="chat">Chat Board</option>
|
||||
</select><br>
|
||||
<input type="submit" value="Create Board">
|
||||
</form>
|
||||
</section>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
.chat-container {
|
||||
width: 100%;
|
||||
height: calc(100% - 2em); /* Adjust for header */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: none;
|
||||
|
@ -29,6 +29,11 @@
|
|||
background-color: #fef6e4;
|
||||
box-shadow: none;
|
||||
}
|
||||
.chat-header {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #001858;
|
||||
}
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
@ -170,6 +175,8 @@
|
|||
@media (prefers-color-scheme: dark) {
|
||||
.chat-container {
|
||||
background-color: #444;
|
||||
}
|
||||
.chat-header {
|
||||
border-color: #fef6e4;
|
||||
}
|
||||
.chat-message-username {
|
||||
|
@ -216,10 +223,11 @@
|
|||
<body>
|
||||
{{template "navbar" .}}
|
||||
<main>
|
||||
<header style="display: none;">
|
||||
<h2>General Chat</h2>
|
||||
</header>
|
||||
<div class="chat-container">
|
||||
<header class="chat-header">
|
||||
<h2>{{.Board.Name}}</h2>
|
||||
<p>{{.Board.Description}}</p>
|
||||
</header>
|
||||
<div class="chat-messages" id="chat-messages">
|
||||
{{range .Messages}}
|
||||
<div class="chat-message" id="msg-{{.ID}}">
|
||||
|
@ -233,7 +241,7 @@
|
|||
<span class="chat-message-timestamp">{{.Timestamp.Format "02/01/2006 15:04"}}</span>
|
||||
</div>
|
||||
{{if gt .ReplyTo 0}}
|
||||
<div class="chat-message-reply" onclick="scrollToMessage({{.ReplyTo}})">Replying to {{.Username}}</div>
|
||||
<div class="chat-message-reply" onclick="scrollToMessage({{.ReplyTo}})">Replying to message...</div>
|
||||
{{end}}
|
||||
<div class="chat-message-content">{{.Content | html}}</div>
|
||||
<div class="post-actions">
|
||||
|
@ -261,14 +269,15 @@
|
|||
const allUsernames = {{.AllUsernames}};
|
||||
|
||||
function connectWebSocket() {
|
||||
ws = new WebSocket('ws://' + window.location.host + '{{.BasePath}}/chat/?ws=true');
|
||||
const boardID = {{.Board.ID}};
|
||||
ws = new WebSocket('ws://' + window.location.host + '{{.BasePath}}/chat/?ws=true&id=' + boardID);
|
||||
ws.onmessage = function(event) {
|
||||
const msg = JSON.parse(event.data);
|
||||
appendMessage(msg);
|
||||
};
|
||||
ws.onclose = function() {
|
||||
console.log("WebSocket closed, reconnecting...");
|
||||
setTimeout(connectWebSocket, 5000); // Reconnect after 5s
|
||||
setTimeout(connectWebSocket, 5000);
|
||||
};
|
||||
ws.onerror = function(error) {
|
||||
console.error("WebSocket error:", error);
|
||||
|
@ -302,7 +311,7 @@
|
|||
if (msg.pfpFileId && msg.pfpFileId.Valid) {
|
||||
pfpHTML = `<img src="{{.BasePath}}/file?id=${msg.pfpFileId.Int64}&t=${new Date().getTime()}" alt="PFP" class="chat-message-pfp">`;
|
||||
}
|
||||
let replyHTML = msg.replyTo > 0 ? `<div class="chat-message-reply" onclick="scrollToMessage(${msg.replyTo})">Replying...</div>` : '';
|
||||
let replyHTML = msg.replyTo > 0 ? `<div class="chat-message-reply" onclick="scrollToMessage(${msg.replyTo})">Replying to message...</div>` : '';
|
||||
let content = msg.content.replace(/@(\w+)/g, '<span class="chat-message-mention">@$1</span>');
|
||||
|
||||
msgDiv.innerHTML = `
|
||||
|
@ -454,9 +463,8 @@
|
|||
window.onload = function() {
|
||||
connectWebSocket();
|
||||
|
||||
// Highlight mentions in pre-loaded messages
|
||||
document.querySelectorAll('.chat-message-content').forEach(function(el) {
|
||||
const text = el.innerHTML; // The Go template already escaped it for security
|
||||
const text = el.innerHTML;
|
||||
const newHTML = text.replace(/@(\w+)/g, '<span class="chat-message-mention">@$1</span>');
|
||||
el.innerHTML = newHTML;
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
{{if .LoggedIn}}
|
||||
<li><a {{if eq .Navbar "userhome"}}class="active"{{end}} href="{{.BasePath}}/userhome/">User Home</a></li>
|
||||
<li><a {{if eq .Navbar "profile"}}class="active"{{end}} href="{{.BasePath}}/profile/">Profile</a></li>
|
||||
<li><a {{if eq .Navbar "chat"}}class="active"{{end}} href="{{.BasePath}}/chat/">Chat</a></li>
|
||||
<li><a href="{{.BasePath}}/logout/">Logout</a></li>
|
||||
{{else}}
|
||||
<li><a {{if eq .Navbar "login"}}class="active"{{end}} href="{{.BasePath}}/login/">Login</a></li>
|
||||
|
|
Loading…
Reference in New Issue