Compare commits

..

2 Commits

Author SHA1 Message Date
d33bd49c38 changes 2025-01-21 11:28:23 -06:00
96dfe12fba changes 2025-01-21 11:26:56 -06:00
5 changed files with 79 additions and 64 deletions

View File

@ -1,8 +1,7 @@
{ {
"server": { "server": {
"ipAddress": "192.168.1.222", "ipAddress": "192.168.1.222",
"port": 8080, "port": 8080
"messageMaxAge": 259200
}, },
"paths": { "paths": {
"databasePath": "/home/radon/Documents/chattest.db", "databasePath": "/home/radon/Documents/chattest.db",
@ -10,5 +9,9 @@
"indexCssPath": "./content/root.css", "indexCssPath": "./content/root.css",
"indexHtmlPath": "./content/root.html", "indexHtmlPath": "./content/root.html",
"messagesHtmlPath": "./content/messages.html" "messagesHtmlPath": "./content/messages.html"
},
"options": {
"messageMaxAge": 259200,
"nameMaxLength": 32
} }
} }

View File

@ -255,14 +255,14 @@ body {
gap: 10px; gap: 10px;
} }
.youtube-embed { .video-embed {
position: inline; position: inline;
padding-top: 10px; padding-top: 10px;
width: 100%; width: 100%;
max-width: 560px; /* Standard YouTube width */ max-width: 560px; /* Standard YouTube width */
} }
.youtube-embed iframe { .video-embed iframe {
border-radius: 4px; border-radius: 4px;
} }

View File

@ -39,9 +39,7 @@ async function deleteMessage(messageId) {
}); });
if (response.ok) { if (response.ok) {
// Refresh messages updateMessagesInPlace();
console.log("Message deleted successfully");
location.reload();
} else { } else {
console.error("Failed to delete message"); console.error("Failed to delete message");
} }
@ -51,6 +49,22 @@ async function deleteMessage(messageId) {
} }
} }
async function updateMessagesInPlace() {
const currenteScrollLocation = getScrollLocation();
await loadMessages(true);
setScrollLocation(currenteScrollLocation);
}
function getScrollLocation() {
const messagesDiv = document.getElementById("messages");
return messagesDiv.scrollTop;
}
function setScrollLocation(height) {
const messagesDiv = document.getElementById("messages");
messagesDiv.scrollTop = height;
}
document.addEventListener("click", function (event) { document.addEventListener("click", function (event) {
const settingsPanel = document.getElementById("settings-panel"); const settingsPanel = document.getElementById("settings-panel");
const settingsButton = document.querySelector(".settings-button"); const settingsButton = document.querySelector(".settings-button");
@ -117,7 +131,7 @@ async function loadUsers() {
} }
let lastMessageCount = 0; let lastMessageCount = 0;
async function loadMessages() { async function loadMessages(forceUpdate = false) {
try { try {
let messagesDiv = document.getElementById("messages"); let messagesDiv = document.getElementById("messages");
const response = await fetch("/messages"); const response = await fetch("/messages");
@ -152,10 +166,10 @@ async function loadMessages() {
const newMessageCount = messages.length; const newMessageCount = messages.length;
if ( const update = newMessageCount != lastMessageCount ||
newMessageCount > lastMessageCount || lastMessageCount === 0 || forceUpdate;
lastMessageCount === 0
) { if (update) {
messagesDiv.innerHTML = ""; messagesDiv.innerHTML = "";
Array.from(messages).forEach((msg) => { Array.from(messages).forEach((msg) => {
const messageDiv = document.createElement( const messageDiv = document.createElement(
@ -172,49 +186,16 @@ async function loadMessages() {
"<br>", "<br>",
); );
const linkedContent = content.replace(
/(?![^<]*>)(https?:\/\/[^\s<]+)/g,
function (url) {
console.log(
"Processing URL:",
url,
); // Debug log
const videoId = getYouTubeID(
url,
);
if (videoId) {
return `<div class="youtube-embed"><iframe
width="100%"
height="315"
src="https://www.youtube.com/embed/${videoId}"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe></div>`;
} else if (isImageUrl(url)) {
console.log(
"Attempting to embed image:",
url,
);
return `<div class="image-embed"><img
src="${url}"
alt="Embedded image"
loading="lazy"
onerror="console.log('Image failed to load:', this.src); this.style.display='none'"
onload="console.log('Image loaded successfully:', this.src)"></div>`;
}
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`;
},
);
let deleteHtml = "";
const usernameDiv = document.createElement( const usernameDiv = document.createElement(
"div", "div",
); );
usernameDiv.innerHTML = username; usernameDiv.innerHTML = username;
compareUsername = usernameDiv.textContent; compareUsername = usernameDiv.textContent;
let deleteHtml = "";
const embeddedContent = contentEmbedding(
content,
);
if ( if (
compareUsername === compareUsername ===
@ -229,7 +210,7 @@ async function loadMessages() {
<div class="message-header"> <div class="message-header">
<div class="username">${username} ${timestamp} ${deleteHtml}</div> <div class="username">${username} ${timestamp} ${deleteHtml}</div>
</div> </div>
<div class="content">${linkedContent}</div>`; <div class="content">${embeddedContent}</div>`;
messagesDiv.appendChild(messageDiv); messagesDiv.appendChild(messageDiv);
}); });
@ -241,6 +222,36 @@ async function loadMessages() {
} }
} }
function contentEmbedding(content) {
return content.replace(
/(?![^<]*>)(https?:\/\/[^\s<]+)/g,
function (url) {
const videoId = getYouTubeID(
url,
);
if (videoId) {
return `<div class="video-embed"><iframe
width="100%"
height="315"
src="https://www.youtube.com/embed/${videoId}"
frameborder="0"
onerror="console.log('Video failed to load:', this.src); this.style.display='none'"
onload="console.log('Video loaded successfully:', this.src)"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe></div>`;
} else if (isImageUrl(url)) {
return `<div class="image-embed"><img
src="${url}"
alt="Embedded image"
loading="lazy"
onerror="console.log('Image failed to load:', this.src); this.style.display='none'"
onload="console.log('Image loaded successfully:', this.src)"></div>`;
}
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`;
},
);
}
function isImageUrl(url) { function isImageUrl(url) {
return url.match(/\.(jpeg|jpg|gif|png|webp|bmp)($|\?)/i) != null; return url.match(/\.(jpeg|jpg|gif|png|webp|bmp)($|\?)/i) != null;
} }
@ -327,8 +338,8 @@ async function setUsername() {
setTimeout(() => { setTimeout(() => {
document.getElementById("settings-panel").style document.getElementById("settings-panel").style
.display = "none"; .display = "none";
}, 500); }, 750);
location.reload(); updateMessagesInPlace();
} else { } else {
showUsernameStatus( showUsernameStatus(
data.error || "Failed to set username", data.error || "Failed to set username",
@ -363,7 +374,7 @@ async function sendMessage() {
if (response.ok) { if (response.ok) {
messageInput.value = ""; messageInput.value = "";
messageInput.style.height = "auto"; messageInput.style.height = "auto";
loadMessages(); loadMessages(true);
} else { } else {
showStatus( showStatus(
data.error || "Failed to send message", data.error || "Failed to send message",
@ -470,7 +481,7 @@ async function initialize() {
setInterval(loadMessages, 1000); setInterval(loadMessages, 1000);
setInterval(loadUsers, 1000); setInterval(loadUsers, 1000);
setInterval(pingCheck, 3000); setInterval(pingCheck, 3000);
await loadMessages(); await loadMessages(true);
scrollToBottom(); scrollToBottom();
} }

17
main.go
View File

@ -438,8 +438,8 @@ func (s *Server) handleUsername(w http.ResponseWriter, r *http.Request) {
s.mu.Lock() s.mu.Lock()
if len(req.Username) > 64 { if len(req.Username) > s.Config.Options.NameMaxLength {
http.Error(w, fmt.Sprintf(`{"error": "Username too long (must be less than 64 characters)"}`), http.StatusRequestEntityTooLarge) http.Error(w, fmt.Sprintf(`{"error": "Username too long (%v out of %v characters maximum)"}`, len(req.Username), s.Config.Options.NameMaxLength), http.StatusRequestEntityTooLarge)
s.mu.Unlock() s.mu.Unlock()
return return
} }
@ -451,8 +451,8 @@ func (s *Server) handleUsername(w http.ResponseWriter, r *http.Request) {
} }
if s.Database.UserNameExists(req.Username) { if s.Database.UserNameExists(req.Username) {
s.mu.Unlock()
http.Error(w, fmt.Sprintf(`{"error": "Username already exists"}`), http.StatusConflict) http.Error(w, fmt.Sprintf(`{"error": "Username already exists"}`), http.StatusConflict)
s.mu.Unlock()
return return
} }
@ -659,7 +659,7 @@ func (s *Server) handleCss(w http.ResponseWriter, r *http.Request) {
func (s *Server) Run() { func (s *Server) Run() {
s.Database.DbCreateTableMessages() s.Database.DbCreateTableMessages()
s.Database.DbCreateTableUsers() s.Database.DbCreateTableUsers()
s.Database.DeleteOldMessages(s.Config.Server.MessageMaxAge) s.Database.DeleteOldMessages(s.Config.Options.MessageMaxAge)
handler := http.NewServeMux() handler := http.NewServeMux()
handler.HandleFunc("/ping", s.handlePing) handler.HandleFunc("/ping", s.handlePing)
handler.HandleFunc("/username", s.handleUsername) handler.HandleFunc("/username", s.handleUsername)
@ -699,9 +699,8 @@ func main() {
type Config struct { type Config struct {
Server struct { Server struct {
IpAddress string `json:"ipAddress"` IpAddress string `json:"ipAddress"`
Port int `json:"port"` Port int `json:"port"`
MessageMaxAge int `json:"messageMaxAge"`
} `json:"server"` } `json:"server"`
Paths struct { Paths struct {
DatabasePath string `json:"databasePath"` DatabasePath string `json:"databasePath"`
@ -710,6 +709,10 @@ type Config struct {
IndexHtmlPath string `json:"indexHtmlPath"` IndexHtmlPath string `json:"indexHtmlPath"`
MessagesHtmlPath string `json:"messagesHtmlPath"` MessagesHtmlPath string `json:"messagesHtmlPath"`
} `json:"paths"` } `json:"paths"`
Options struct {
MessageMaxAge int `json:"messageMaxAge"`
NameMaxLength int `json:"nameMaxLength"`
} `json:"options"`
} }
func LoadConfig(filepath string) Config { func LoadConfig(filepath string) Config {

View File

@ -2,8 +2,6 @@
## Frontend ## Frontend
### High Priority ### High Priority
- Nothing yet - Nothing yet
- Fix scroll to bottom on initial load?
- Add delete button tooltip
### Mid Priority ### Mid Priority
- Other embeds (Twitter posts, spotify tracks, soundcloud, github repos, instagram posts, other video platforms) - Other embeds (Twitter posts, spotify tracks, soundcloud, github repos, instagram posts, other video platforms)
### Low Priority ### Low Priority