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 = [];