Compare commits
2 Commits
15770f58c1
...
d33bd49c38
Author | SHA1 | Date | |
---|---|---|---|
d33bd49c38 | |||
96dfe12fba |
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
113
content/root.js
113
content/root.js
@ -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
17
main.go
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user