This commit is contained in:
Radon 2025-01-21 16:14:22 -06:00
parent 9e931243ef
commit c6b6021fa9
4 changed files with 112 additions and 4 deletions

View File

@ -224,6 +224,21 @@ body {
transition: opacity 0.3s; transition: opacity 0.3s;
} }
.edit-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 .edit-button {
opacity: 1;
}
.message:hover .delete-button { .message:hover .delete-button {
opacity: 1; opacity: 1;
} }
@ -232,6 +247,10 @@ body {
background: var(--timestamp-color); background: var(--timestamp-color);
} }
.edit-button:hover {
background: var(--timestamp-color);
}
.settings-icon, .users-icon, .theme-icon{ .settings-icon, .users-icon, .theme-icon{
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -537,6 +556,11 @@ button.scroll:hover {
opacity: 1; /* Always visible on mobile */ opacity: 1; /* Always visible on mobile */
} }
.edit-button {
padding: 8px 12px;
opacity: 1; /* Always visible on mobile */
}
@media (orientation: landscape) { @media (orientation: landscape) {
.messages-section { .messages-section {
margin-top: 45px; margin-top: 45px;

View File

@ -27,6 +27,41 @@ function toggleTheme() {
localStorage.setItem("theme", newTheme); localStorage.setItem("theme", newTheme);
} }
async function editMessage(messageId, content) {
const newContent = prompt("Edit message:", content);
if (newContent === null) {
return;
}
if (newContent === "") {
deleteMessage(messageId);
return;
}
if (newContent === content) {
return;
}
try {
const response = await fetch("/messages", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
messageId: messageId,
messageContent: newContent,
}),
});
if (response.ok) {
updateMessagesInPlace();
} else {
console.error("Failed to edit message");
}
} catch (error) {
console.error("Error editing message:", error);
}
}
async function deleteMessage(messageId) { async function deleteMessage(messageId) {
if (confirm("Delete this message?")) { if (confirm("Delete this message?")) {
try { try {
@ -201,12 +236,24 @@ async function loadMessages(forceUpdate = false, scrollLocation) {
); );
usernameDiv.innerHTML = username; usernameDiv.innerHTML = username;
compareUsername = usernameDiv.textContent; compareUsername = usernameDiv.textContent;
let deleteHtml = "";
const embeddedContent = contentEmbedding( const embeddedContent = contentEmbedding(
content, content,
); );
let deleteHtml = "";
let editHtml = "";
const parser = new DOMParser();
const contentHtmlString = content;
const doc = parser.parseFromString(
contentHtmlString,
"text/html",
);
const contentString =
doc.querySelector("span").textContent;
console.log(contentString);
if ( if (
compareUsername === compareUsername ===
document.getElementById( document.getElementById(
@ -215,10 +262,12 @@ async function loadMessages(forceUpdate = false, scrollLocation) {
) { ) {
deleteHtml = deleteHtml =
`<button class="delete-button" title="Delete message" onclick="deleteMessage('${messageId}')" style="display: inline;">🗑️</button>`; `<button class="delete-button" title="Delete message" onclick="deleteMessage('${messageId}')" style="display: inline;">🗑️</button>`;
editHtml =
`<button class="edit-button" title="Edit message" onclick="editMessage('${messageId}', '${contentString}')" style="display: inline;">📝</button>`;
} }
messageDiv.innerHTML = ` messageDiv.innerHTML = `
<div class="message-header"> <div class="message-header">
<div class="username">${username} ${timestamp} ${deleteHtml}</div> <div class="username">${username} ${timestamp} ${deleteHtml} ${editHtml}</div>
</div> </div>
<div class="content">${embeddedContent}</div>`; <div class="content">${embeddedContent}</div>`;

36
main.go
View File

@ -281,6 +281,18 @@ func (db *Database) MessageDeleteIfOwner(id string, ip string) (int, error) {
} }
func (db *Database) MessageEditIfOwner(id string, content string, ip string) (int, error) {
res, err := db.db.Exec("UPDATE messages SET content = ? WHERE id = ? AND ip_address = ?", content, 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) { func (db *Database) DeleteOldMessages(ageMinutes int) {
if ageMinutes <= 0 { if ageMinutes <= 0 {
return return
@ -474,6 +486,30 @@ func getMessageTemplate(filepath string, body string) string {
func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) { func (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {
switch r.Method { switch r.Method {
case http.MethodPatch:
w.Header().Set("Content-Type", "application/json")
var req struct {
MessageId string `json:"messageId"`
MessageContent string `json:"messageContent"`
}
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.MessageEditIfOwner(req.MessageId, req.MessageContent, 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 edited successfully",
})
case http.MethodGet: case http.MethodGet:
w.Header().Set("Content-Type", "text/html") w.Header().Set("Content-Type", "text/html")

View File

@ -7,11 +7,10 @@
### Low Priority ### Low Priority
- Reposition the search button - Reposition the search button
- Fix mobile views instead of hiding elements that you don't want to position properly - Fix mobile views instead of hiding elements that you don't want to position properly
- Ability to edit messages
## Backend ## Backend
### High Priority ### High Priority
- Lazy load with pagination (frontend and backend) - Lazy load with pagination (frontend and backend)
### Mid Priority ### Mid Priority
- Nothing yet - Nothing yet
### Low Priority ### Low Priority
- Ability to edit messages - Nothing yet