diff --git a/assets/category_submit.js b/assets/category_submit.js
index 69b586c..ea699ff 100644
--- a/assets/category_submit.js
+++ b/assets/category_submit.js
@@ -1,41 +1,91 @@
-document.addEventListener('DOMContentLoaded', function() {
+(function() {
if (window.location.pathname !== '/challenges') return;
+ function injectSubmissionBoxes(config) {
+ const enabledCategories = config.categories || [];
+ // Find all category headers
+ const headers = document.querySelectorAll('.category-header');
+
+ headers.forEach(header => {
+ const catName = header.textContent.trim();
+
+ // 1. Check if category is enabled
+ // 2. Check if we already injected a box (to prevent infinite loops)
+ const alreadyInjected = header.nextElementSibling &&
+ header.nextElementSibling.classList.contains('custom-cat-sub-box');
+
+ if (enabledCategories.includes(catName) && !alreadyInjected) {
+ const div = document.createElement('div');
+ div.className = "custom-cat-sub-box input-group mt-2 mb-4 p-3 bg-light border rounded";
+ div.style.maxWidth = "500px"; // Keep it tidy
+ div.innerHTML = `
+
+
+
+
+ `;
+
+ header.after(div);
+
+ // Submission logic
+ div.querySelector('button').onclick = function() {
+ const btn = this;
+ const input = document.getElementById(`in-${catName.replace(/\s+/g, '-')}`);
+ const val = input.value.trim();
+ if (!val) return;
+
+ btn.disabled = true;
+
+ const params = new URLSearchParams({
+ submission: val,
+ category: catName,
+ nonce: init.csrfNonce
+ });
+
+ fetch('/category_submit', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'},
+ body: params
+ })
+ .then(r => r.json())
+ .then(data => {
+ alert(data.message);
+ if (data.success) {
+ location.reload(); // Refresh to show the checkmark on the challenge
+ }
+ btn.disabled = false;
+ })
+ .catch(() => {
+ alert("Error submitting flag.");
+ btn.disabled = false;
+ });
+ };
+ }
+ });
+ }
+
+ // Initialize the plugin
fetch('/category_submit/config')
- .then(r => r.json())
- .then(config => {
- if (!config.categories) return;
+ .then(r => r.json())
+ .then(config => {
+ // Run once on load
+ injectSubmissionBoxes(config);
- function inject() {
- document.querySelectorAll('.category-header').forEach(header => {
- const catName = header.textContent.trim();
- if (config.categories.includes(catName) && !header.querySelector('.custom-box')) {
- const div = document.createElement('div');
- div.className = "custom-box input-group mt-2 mb-4 p-2 bg-light border rounded";
- div.innerHTML = `
-
-
- `;
- header.after(div);
-
- div.querySelector('button').onclick = function() {
- const btn = this;
- const val = document.getElementById(`in-${catName}`).value;
- btn.disabled = true;
-
- const body = new URLSearchParams({ submission: val, category: catName, nonce: init.csrfNonce });
- fetch('/category_submit', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: body })
- .then(r => r.json())
- .then(data => {
- alert(data.message);
- if(data.success) location.reload();
- btn.disabled = false;
- });
- };
- }
+ // Observe the challenge board for changes (e.g. category filtering/loading)
+ // We use a debounce timer to avoid the "Loading Forever" infinite loop
+ let timeout;
+ const observer = new MutationObserver(() => {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ // Temporarily disconnect to avoid observing our own changes
+ observer.disconnect();
+ injectSubmissionBoxes(config);
+ // Re-observe after injection
+ observer.observe(document.body, { childList: true, subtree: true });
+ }, 200);
});
- }
- // Watch for CTFd's dynamic challenge loading
- new MutationObserver(inject).observe(document.body, { childList: true, subtree: true });
- });
-});
\ No newline at end of file
+
+ observer.observe(document.body, { childList: true, subtree: true });
+ })
+ .catch(err => console.error("Could not load category submission config", err));
+})();
\ No newline at end of file