Fix errors on js and add grouped messages
parent
9749457e2f
commit
78a2875958
|
|
@ -47,10 +47,10 @@ function enableEnterToSubmit(input, form) {
|
||||||
form.requestSubmit();
|
form.requestSubmit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimistic UI for like/dislike buttons
|
// Optimistic UI for like/dislike buttons
|
||||||
document.querySelectorAll('form[action*="/like/"]').forEach(form => {
|
document.querySelectorAll('form[action*="/like/"]').forEach(form => {
|
||||||
form.addEventListener('submit', (e) => {
|
form.addEventListener('submit', (e) => {
|
||||||
const button = form.querySelector('button[type="submit"]');
|
const button = form.querySelector('button[type="submit"]');
|
||||||
if (button) {
|
if (button) {
|
||||||
|
|
@ -58,8 +58,7 @@ function enableEnterToSubmit(input, form) {
|
||||||
button.textContent = button.textContent + ' ✓';
|
button.textContent = button.textContent + ' ✓';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue