diff --git a/chatserver b/chatserver new file mode 100644 index 0000000..30fdc90 Binary files /dev/null and b/chatserver differ diff --git a/content/root.css b/content/root.css index aaef1bd..5bae233 100644 --- a/content/root.css +++ b/content/root.css @@ -114,6 +114,32 @@ body { right: calc(30%); } +.message-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-right: 8px; +} + +.delete-button { + background: none; + border: none; + cursor: pointer; + color: var(--timestamp-color); + padding: 2px 6px; + font-size: 12px; + opacity: 0; + transition: opacity 0.3s; +} + +.message:hover .delete-button { + opacity: 1; +} + +.delete-button:hover { + background: var(--pum-button-inactive-fg); +} + .settings-icon, .users-icon, .theme-icon{ width: 24px; height: 24px; diff --git a/content/root.js b/content/root.js index 1b6773d..fb265b8 100644 --- a/content/root.js +++ b/content/root.js @@ -1,6 +1,8 @@ function toggleSettings() { const panel = document.getElementById("settings-panel"); - panel.style.display = panel.style.display === "none" ? "block" : "none"; + panel.style.display = panel.style.display === "block" + ? "none" + : "block"; if (panel.style.display === "block") { const username = document.getElementById("username"); username.focus(); @@ -11,7 +13,9 @@ function toggleSettings() { function toggleUsers() { const panel = document.getElementById("users-panel"); - panel.style.display = panel.style.display === "none" ? "block" : "none"; + panel.style.display = panel.style.display === "block" + ? "none" + : "block"; } function toggleTheme() { @@ -23,6 +27,30 @@ function toggleTheme() { localStorage.setItem("theme", newTheme); } +async function deleteMessage(messageId) { + if (confirm("Delete this message?")) { + try { + const response = await fetch("/messages", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ messageId: messageId }), + }); + + if (response.ok) { + // Refresh messages + console.log("Message deleted successfully"); + location.reload(); + } else { + console.error("Failed to delete message"); + } + } catch (error) { + console.error("Error deleting message:", error); + } + } +} + document.addEventListener("click", function (event) { const settingsPanel = document.getElementById("settings-panel"); const settingsButton = document.querySelector(".settings-button"); @@ -134,9 +162,16 @@ async function loadMessages() { "div", ); messageDiv.className = "message"; - const [username, content] = msg.innerHTML.split( - "
", - ); + const [ + messageId, + username, + timestamp, + content, + ] = msg + .innerHTML.split( + "
", + ); + const linkedContent = content.replace( /(?![^<]*>)(https?:\/\/[^\s<]+)/g, function (url) { @@ -176,11 +211,30 @@ async function loadMessages() { return `${url}`; }, ); - messageDiv.innerHTML = - '
' + username + - "
" + - '
' + - linkedContent + "
"; + + let deleteHtml = ""; + + const usernameDiv = document.createElement( + "div", + ); + usernameDiv.innerHTML = username; + compareUsername = usernameDiv.textContent; + + if ( + compareUsername === + document.getElementById( + "current-user", + ).textContent + ) { + deleteHtml = + ``; + } + messageDiv.innerHTML = ` +
+
${username} ${timestamp} ${deleteHtml}
+
+
${linkedContent}
`; + messagesDiv.appendChild(messageDiv); }); scrollToBottom(); diff --git a/main.go b/main.go index bcc1133..8d9c534 100644 --- a/main.go +++ b/main.go @@ -182,26 +182,6 @@ func (db *Database) MessagesGet() []Message { var username string rows.Scan(&id, &ip_address, &content, &created_at, &username) - // TODO: Implement better message parsing for admin commands - // TESTING: - // if msgId, ok := strings.CutPrefix(content, "@delete message id"); ok { - // msgId = strings.TrimSpace(msgId) - // go func() { - // db.MessageDeleteId(msgId) - // // db.MessageDeleteId(id) - // }() - // continue - // } - // TESTING: - // if msgId, ok := strings.CutPrefix(content, "@delete user messages"); ok { - // msgId = strings.TrimSpace(msgId) - // go func() { - // db.UserMessagesDelete(ip_address) - // // db.MessageDeleteId(id) - // }() - // continue - // } - message := Message{ Id: id, Content: content, @@ -288,6 +268,18 @@ func (db *Database) MessageDeleteId(id string) { fmt.Println(err) } } +func (db *Database) MessageDeleteIfOwner(id string, ip string) (int, error) { + res, err := db.db.Exec("DELETE FROM messages WHERE id = ? AND ip_address = ?", id, ip) + if err != nil { + return 0, err + } + affected, err := res.RowsAffected() + if err != nil { + return 0, err + } + return int(affected), nil + +} func (db *Database) DeleteOldMessages(ageMinutes int) { if ageMinutes <= 0 { @@ -491,12 +483,11 @@ func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) { clientIP := getClientIP(r) timeZone := s.Database.UserGetTimezone(clientIP) timeLocal := TimeStringToTimeInLocation(msg.Timestamp, timeZone) - body += fmt.Sprintf(`%s

%s %s
%s

`, + body += fmt.Sprintf(`

%s
%s
%s
%s

`, msg.Id, msg.SenderUsername, timeLocal, msg.Content) } w.Write([]byte(getMessageTemplate(s.Config.Paths.MessagesHtmlPath, body))) - case http.MethodPut: w.Header().Set("Content-Type", "application/json") @@ -529,6 +520,30 @@ func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) { "status": "Message received", "from": username, }) + case http.MethodDelete: + w.Header().Set("Content-Type", "application/json") + + var req struct { + MessageId string `json:"messageId"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, `{"error": "Invalid JSON"}`, http.StatusBadRequest) + return + } + + clientIP := getClientIP(r) + if affected, err := s.Database.MessageDeleteIfOwner(req.MessageId, clientIP); err != nil { + http.Error(w, `{"error": "Unauthorized"}`, http.StatusNotFound) + return + } else if affected == 0 { + http.Error(w, `{"error": "Message not found"}`, http.StatusNotFound) + return + } + + json.NewEncoder(w).Encode(map[string]string{ + "status": "Message deleted", + }) default: http.Error(w, `{"error": "Method not allowed"}`, http.StatusMethodNotAllowed) return diff --git a/readme.md b/readme.md index 32c11e4..8f015df 100644 --- a/readme.md +++ b/readme.md @@ -6,12 +6,10 @@ - Nothing yet ### Low Priority - Mobile formatting @media -- First click of user list does not register ## Backend ### High Priority -- Updating messages should lazily load prior messages (pagination?) +- Nothing yet ### Mid Priority - Nothing yet ### Low Priority - Search functionality? -- Delete messages? (in progress, capability exists, flesh this out a bit)