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