comment safe guards

This commit is contained in:
2026-05-24 10:11:41 +02:00
parent bb4125601f
commit ca4a722029
2 changed files with 110 additions and 23 deletions

View File

@@ -4,6 +4,16 @@
const _f0ckDebug = (...args) => (typeof window.f0ckDebug === 'function' ? window.f0ckDebug(...args) : void 0); const _f0ckDebug = (...args) => (typeof window.f0ckDebug === 'function' ? window.f0ckDebug(...args) : void 0);
class CommentSystem { class CommentSystem {
get isMainSubmitting() {
return this._globalState ? this._globalState.isMainSubmitting : false;
}
set isMainSubmitting(val) {
if (this._globalState) this._globalState.isMainSubmitting = val;
}
get pendingSubmissions() {
return this._globalState ? this._globalState.pendingSubmissions : new Set();
}
constructor() { constructor() {
this.container = document.getElementById('comments-container'); this.container = document.getElementById('comments-container');
this.itemId = this.container ? this.container.dataset.itemId : null; this.itemId = this.container ? this.container.dataset.itemId : null;
@@ -47,8 +57,24 @@ class CommentSystem {
} }
this.initialLoadDone = false; this.initialLoadDone = false;
this.pendingSubmissions = new Set();
this.isMainSubmitting = false; // Retrieve or initialize global submission state for this item to survive f0ck:contentLoaded re-inits
if (this.itemId) {
window._f0ckActiveSubmissions = window._f0ckActiveSubmissions || {};
if (!window._f0ckActiveSubmissions[this.itemId]) {
window._f0ckActiveSubmissions[this.itemId] = {
isMainSubmitting: false,
pendingSubmissions: new Set()
};
}
this._globalState = window._f0ckActiveSubmissions[this.itemId];
} else {
this._globalState = {
isMainSubmitting: false,
pendingSubmissions: new Set()
};
}
this.scrollListenerAdded = false; this.scrollListenerAdded = false;
this.commentCache = new Map(); this.commentCache = new Map();
this._anchorScrollDone = false; // true after the first hash-anchor scroll on initial load this._anchorScrollDone = false; // true after the first hash-anchor scroll on initial load
@@ -280,6 +306,7 @@ class CommentSystem {
saveState() { saveState() {
const state = { const state = {
mainText: '', mainText: '',
isMainSubmitting: this.isMainSubmitting,
openReplies: [], openReplies: [],
focused: null focused: null
}; };
@@ -288,7 +315,7 @@ class CommentSystem {
// 1. Save main input // 1. Save main input
const mainInput = this.container.querySelector('.main-input textarea'); const mainInput = this.container.querySelector('.main-input textarea');
if (mainInput && !this.isMainSubmitting) { if (mainInput) {
state.mainText = mainInput.value; state.mainText = mainInput.value;
if (document.activeElement === mainInput) { if (document.activeElement === mainInput) {
state.focused = { type: 'main', start: mainInput.selectionStart, end: mainInput.selectionEnd }; state.focused = { type: 'main', start: mainInput.selectionStart, end: mainInput.selectionEnd };
@@ -298,17 +325,19 @@ class CommentSystem {
// 2. Save open replies // 2. Save open replies
this.container.querySelectorAll('.reply-input').forEach(form => { this.container.querySelectorAll('.reply-input').forEach(form => {
const parentId = form.dataset.parent; const parentId = form.dataset.parent;
if (!parentId || this.pendingSubmissions.has(parentId)) return; if (!parentId) return;
const textarea = form.querySelector('textarea'); const textarea = form.querySelector('textarea');
const text = textarea ? textarea.value : ''; const text = textarea ? textarea.value : '';
if (parentId) { const replyState = {
const replyState = { parentId, text }; parentId,
text,
isPending: this.pendingSubmissions.has(parentId)
};
if (document.activeElement === textarea) { if (document.activeElement === textarea) {
state.focused = { type: 'reply', parentId, start: textarea.selectionStart, end: textarea.selectionEnd }; state.focused = { type: 'reply', parentId, start: textarea.selectionStart, end: textarea.selectionEnd };
} }
state.openReplies.push(replyState); state.openReplies.push(replyState);
}
}); });
return state; return state;
@@ -318,7 +347,7 @@ class CommentSystem {
if (!this.container) return; if (!this.container) return;
// 1. Restore open replies // 1. Restore open replies
state.openReplies.forEach(({ parentId, text }) => { state.openReplies.forEach(({ parentId, text, isPending }) => {
const commentBody = this.container.querySelector(`#c${parentId} > .comment-body`); const commentBody = this.container.querySelector(`#c${parentId} > .comment-body`);
if (commentBody && !commentBody.querySelector('.reply-input')) { if (commentBody && !commentBody.querySelector('.reply-input')) {
const div = document.createElement('div'); const div = document.createElement('div');
@@ -327,16 +356,37 @@ class CommentSystem {
const newForm = commentBody.querySelector('.reply-input'); const newForm = commentBody.querySelector('.reply-input');
if (newForm) { if (newForm) {
const textarea = newForm.querySelector('textarea'); const textarea = newForm.querySelector('textarea');
if (textarea) textarea.value = text; if (textarea) {
textarea.value = text;
if (isPending) textarea.disabled = true;
}
if (isPending) {
const submitBtn = newForm.querySelector('.submit-comment');
if (submitBtn) {
submitBtn.classList.add('loading');
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
}
}
this.setupEmojiPicker(newForm); this.setupEmojiPicker(newForm);
} }
} }
}); });
// 2. Restore main input text // 2. Restore main input text & submitting state
const mainInput = this.container.querySelector('.main-input textarea'); const mainInput = this.container.querySelector('.main-input textarea');
if (mainInput && state.mainText) { if (mainInput) {
mainInput.value = state.mainText; mainInput.value = state.mainText;
if (state.isMainSubmitting) {
mainInput.disabled = true;
const wrap = mainInput.closest('.comment-input');
const submitBtn = wrap ? wrap.querySelector('.submit-comment') : null;
if (submitBtn) {
submitBtn.classList.add('loading');
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
}
}
} }
// 3. Restore focus // 3. Restore focus
@@ -348,7 +398,7 @@ class CommentSystem {
targetEl = this.container.querySelector(`#c${state.focused.parentId} .reply-input textarea`); targetEl = this.container.querySelector(`#c${state.focused.parentId} .reply-input textarea`);
} }
if (targetEl) { if (targetEl && !targetEl.disabled) {
targetEl.focus(); targetEl.focus();
// Ensure selection is restored after focus // Ensure selection is restored after focus
if (typeof targetEl.setSelectionRange === 'function') { if (typeof targetEl.setSelectionRange === 'function') {
@@ -2149,11 +2199,15 @@ class CommentSystem {
this.container.addEventListener('keydown', (e) => { this.container.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.ctrlKey) { if (e.key === 'Enter' && e.ctrlKey) {
const textarea = e.target.closest('textarea'); const textarea = e.target.closest('textarea');
if (!textarea) return; if (!textarea || textarea.disabled) return;
const wrap = textarea.closest('.comment-input'); const wrap = textarea.closest('.comment-input');
if (!wrap) return; if (!wrap) return;
const submitBtn = wrap.querySelector('.submit-comment'); const submitBtn = wrap.querySelector('.submit-comment');
if (submitBtn) submitBtn.click(); if (submitBtn && !submitBtn.disabled && !submitBtn.classList.contains('loading')) {
e.preventDefault();
e.stopPropagation();
submitBtn.click();
}
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
const textarea = e.target.closest('textarea'); const textarea = e.target.closest('textarea');
if (textarea) textarea.blur(); if (textarea) textarea.blur();
@@ -2760,11 +2814,13 @@ class CommentSystem {
const parentId = wrap.dataset.parent || null; const parentId = wrap.dataset.parent || null;
if (!text.trim()) return; if (!text.trim()) return;
if (submitBtn.classList.contains('loading')) return; if (submitBtn.classList.contains('loading') || submitBtn.disabled) return;
if (wrap._pendingUploads > 0) return; if (wrap._pendingUploads > 0) return;
// Start loading state // Start loading state
submitBtn.classList.add('loading'); submitBtn.classList.add('loading');
submitBtn.disabled = true;
textarea.disabled = true;
const originalBtnHtml = submitBtn.innerHTML; const originalBtnHtml = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>'; submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
@@ -3038,15 +3094,35 @@ class CommentSystem {
} }
_finishSubmit(btn, originalHtml, parentId) { _finishSubmit(btn, originalHtml, parentId) {
if (btn) {
btn.classList.remove('loading');
btn.innerHTML = originalHtml;
}
if (parentId) { if (parentId) {
this.pendingSubmissions.delete(parentId); this.pendingSubmissions.delete(parentId);
} else { } else {
this.isMainSubmitting = false; this.isMainSubmitting = false;
} }
// Surgical lookup of the active wrap in the live DOM
let activeWrap = null;
if (this.container) {
if (parentId) {
activeWrap = this.container.querySelector(`#c${parentId} .reply-input`);
} else {
activeWrap = this.container.querySelector('.main-input');
}
}
const wrap = activeWrap || (btn ? btn.closest('.comment-input') : null);
if (wrap) {
const activeBtn = wrap.querySelector('.submit-comment');
const activeTextarea = wrap.querySelector('textarea');
if (activeBtn) {
activeBtn.classList.remove('loading');
activeBtn.disabled = false;
activeBtn.innerHTML = originalHtml;
}
if (activeTextarea) {
activeTextarea.disabled = false;
}
}
} }

View File

@@ -110,6 +110,8 @@ window.initUploadForm = (selector) => {
if (form._f0ckInit) return form._f0ckUploader; if (form._f0ckInit) return form._f0ckUploader;
form._f0ckInit = true; form._f0ckInit = true;
let isUploading = false;
// Use querySelector to find elements within this specific form instance // Use querySelector to find elements within this specific form instance
const fileInput = form.querySelector('.file-input'); const fileInput = form.querySelector('.file-input');
const dropZone = form.querySelector('.drop-zone'); const dropZone = form.querySelector('.drop-zone');
@@ -643,6 +645,11 @@ window.initUploadForm = (selector) => {
}; };
const updateSubmitButton = () => { const updateSubmitButton = () => {
if (isUploading) {
if (submitBtn) submitBtn.disabled = true;
return;
}
const isShitpost = !!window.f0ckShitpostMode; const isShitpost = !!window.f0ckShitpostMode;
const rating = form.querySelector('input[name="rating"]:checked'); const rating = form.querySelector('input[name="rating"]:checked');
@@ -1795,7 +1802,7 @@ window.initUploadForm = (selector) => {
if (e && e.preventDefault) e.preventDefault(); if (e && e.preventDefault) e.preventDefault();
// If already uploading, don't start again // If already uploading, don't start again
if (submitBtn && submitBtn.disabled && submitBtn.querySelector('.btn-loading')?.style.display === 'inline') { if (isUploading) {
return; return;
} }
@@ -1826,6 +1833,7 @@ window.initUploadForm = (selector) => {
const isOc = form.querySelector('#upload-oc-checkbox')?.checked || false; const isOc = form.querySelector('#upload-oc-checkbox')?.checked || false;
const setBtnLoading = (text) => { const setBtnLoading = (text) => {
isUploading = true;
if (!submitBtn) return; if (!submitBtn) return;
submitBtn.disabled = true; submitBtn.disabled = true;
const btnText = submitBtn.querySelector('.btn-text'); const btnText = submitBtn.querySelector('.btn-text');
@@ -1838,12 +1846,14 @@ window.initUploadForm = (selector) => {
}; };
const restoreBtn = () => { const restoreBtn = () => {
isUploading = false;
if (!submitBtn) return; if (!submitBtn) return;
submitBtn.disabled = false; submitBtn.disabled = false;
const btnText = submitBtn.querySelector('.btn-text'); const btnText = submitBtn.querySelector('.btn-text');
const btnLoading = submitBtn.querySelector('.btn-loading'); const btnLoading = submitBtn.querySelector('.btn-loading');
if (btnText) btnText.style.display = 'inline'; if (btnText) btnText.style.display = 'inline';
if (btnLoading) btnLoading.style.display = 'none'; if (btnLoading) btnLoading.style.display = 'none';
updateSubmitButton();
}; };
if (activeMode === 'url') { if (activeMode === 'url') {
@@ -2106,6 +2116,7 @@ window.initUploadForm = (selector) => {
handleFile: handleFile, handleFile: handleFile,
performUpload: performUpload, performUpload: performUpload,
reset: () => { reset: () => {
isUploading = false;
form.reset(); form.reset();
tags = []; tags = [];
selectedFiles = []; selectedFiles = [];