${parentId ? `` : ''}
`;
}
setupHoverPreviews() {
if (CommentSystem.hoverPreviewsAttached) return;
CommentSystem.hoverPreviewsAttached = true;
// Hover for Comment Context Links (>>ID) - Global delegation for nested previews (inception)
this.mouseCurrentLevel = -1;
this.currentHoverLink = null;
document.addEventListener('mouseover', (e) => {
const contextLink = e.target.closest('.comment-context-link');
const popup = e.target.closest('.comment-preview-popup');
if (contextLink || popup) {
if (this.previewCloseTimer) {
clearTimeout(this.previewCloseTimer);
this.previewCloseTimer = null;
}
}
if (contextLink) {
// Ignore mouseover for previews on mobile touch devices to prevent tap-to-preview
// We handle mobile previews via the touchstart timer instead.
if (window.matchMedia('(pointer: coarse)').matches) return;
if (this.currentHoverLink === contextLink) return;
this.currentHoverLink = contextLink;
if (this.previewOpenTimer) clearTimeout(this.previewOpenTimer);
this.previewOpenTimer = setTimeout(() => {
this.showCommentPreview(contextLink, e);
}, 150); // 150ms dwell time to prevent flickering while moving mouse
} else {
if (this.previewOpenTimer) {
clearTimeout(this.previewOpenTimer);
this.previewOpenTimer = null;
}
this.currentHoverLink = null;
const level = popup ? parseInt(popup.dataset.level || 0) : -1;
// If we move back to a parent level or blank area of a popup, close its children
// but use a delay so the user can reach the child popup if they are moving towards it.
if (popup && !contextLink) {
this.previewCloseTimer = setTimeout(() => {
this.closePreviewsAboveLevel(level);
}, 400);
}
this.mouseCurrentLevel = level;
}
});
document.addEventListener('mouseout', (e) => {
const contextLink = e.target.closest('.comment-context-link');
const popup = e.target.closest('.comment-preview-popup');
if (contextLink) {
if (this.previewOpenTimer) {
clearTimeout(this.previewOpenTimer);
this.previewOpenTimer = null;
}
this.currentHoverLink = null;
}
if (contextLink || popup) {
if (this.previewCloseTimer) clearTimeout(this.previewCloseTimer);
this.previewCloseTimer = setTimeout(() => {
this.closePreviewsAboveLevel(-1);
this.mouseCurrentLevel = -1;
}, 400);
}
});
// Mobile Touch Support: Touch-and-hold to preview
let touchPreviewTimer = null;
document.addEventListener('touchstart', (e) => {
const contextLink = e.target.closest('.comment-context-link');
if (contextLink) {
if (touchPreviewTimer) clearTimeout(touchPreviewTimer);
touchPreviewTimer = setTimeout(() => {
this.showCommentPreview(contextLink, e);
}, 150); // 150ms hold to trigger preview on mobile
}
}, { passive: true });
document.addEventListener('touchmove', () => {
if (touchPreviewTimer) {
clearTimeout(touchPreviewTimer);
touchPreviewTimer = null;
}
}, { passive: true });
document.addEventListener('touchend', () => {
if (touchPreviewTimer) {
clearTimeout(touchPreviewTimer);
touchPreviewTimer = null;
}
}, { passive: true });
// Global click listener to close popups (useful for mobile dismissal)
document.addEventListener('click', (e) => {
const isLink = e.target.closest('.comment-context-link');
const isPopup = e.target.closest('.comment-preview-popup');
if (!isLink && !isPopup) {
this.closePreviewsAboveLevel(-1);
}
});
}
setupDelegatedEvents() {
_f0ckDebug('[DEBUG] Setting up delegated events for container:', this.container);
if (!this.container) return;
// Ctrl+Enter to submit comment
this.container.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
const textarea = e.target.closest('textarea');
if (!textarea) return;
const wrap = textarea.closest('.comment-input');
if (!wrap) return;
const submitBtn = wrap.querySelector('.submit-comment');
if (submitBtn) submitBtn.click();
} else if (e.key === 'Escape') {
const textarea = e.target.closest('textarea');
if (textarea) textarea.blur();
}
});
// Single Change Listener for Sort
this.container.addEventListener('change', (e) => {
if (e.target.id === 'comment-sort') {
this.sort = e.target.value;
this.loadComments();
}
});
// Single Click Listener for Everything
this.container.addEventListener('click', async (e) => {
_f0ckDebug('[DEBUG] Click on container:', e.target);
const target = e.target;
// Toggling Scroll Action
const scrollBtn = target.closest('.scroll-to-bottom');
if (scrollBtn) {
if (scrollBtn.classList.contains('is-at-bottom')) {
// Scroll to Top of the page
window.scrollTo({
top: 0,
behavior: 'smooth'
});
} else {
// Scroll to Bottom of comments
const bottomElement = this.container.querySelector('.main-input') || this.container.querySelector('.lock-notice') || this.container.querySelector('.login-placeholder');
if (bottomElement) {
bottomElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
this.container.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
}
return;
}
// Submit Comment
if (target.matches('.submit-comment')) {
this.handleSubmit(e);
return;
}
// Cancel Reply
if (target.matches('.cancel-reply')) {
const form = target.closest('.reply-input');
if (form) form.remove();
return;
}
// Load full comment (expand truncated)
const loadFullBtn = target.closest('.load-full-comment-btn');
if (loadFullBtn) {
const contentEl = loadFullBtn.closest('.comment-content');
if (contentEl) {
const fullContent = contentEl.dataset.raw;
if (fullContent) {
contentEl.innerHTML = this.renderCommentContent(fullContent, null, true);
}
}
return;
}
// Comment Context Link (>>ID)
const contextLink = target.closest('.comment-context-link');
if (contextLink) {
e.preventDefault();
const targetId = contextLink.dataset.id;
this.scrollToComment(targetId, 0, true);
// Highlight effect
const targetEl = document.getElementById('c' + targetId);
if (targetEl) {
targetEl.classList.add('highlight-comment');
setTimeout(() => targetEl.classList.remove('highlight-comment'), 2000);
}
return;
}
// User Delete
const delBtn = target.closest('.delete-btn');
if (delBtn) {
if (delBtn.dataset.confirming !== 'true') {
delBtn.dataset.confirming = 'true';
const originalText = delBtn.innerHTML;
delBtn.innerHTML = 'Sure?';
delBtn.classList.add('btn-danger'); // Optional styling
setTimeout(() => {
delBtn.dataset.confirming = 'false';
delBtn.innerHTML = originalText;
delBtn.classList.remove('btn-danger');
}, 3000);
return;
}
const id = delBtn.dataset.id;
const res = await fetch(`/api/comments/${id}/delete`, {
method: 'POST',
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
});
const json = await res.json();
if (json.success) this.loadComments();
else alert('Failed to delete: ' + (json.message || 'Error'));
return;
}
// Admin Delete
const adminDelBtn = target.closest('.admin-delete-btn');
if (adminDelBtn) {
if (typeof ModAction === 'undefined') return alert('Error: ModAction module not loaded. Are you a moderator?');
const id = adminDelBtn.dataset.id;
ModAction.confirm('Delete Comment', `Are you sure you want to delete comment