From ca4a722029ace7b935d69a38fc6e100ece8cc3dc Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Sun, 24 May 2026 10:11:41 +0200 Subject: [PATCH] comment safe guards --- public/s/js/comments.js | 120 ++++++++++++++++++++++++++++++++-------- public/s/js/upload.js | 13 ++++- 2 files changed, 110 insertions(+), 23 deletions(-) diff --git a/public/s/js/comments.js b/public/s/js/comments.js index 1ea4f2e..066beb2 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -4,6 +4,16 @@ const _f0ckDebug = (...args) => (typeof window.f0ckDebug === 'function' ? window.f0ckDebug(...args) : void 0); 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() { this.container = document.getElementById('comments-container'); this.itemId = this.container ? this.container.dataset.itemId : null; @@ -47,8 +57,24 @@ class CommentSystem { } 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.commentCache = new Map(); this._anchorScrollDone = false; // true after the first hash-anchor scroll on initial load @@ -280,6 +306,7 @@ class CommentSystem { saveState() { const state = { mainText: '', + isMainSubmitting: this.isMainSubmitting, openReplies: [], focused: null }; @@ -288,7 +315,7 @@ class CommentSystem { // 1. Save main input const mainInput = this.container.querySelector('.main-input textarea'); - if (mainInput && !this.isMainSubmitting) { + if (mainInput) { state.mainText = mainInput.value; if (document.activeElement === mainInput) { state.focused = { type: 'main', start: mainInput.selectionStart, end: mainInput.selectionEnd }; @@ -298,17 +325,19 @@ class CommentSystem { // 2. Save open replies this.container.querySelectorAll('.reply-input').forEach(form => { const parentId = form.dataset.parent; - if (!parentId || this.pendingSubmissions.has(parentId)) return; + if (!parentId) return; const textarea = form.querySelector('textarea'); const text = textarea ? textarea.value : ''; - if (parentId) { - const replyState = { parentId, text }; - if (document.activeElement === textarea) { - state.focused = { type: 'reply', parentId, start: textarea.selectionStart, end: textarea.selectionEnd }; - } - state.openReplies.push(replyState); + const replyState = { + parentId, + text, + isPending: this.pendingSubmissions.has(parentId) + }; + if (document.activeElement === textarea) { + state.focused = { type: 'reply', parentId, start: textarea.selectionStart, end: textarea.selectionEnd }; } + state.openReplies.push(replyState); }); return state; @@ -318,7 +347,7 @@ class CommentSystem { if (!this.container) return; // 1. Restore open replies - state.openReplies.forEach(({ parentId, text }) => { + state.openReplies.forEach(({ parentId, text, isPending }) => { const commentBody = this.container.querySelector(`#c${parentId} > .comment-body`); if (commentBody && !commentBody.querySelector('.reply-input')) { const div = document.createElement('div'); @@ -327,16 +356,37 @@ class CommentSystem { const newForm = commentBody.querySelector('.reply-input'); if (newForm) { 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 = ''; + } + } this.setupEmojiPicker(newForm); } } }); - // 2. Restore main input text + // 2. Restore main input text & submitting state const mainInput = this.container.querySelector('.main-input textarea'); - if (mainInput && state.mainText) { + if (mainInput) { 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 = ''; + } + } } // 3. Restore focus @@ -348,7 +398,7 @@ class CommentSystem { targetEl = this.container.querySelector(`#c${state.focused.parentId} .reply-input textarea`); } - if (targetEl) { + if (targetEl && !targetEl.disabled) { targetEl.focus(); // Ensure selection is restored after focus if (typeof targetEl.setSelectionRange === 'function') { @@ -2149,11 +2199,15 @@ class CommentSystem { this.container.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.ctrlKey) { const textarea = e.target.closest('textarea'); - if (!textarea) return; + if (!textarea || textarea.disabled) return; const wrap = textarea.closest('.comment-input'); if (!wrap) return; 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') { const textarea = e.target.closest('textarea'); if (textarea) textarea.blur(); @@ -2760,11 +2814,13 @@ class CommentSystem { const parentId = wrap.dataset.parent || null; if (!text.trim()) return; - if (submitBtn.classList.contains('loading')) return; + if (submitBtn.classList.contains('loading') || submitBtn.disabled) return; if (wrap._pendingUploads > 0) return; // Start loading state submitBtn.classList.add('loading'); + submitBtn.disabled = true; + textarea.disabled = true; const originalBtnHtml = submitBtn.innerHTML; submitBtn.innerHTML = ''; @@ -3038,15 +3094,35 @@ class CommentSystem { } _finishSubmit(btn, originalHtml, parentId) { - if (btn) { - btn.classList.remove('loading'); - btn.innerHTML = originalHtml; - } if (parentId) { this.pendingSubmissions.delete(parentId); } else { 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; + } + } } diff --git a/public/s/js/upload.js b/public/s/js/upload.js index b781593..ecf5483 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -110,6 +110,8 @@ window.initUploadForm = (selector) => { if (form._f0ckInit) return form._f0ckUploader; form._f0ckInit = true; + let isUploading = false; + // Use querySelector to find elements within this specific form instance const fileInput = form.querySelector('.file-input'); const dropZone = form.querySelector('.drop-zone'); @@ -643,6 +645,11 @@ window.initUploadForm = (selector) => { }; const updateSubmitButton = () => { + if (isUploading) { + if (submitBtn) submitBtn.disabled = true; + return; + } + const isShitpost = !!window.f0ckShitpostMode; const rating = form.querySelector('input[name="rating"]:checked'); @@ -1795,7 +1802,7 @@ window.initUploadForm = (selector) => { if (e && e.preventDefault) e.preventDefault(); // If already uploading, don't start again - if (submitBtn && submitBtn.disabled && submitBtn.querySelector('.btn-loading')?.style.display === 'inline') { + if (isUploading) { return; } @@ -1826,6 +1833,7 @@ window.initUploadForm = (selector) => { const isOc = form.querySelector('#upload-oc-checkbox')?.checked || false; const setBtnLoading = (text) => { + isUploading = true; if (!submitBtn) return; submitBtn.disabled = true; const btnText = submitBtn.querySelector('.btn-text'); @@ -1838,12 +1846,14 @@ window.initUploadForm = (selector) => { }; const restoreBtn = () => { + isUploading = false; if (!submitBtn) return; submitBtn.disabled = false; const btnText = submitBtn.querySelector('.btn-text'); const btnLoading = submitBtn.querySelector('.btn-loading'); if (btnText) btnText.style.display = 'inline'; if (btnLoading) btnLoading.style.display = 'none'; + updateSubmitButton(); }; if (activeMode === 'url') { @@ -2106,6 +2116,7 @@ window.initUploadForm = (selector) => { handleFile: handleFile, performUpload: performUpload, reset: () => { + isUploading = false; form.reset(); tags = []; selectedFiles = [];