Fix errors on js and add grouped messages

jocadbz
Joca 2026-01-20 23:33:46 -03:00
parent 9749457e2f
commit 78a2875958
Signed by: jocadbz
GPG Key ID: B1836DCE2F50BDF7
3 changed files with 137 additions and 14 deletions

View File

@ -59,7 +59,6 @@ function enableEnterToSubmit(input, form) {
} }
}); });
}); });
}
// Initialize on DOM ready // Initialize on DOM ready
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
@ -343,3 +342,88 @@ function showDraftIndicator(content, timestamp, onRestore, onDiscard) {
return indicator; return indicator;
} }
// ============================================
// Validation Functions
// ============================================
function validateRequired(value, fieldName) {
if (!value || value.trim() === '') {
return `${fieldName} is required`;
}
return null;
}
function validateUsername(username) {
if (!username || username.trim() === '') {
return 'Username is required';
}
if (username.length < 3) {
return 'Username must be at least 3 characters';
}
if (username.length > 50) {
return 'Username must be less than 50 characters';
}
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
return 'Username can only contain letters, numbers, and underscores';
}
return null;
}
function validatePassword(password) {
if (!password || password.trim() === '') {
return 'Password is required';
}
if (password.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
}
function showFieldError(field, error) {
field.classList.add('error');
let errorDiv = field.nextElementSibling;
if (!errorDiv || !errorDiv.classList.contains('field-error')) {
errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
field.parentNode.insertBefore(errorDiv, field.nextSibling);
}
errorDiv.textContent = error;
}
function clearFieldError(field) {
field.classList.remove('error');
const errorDiv = field.nextElementSibling;
if (errorDiv && errorDiv.classList.contains('field-error')) {
errorDiv.remove();
}
}
function autoResizeTextarea(textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
function addCharacterCounter(textarea, maxLength) {
const counter = document.createElement('div');
counter.className = 'char-counter';
textarea.parentNode.insertBefore(counter, textarea.nextSibling);
const updateCounter = () => {
const length = textarea.value.length;
counter.textContent = `${length}/${maxLength}`;
if (length > maxLength * 0.9) {
counter.classList.add('warning');
} else {
counter.classList.remove('warning');
}
};
textarea.addEventListener('input', updateCounter);
updateCounter();
}
function initRelativeTimestamps() {
// This function can be used to show relative timestamps
// For now, it's just a placeholder
}

View File

@ -343,6 +343,13 @@ p.thread-info {
background-color: #ffe0f0; /* Light pink background */ background-color: #ffe0f0; /* Light pink background */
animation: highlight-fade 2s ease-out; animation: highlight-fade 2s ease-out;
} }
/* Grouped messages (hide header for consecutive messages from same user) */
.chat-message.grouped {
margin-top: -4px;
}
.chat-message.grouped .chat-message-header {
display: none;
}
@keyframes highlight-fade { @keyframes highlight-fade {
from { from {
background-color: #f582ae; background-color: #f582ae;

View File

@ -437,7 +437,7 @@
<span id="typing-users"></span><span class="typing-dots"><span>.</span><span>.</span><span>.</span></span> <span id="typing-users"></span><span class="typing-dots"><span>.</span><span>.</span><span>.</span></span>
</div> </div>
{{range .Messages}} {{range .Messages}}
<div class="chat-message{{if .Mentions}}{{range .Mentions}}{{if eq . $.CurrentUsername}} chat-message-highlighted{{end}}{{end}}{{end}}" id="msg-{{.ID}}"> <div class="chat-message{{if .Mentions}}{{range .Mentions}}{{if eq . $.CurrentUsername}} chat-message-highlighted{{end}}{{end}}{{end}}" id="msg-{{.ID}}" data-user="{{.Username}}" data-reply="{{.ReplyTo}}">
<div class="chat-message-header"> <div class="chat-message-header">
{{if .PfpFileID.Valid}} {{if .PfpFileID.Valid}}
<img src="{{$.BasePath}}/file?id={{.PfpFileID.Int64}}" alt="PFP" class="chat-message-pfp"> <img src="{{$.BasePath}}/file?id={{.PfpFileID.Int64}}" alt="PFP" class="chat-message-pfp">
@ -627,6 +627,8 @@
} }
msgDiv.className = 'chat-message' + highlightClass; msgDiv.className = 'chat-message' + highlightClass;
msgDiv.id = 'msg-' + msg.id; msgDiv.id = 'msg-' + msg.id;
msgDiv.dataset.user = msg.username;
msgDiv.dataset.reply = msg.replyTo || 0;
let pfpHTML = `<div class="chat-message-pfp" style="background-color: #001858;"></div>`; let pfpHTML = `<div class="chat-message-pfp" style="background-color: #001858;"></div>`;
if (msg.pfpFileId && msg.pfpFileId.Valid) { 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">`; pfpHTML = `<img src="{{.BasePath}}/file?id=${msg.pfpFileId.Int64}&t=${new Date().getTime()}" alt="PFP" class="chat-message-pfp">`;
@ -647,6 +649,9 @@
`; `;
messages.appendChild(msgDiv); messages.appendChild(msgDiv);
// Apply grouping
applyGrouping();
// Scroll handling // Scroll handling
if (wasAtBottom) { if (wasAtBottom) {
messages.scrollTop = messages.scrollHeight; messages.scrollTop = messages.scrollHeight;
@ -834,6 +839,9 @@
const messagesContainer = document.getElementById('chat-messages'); const messagesContainer = document.getElementById('chat-messages');
messagesContainer.scrollTop = messagesContainer.scrollHeight; messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Apply message grouping on load
applyGrouping();
// Add scroll event listener // Add scroll event listener
messagesContainer.addEventListener('scroll', checkScrollPosition); messagesContainer.addEventListener('scroll', checkScrollPosition);
@ -909,6 +917,30 @@
// Initial check for scroll position // Initial check for scroll position
checkScrollPosition(); checkScrollPosition();
}; };
function applyGrouping() {
const container = document.getElementById('chat-messages');
const msgs = Array.from(container.querySelectorAll('.chat-message')).filter(el => el.id.startsWith('msg-'));
for (let i = 0; i < msgs.length; i++) {
const curr = msgs[i];
// Remove grouped class first
curr.classList.remove('grouped');
if (i === 0) continue; // First message is never grouped
const prev = msgs[i - 1];
const currUser = curr.dataset.user;
const prevUser = prev.dataset.user;
const currReply = parseInt(curr.dataset.reply) || -1;
// Group if same user and not a reply (reply is -1 or 0 when there's no reply)
if (currUser === prevUser && (currReply <= 0)) {
curr.classList.add('grouped');
}
}
}
</script> </script>
</body> </body>
</html> </html>