let questionid=""; window.onload = () => resetState(); document.addEventListener('DOMContentLoaded', () => { resetState(); jsonDataFetched = false; }); function toggleModal(){document.getElementById("modal").classList.toggle("hidden")} function toggleMS(){document.getElementById("markscheme").classList.toggle("hidden")} function toggleR(){document.getElementById("report").classList.toggle("hidden")} function toggleHelp(){document.getElementById("helpmenu").classList.toggle("hidden")} function toggleDownAllQs(){document.getElementById("addalltoPDFbtn").classList.remove('hidden');document.getElementById("generatePDFbtn").classList.remove('hidden')} function toggleFilters(){document.querySelector(".selectables").classList.remove('hidden')}; function checkWidth() { const btn1 = document.getElementById('generatePDFbtn'); const btn2 = document.getElementById('addalltoPDFbtn'); if (window.innerWidth <= 480) { btn1.style.display = 'none'; btn2.style.display = 'none'; } else { btn1.style.display = ''; btn2.style.display = ''; } } window.addEventListener('resize', checkWidth); function toggleDarkMode() { var body = document.body; var head = document.head; var toggleButton = document.getElementById("darkmodebtn"); if (localStorage.getItem("darkMode") === "disabled") { body.classList.add("dark-mode"); head.classList.add("dark-mode"); localStorage.setItem("darkMode", "enabled"); toggleButton.innerText = "Light Mode"; } else { body.classList.remove("dark-mode"); head.classList.remove("dark-mode"); localStorage.setItem("darkMode", "disabled"); toggleButton.innerText = "Dark Mode"; } } document.addEventListener("DOMContentLoaded", () => { const darkModeStatus = localStorage.getItem("darkMode"); const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)").matches; if (darkModeStatus === "enabled") { document.body.classList.add("dark-mode"); document.head.classList.add("dark-mode"); document.getElementById("darkmodebtn").innerText = "Light Mode"; } else if (darkModeStatus === "disabled") { document.body.classList.remove("dark-mode"); document.head.classList.remove("dark-mode"); document.getElementById("darkmodebtn").innerText = "Dark Mode"; } else if (prefersDarkScheme) { document.body.classList.add("dark-mode"); document.head.classList.add("dark-mode"); document.getElementById("darkmodebtn").innerText = "Light Mode"; localStorage.setItem("darkMode", "enabled"); } else { document.body.classList.remove("dark-mode"); document.head.classList.remove("dark-mode"); document.getElementById("darkmodebtn").innerText = "Dark Mode"; localStorage.setItem("darkMode", "disabled"); } }); /*function startRandomTimerLoop() { var randomTime = Math.floor(11 * Math.random()) + 20; setTimeout(() => { alert("Provided by pirateIB\nhttps://pirateib.xyz"); startRandomTimerLoop(); }, 60 * randomTime * 1000); } window.onload = function() { setTimeout(() => { startRandomTimerLoop(); }, 300000); };*/ /*function generatePDF() { const selectedQuestionIds = JSON.parse(sessionStorage.getItem("selectedQuestionIds")) || []; if (0 === selectedQuestionIds.length) return alert("Select some questions first!"); const printWindow = window.open("", "_blank"); printWindow.document.write('\n \n \n QuestionBank Test\n \n \n \n \n '); let concatenatedHTML = ""; let markschemesHTML = ''; selectedQuestionIds.forEach((questionId => { const questionDiv = document.getElementById(questionId), h3 = questionDiv.querySelector("h3"), squareContainer = questionDiv.querySelector(".square-container"); concatenatedHTML += h3.outerHTML + squareContainer.outerHTML const msDiv = document.querySelector(`[id*="markscheme-${questionId}"]`); const cloneMsDiv = msDiv.cloneNode(true); cloneMsDiv.classList.remove('hidden'); markschemesHTML += h3.outerHTML + cloneMsDiv.outerHTML; })), printWindow.document.write(`

Questions


${concatenatedHTML}

Markschemes


${markschemesHTML}`), printWindow.document.write("\n \n \n "), printWindow.document.close(); setTimeout(() => { printWindow.print(), printWindow.onafterprint = () => printWindow.close(); }, 1000);}*/ function generatePDF() { const selectedQuestionIds = JSON.parse(sessionStorage.getItem("selectedQuestionIds")) || []; if (0 === selectedQuestionIds.length) return alert("Select some questions first!"); let includeMarkschemes = null; while (includeMarkschemes !== "yes" && includeMarkschemes !== "no") { includeMarkschemes = prompt("Do you want to include markschemes? (yes/no)").toLowerCase(); if (includeMarkschemes !== "yes" && includeMarkschemes !== "no") { alert("Please answer 'yes' or 'no'."); } } const printWindow = window.open("", "_blank"); printWindow.document.write('\n \n \n QuestionBank Test\n \n \n \n \n '); let concatenatedHTML = ""; let markschemesHTML = ''; selectedQuestionIds.forEach((questionId) => { const questionDiv = document.getElementById(questionId), h3 = questionDiv.querySelector("h3"), squareContainer = questionDiv.querySelector(".square-container"); concatenatedHTML += h3.outerHTML + squareContainer.outerHTML; if (includeMarkschemes === "yes") { const msDiv = document.querySelector(`[id*="markscheme-${questionId} "]`); const cloneMsDiv = msDiv.cloneNode(true); cloneMsDiv.classList.remove('hidden'); markschemesHTML += h3.outerHTML + cloneMsDiv.outerHTML; } }); printWindow.document.write(`

Questions


${concatenatedHTML}`); if (includeMarkschemes === "yes") { printWindow.document.write(`

Markschemes


${markschemesHTML}`); } printWindow.document.write("\n \n \n "); printWindow.document.close(); setTimeout(() => { printWindow.print(); printWindow.onafterprint = () => printWindow.close(); }, 1000); } function addalltoPDF() { const buttons = document.querySelectorAll('.btn-secondary'); buttons.forEach(button => { if (button.textContent.trim() === "Add to PDF" && !button.parentElement.parentElement.classList.contains('hidden')) { button.click(); } }); } let jsonDataFetched = false; let jsonData = null; let currentFileName = null; let topics = []; const domCache = { rightCol: document.getElementById("right-col"), msbox: document.getElementById("markscheme-box"), reportbox: document.getElementById("report-box"), msbox2: document.getElementById("markscheme-box2"), repbox2: document.getElementById("report-box2"), leftCol: document.getElementById('left-col') }; document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { const modal = document.getElementById('modal'); const helpmenu = document.getElementById('helpmenu'); if (modal && !modal.classList.contains('hidden')) { toggleModal(); } else if (helpmenu && !helpmenu.classList.contains('hidden')) { toggleHelp(); } } }); const fileNameMap = { 'bioqb': 'Biology QB.json', 'bioqb25-split': 'Biology 2025 QB split.json', 'bioqb25-merged': 'Biology 2025 QB merged.json', 'bioqb25-full': 'Biology 2025 QB full.json', 'bmqb': 'Business Management QB.json', 'chemqb': 'Chemistry QB.json', 'chemqb25-split': 'Chemistry 2025 QB split.json', 'chemqb25-merged': 'Chemistry 2025 QB merged.json', 'chemqb25-full': 'Chemistry 2025 QB full.json', 'compsciqb': 'Computer Science QB.json', 'destechqb': 'Design Technology QB.json', 'digsocqb': 'Digital Society QB.json', 'econqb': 'Economics QB.json', 'essqb': 'ESS QB.json', 'geoqb': 'Geography QB.json', 'histqb': 'History QB.json', 'mathaaqb': 'Math AA QB.json', 'mathaiqb': 'Math AI QB.json', 'phyqb': 'Physics QB.json', 'phyqb25-split': 'Physics 2025 QB split.json', 'phyqb25-merged': 'Physics 2025 QB merged.json', 'phyqb25-full': 'Physics 2025 QB full.json', 'psychqb': 'Psychology QB.json', 'sehsqb': 'SEHS QB.json' }; function createSVGElement(questionid) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const attributes = { "width": "2rem", "height": "2rem", "viewBox": "0 0 24 24", "fill": "none", "stroke": "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }; Object.entries(attributes).forEach(([key, value]) => { svg.setAttribute(key, value); }); svg.classList.add("cursor-pointer", "text-primary", "hidden"); svg.innerHTML = ` `; return svg; } function showLoading() { document.getElementById('loadingBanner').style.display = 'block'; } function hideLoading() { document.getElementById('loadingBanner').style.display = 'none'; } function openDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open('myDatabase', 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains('myStore')) { db.createObjectStore('myStore'); } }; request.onsuccess = (event) => { resolve(event.target.result); }; request.onerror = (event) => { reject('Error opening database:', event.target.error); }; }); } async function loadJSON(filename) { showLoading(); const db = await openDatabase(); const cachedData = await new Promise((resolve, reject) => { const transaction = db.transaction('myStore', 'readonly'); const store = transaction.objectStore('myStore'); const request = store.get(filename); request.onsuccess = (event) => { resolve(event.target.result); }; request.onerror = (event) => { reject('Error fetching data from IndexedDB:', event.target.error); }; }); if (cachedData) { hideLoading(); processData(cachedData, filename); return; } try { const response = await fetch(`https://pub-59370068cd854c158959e7ca4578e5bd.r2.dev/${filename}`); // ../assets/jsonqb/ if (!response.ok) { throw new Error('Network response was not ok'); } const data = await response.json(); await new Promise((resolve, reject) => { const transaction = db.transaction('myStore', 'readwrite'); const store = transaction.objectStore('myStore'); const request = store.put(data, filename); request.onsuccess = () => { resolve(); }; request.onerror = (event) => { reject('Error storing data in IndexedDB:', event.target.error); }; }); sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('visibleIDs', '[]'); setTimeout(() => { processData(data, filename); hideLoading(); sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('visibleIDs', '[]'); }, 0); } catch (error) { console.error('Error fetching JSON:', error); hideLoading(); sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('visibleIDs', '[]'); } } function processData(data, filename) { jsonDataFetched = true; currentFileName = filename; jsonData = data; topics = [...new Set(data.flatMap(item => item.topics))].sort(); subtopics = [...new Set(data.flatMap(item => item.subtopics))].sort(); renderTopics(); renderSubtopics(); const fragment = document.createDocumentFragment(); data.forEach(item => { const { Question: question, question_id: questionid, Markscheme: markscheme, 'Examiners report': report, topics, subtopics } = item; const bigQuestionBox = document.createElement("div"); bigQuestionBox.id = questionid; /*const allClasses = [...topics.map(t => t.trim()), subtopics, "hidden"]; bigQuestionBox.classList.add(...allClasses);*/ const allClasses = [ ...topics.map(t => t.trim()).filter(t => t), ...(typeof subtopics === "string" ? [subtopics] : []), "hidden" ]; bigQuestionBox.classList.add(...allClasses); const btnContainer = document.createElement("div"); btnContainer.classList.add("btn-container"); function toggleMScont(questionid) { const markschemeContainer = document.getElementById(`markscheme-${questionid} ${currentFileName}`); toggleMSSvg.classList.toggle('hidden'); markschemeContainer.classList.toggle('hidden'); activeQuestionId = markschemeContainer.classList.contains('hidden') ? null : questionid; } function toggleRepcont(questionid) { const reportContainer = document.getElementById(`report-${questionid} ${currentFileName}`); toggleRepSvg.classList.toggle('hidden'); reportContainer.classList.toggle('hidden'); activeQuestionId = reportContainer.classList.contains('hidden') ? null : questionid; } const buttons = [ { text: "Markscheme", handler: () => { toggleMS(); toggleMScont(questionid); } }, { text: "Examiners report", handler: () => { toggleR(); toggleRepcont(questionid); } }, { text: "Add to PDF", handler: createPDFButtonHandler(questionid) } ].map(createButton); buttons.forEach(button => btnContainer.appendChild(button)); const content = `

${questionid}

Topics: ${topics.join(', ')}

Subtopics ${subtopics.join(', ')}

${question}
`; bigQuestionBox.innerHTML = content; bigQuestionBox.querySelector('h3').after(btnContainer); if (markscheme) { createContainer('markscheme', questionid, filename, markscheme, domCache.msbox); } if (report) { createContainer('report', questionid, filename, report, domCache.reportbox); } /********** Removed ID appending method since it was slowing down interaction with the X svg *********/ const toggleMSSvg = createSVGElement(questionid); //toggleMSSvg.id = `toggleMSSvg-${questionid}`; const toggleRepSvg = createSVGElement(questionid); //toggleRepSvg.id = `toggleRepSvg-${questionid}`; domCache.msbox2.appendChild(toggleMSSvg); domCache.repbox2.appendChild(toggleRepSvg); /*toggleMSSvg.addEventListener('click', () => { toggleMScont(questionid); toggleMS(); });*/ /********** Identifying by active question instead **********/ let activeQuestionId = null; const handleToggle = () => { if (activeQuestionId) { const markschemeContainer = document.getElementById(`markscheme-${activeQuestionId} ${currentFileName}`); const reportContainer = document.getElementById(`report-${activeQuestionId} ${currentFileName}`); if (markschemeContainer && !markschemeContainer.classList.contains('hidden')) { toggleMSSvg.classList.toggle('hidden'); toggleMS(); markschemeContainer.classList.toggle('hidden'); activeQuestionId = null; } else if (reportContainer && !reportContainer.classList.contains('hidden')) { toggleRepSvg.classList.toggle('hidden'); toggleR(); reportContainer.classList.toggle('hidden'); activeQuestionId = null; } } } toggleMSSvg.addEventListener('click', handleToggle); toggleRepSvg.addEventListener('click', handleToggle); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { handleToggle(); } }); /*toggleRepSvg.addEventListener('click', () => { toggleRepcont(questionid); toggleR(); });*/ fragment.appendChild(bigQuestionBox); }); domCache.rightCol.appendChild(fragment); updateSquareContainers(); toggleDownAllQs(); toggleFilters(); } /******** Old function for fetching JSON, deprecated in favor of IndexedDB ********/ /*function loadJSON(filename) { fetch(`https://pub-59370068cd854c158959e7ca4578e5bd.r2.dev/${filename}`) // ../assets/jsonqb/ .then(response => response.json()) .then(data => { jsonDataFetched = true; currentFileName = filename; topics = [...new Set(data.flatMap(item => item.topics))].sort(); renderTopics(); const fragment = document.createDocumentFragment(); data.forEach(item => {}); domCache.rightCol.appendChild(fragment); updateSquareContainers(); }) .catch(error => console.error('Error fetching JSON:', error)); }*/ function createButton({ text, handler, className = 'btn-secondary' }) { const button = document.createElement("button"); button.classList.add(className); button.textContent = text; button.addEventListener('click', handler); return button; } function createPDFButtonHandler(questionid) { return function () { let selectedQuestionIds = JSON.parse(sessionStorage.getItem('selectedQuestionIds')) || []; const index = selectedQuestionIds.indexOf(questionid); if (index !== -1) { selectedQuestionIds.splice(index, 1); this.style.backgroundColor = 'rgb(66 165 245)'; this.textContent = 'Add to PDF'; } else { selectedQuestionIds.push(questionid); this.style.backgroundColor = '#e03b3b'; this.textContent = 'Added!'; } sessionStorage.setItem('selectedQuestionIds', JSON.stringify(selectedQuestionIds)); }; } function createContainer(type, questionid, filename, content, parent) { const container = document.createElement("div"); container.classList.add("square-container", "hidden"); container.id = `${type}-${questionid} ${filename}`; container.innerHTML = content; parent.appendChild(container); } function renderSubtopics() { const subtopicListContainer = document.getElementById('subtopic-select'); subtopicListContainer.innerHTML = ''; const fragment = document.createDocumentFragment(); subtopics.forEach(subtopic => { const option = document.createElement('option'); option.innerText = subtopic; option.value = subtopic; fragment.appendChild(option); }); subtopicListContainer.appendChild(fragment); } function renderTopics() { currentFilters.level = currentFilters.paper = currentFilters.subtopic = null; document.getElementById('level-select').value = ''; document.getElementById('paper-select').value = ''; document.getElementById('subtopic-select').value = ''; const topicListContainer = document.getElementById('topic-list'); topicListContainer.innerHTML = ''; const fragment = document.createDocumentFragment(); topics.forEach(topic => { const label = document.createElement('label'); label.classList.add('topic-label'); const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.name = 'topic'; checkbox.value = topic; checkbox.addEventListener('change', () => { document.getElementById('level-select').value = ''; document.getElementById('paper-select').value = ''; document.getElementById('subtopic-select').value = ''; currentFilters.level = null; currentFilters.paper = null; currentFilters.subtopic = null; const checkedTopics = Array.from( document.querySelectorAll('input[name="topic"]:checked') ).map(cb => cb.value); applyAllFilters('topic', checkedTopics); applyAllFilters('level', null); applyAllFilters('paper', null); applyAllFilters('subtopic', null); const visibleIds = Array.from( document.querySelectorAll('#right-col > div:not(.hidden)') ).map(div => div.id); sessionStorage.setItem('visibleIDs', JSON.stringify(visibleIds)); }); label.append(checkbox, document.createTextNode(topic)); fragment.append(label); }); topicListContainer.appendChild(fragment); } function findRecordById(qid) { return jsonData.find(item => item.question_id === qid) || {}; } const currentFilters = { level: null, paper: null, topic: null, subtopic: null }; function applyAllFilters(type, value) { if (type === 'topic' || type === 'subtopic') { const arr = Array.isArray(value) ? value.filter(Boolean) : []; currentFilters[type] = arr.length > 0 ? arr : null; } else { currentFilters[type] = value || null; } document.querySelectorAll('#right-col > div').forEach(div => { const qid = div.id; const record = findRecordById(qid); let hide = false; // 1) Level if (currentFilters.level) { const ok = currentFilters.level === 'standard' ? qid.includes('.SL.') : (qid.includes('.HL.') || qid.includes('.AHL.')); hide ||= !ok; } if (currentFilters.paper) { const pap = currentFilters.paper; const regex = pap === '1' ? /\.1(?:[ABC])?\./ : new RegExp(`\\.${pap}\\.`); hide ||= !regex.test(qid); } if (!currentFilters.topic) { hide ||= true; } else { const ok = (record.topics || []).some(t => currentFilters.topic.includes(t) ); hide ||= !ok; } if (currentFilters.subtopic) { const ok = (record.subtopics||[]) .some(st => currentFilters.subtopic.includes(st)); hide ||= !ok; } div.classList.toggle('hidden', hide); }); } document.addEventListener('DOMContentLoaded', () => { document.getElementById('paper-select') .addEventListener('change', e => { applyAllFilters('paper', e.target.value); }); document.getElementById('level-select') .addEventListener('change', e => { applyAllFilters('level', e.target.value); }); document .getElementById('subtopic-select') .addEventListener('change', e => { const vals = Array.from(e.target.selectedOptions) .map(opt => opt.value) .filter(v => v !== ''); // ← remove the "" entry applyAllFilters('subtopic', vals); }); }); function updateSquareContainers() { document.querySelectorAll('.square-container').forEach(container => { const firstChild = container.children[0]; if (firstChild?.classList.contains('question')) { firstChild.classList.replace('question', 'specification'); } }); } document.addEventListener('DOMContentLoaded', () => { document.addEventListener('click', event => { const filename = fileNameMap[event.target.id]; if (!filename) return; if (jsonDataFetched && filename !== currentFileName) { resetState(); loadJSON(filename); //} else if (jsonDataFetched && filename === currentFileName) { //resetState(); //jsonDataFetched = false; } else if (!jsonDataFetched) { loadJSON(filename); } }); }); function resetState() { domCache.rightCol.innerHTML = ''; document.querySelectorAll('.topic-label').forEach(label => label.remove()); sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('visibleIDs', '[]'); checkWidth() }