Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ <h5>Question Management</h5>
<div class="text-center">
<a class="btn btn-primary mb-2" href="./question-management/set">Update Set</a>
<a class="btn btn-primary mb-2" href="./question-management/packet">Update Packet</a>
<a class="btn btn-primary mb-2" href="./question-management/question">Edit Question</a>
</div>
</div>
</div>
Expand Down
175 changes: 175 additions & 0 deletions client/admin/question-management/question.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<!doctype html>
<html lang="en">

<head>
<title>QB Reader</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">

<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed">
<link type="image/x-icon" href="/favicon.ico" rel="icon">

<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<link href="/style.css" rel="stylesheet">
<script src="/scripts/global.js"></script>
</head>

<body>
<nav class="navbar navbar-expand-lg" id="navbar" style="z-index: 10">
<div class="container-fluid">
<a class="navbar-brand ms-1 py-0" id="logo" href="/">
<span class="logo-prefix">QB</span><span class="logo-suffix">Reader</span>
</a>
<button class="navbar-toggler" data-bs-target="#navbarSupportedContent" data-bs-toggle="collapse" type="button"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="navbar-nav me-auto mb-2 mb-lg-0">
<a class="nav-link" href="/play/">Play</a>
<a class="nav-link" href="/play/mp/">Multiplayer</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Database
</a>
<ul class="dropdown-menu">
<a class="dropdown-item" href="/db/">Search</a>
<a class="dropdown-item" href="/db/backups">Backups</a>
<a class="dropdown-item" href="/db/set-list/">Set List</a>
<a class="dropdown-item" href="/db/frequency-list/">Frequency List</a>
</ul>
</li>
<a class="nav-link" href="/geoword/">Geoword</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Tools
</a>
<ul class="dropdown-menu">
<a class="dropdown-item" href="/tools/api-docs/">API Docs</a>
<a class="dropdown-item icon-link" href="https://modaq.github.io/">
MODAQ <i class="bi bi-box-arrow-up-right"></i>
</a>
<a class="dropdown-item" href="/tools/packet-parser/">Packet Parser</a>
<a class="dropdown-item" href="/tools/packet-viewer/">Packet Viewer</a>
<a class="dropdown-item" href="/tools/tb-splitter/">TB Splitter</a>
</ul>
</li>
<a class="nav-link" href="/about/">About</a>
<a class="nav-link" href="/settings/">Settings</a>
</div>
<div class="d-flex">
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="nav-item dropdown">
<button id="bd-theme" class="btn btn-link nav-link dropdown-toggle d-flex align-items-center"
data-bs-toggle="dropdown">
<span class="theme-icon-active"><i class="bi bi-moon-stars-fill"></i></span>
<span class="d-lg-none ms-2" id="bd-theme-text">Toggle theme</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item d-flex" data-bs-theme-value="light">
<i class="bi bi-sun-fill me-2"></i>Light<i class="bi bi-check2 ms-auto d-none"></i>
</a>
<a class="dropdown-item d-flex" data-bs-theme-value="night">
<i class="bi bi-moon-stars-fill me-2"></i>Night<i class="bi bi-check2 ms-auto d-none"></i>
</a>
<a class="dropdown-item d-flex active" data-bs-theme-value="auto">
<i class="bi bi-circle-half me-2"></i>Auto<i class="bi bi-check2 ms-auto d-none"></i>
</a>
</ul>
</li>
<li class="nav-item">
<div class="vr d-none d-lg-flex h-100 mx-lg-2"></div>
</li>
<a class="nav-link" href="/user/login" id="login-link">Log in</a>
</ul>
</div>
</div>
</div>
</nav>

<div class="container-xl mt-3 mb-5 pb-5">
<div class="d-flex justify-content-end">
<a href="/admin/">Back to admin page</a>
</div>
<h4 class="mb-3">Edit Question by ID</h4>
<div class="mb-3">
<label for="question-type" class="form-label">Question Type</label>
<select class="form-select" id="question-type">
<option value="tossup">Tossup</option>
<option value="bonus">Bonus</option>
</select>
</div>
<div class="mb-3">
<label for="question-id" class="form-label">Question _id</label>
<div class="input-group">
<input type="text" class="form-control" id="question-id" placeholder="Enter question _id" required />
<button class="btn btn-outline-secondary" type="button" id="load-question">Load</button>
</div>
<div class="invalid-feedback">Please enter a valid question _id.</div>
</div>

<div id="question-preview" class="d-none mb-3">
<div class="card">
<div class="card-header" id="preview-header"></div>
<div class="card-body" id="preview-body"></div>
</div>
</div>

<form class="needs-validation d-none" id="tossup-form" novalidate>
<div class="mb-3">
<label for="tossup-question" class="form-label">Question Text</label>
<textarea class="form-control" id="tossup-question" rows="5"></textarea>
<div class="form-text">Allowed HTML tags: &lt;b&gt;, &lt;i&gt;, &lt;u&gt;</div>
</div>
<div class="mb-3">
<label for="tossup-answer" class="form-label">Answer</label>
<textarea class="form-control" id="tossup-answer" rows="2"></textarea>
<div class="form-text">Allowed HTML tags: &lt;b&gt;, &lt;i&gt;, &lt;u&gt;</div>
</div>
<button class="btn btn-primary" id="tossup-submit" type="submit">Save Changes</button>
</form>

<form class="needs-validation d-none" id="bonus-form" novalidate>
<div class="mb-3">
<label for="bonus-leadin" class="form-label">Leadin</label>
<textarea class="form-control" id="bonus-leadin" rows="2"></textarea>
<div class="form-text">Allowed HTML tags: &lt;b&gt;, &lt;i&gt;, &lt;u&gt;</div>
</div>
<div class="mb-3">
<label for="bonus-part-0" class="form-label">Part 1</label>
<textarea class="form-control" id="bonus-part-0" rows="2"></textarea>
</div>
<div class="mb-3">
<label for="bonus-answer-0" class="form-label">Answer 1</label>
<textarea class="form-control" id="bonus-answer-0" rows="2"></textarea>
</div>
<div class="mb-3">
<label for="bonus-part-1" class="form-label">Part 2</label>
<textarea class="form-control" id="bonus-part-1" rows="2"></textarea>
</div>
<div class="mb-3">
<label for="bonus-answer-1" class="form-label">Answer 2</label>
<textarea class="form-control" id="bonus-answer-1" rows="2"></textarea>
</div>
<div class="mb-3">
<label for="bonus-part-2" class="form-label">Part 3</label>
<textarea class="form-control" id="bonus-part-2" rows="2"></textarea>
</div>
<div class="mb-3">
<label for="bonus-answer-2" class="form-label">Answer 3</label>
<textarea class="form-control" id="bonus-answer-2" rows="2"></textarea>
</div>
<div class="form-text mb-3">Allowed HTML tags: &lt;b&gt;, &lt;i&gt;, &lt;u&gt;</div>
<button class="btn btn-primary" id="bonus-submit" type="submit">Save Changes</button>
</form>
</div>



<script type="module" src="./question.js"></script>
</body>

</html>
136 changes: 136 additions & 0 deletions client/admin/question-management/question.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const questionTypeSelect = document.getElementById('question-type');
const questionIdInput = document.getElementById('question-id');
const loadButton = document.getElementById('load-question');
const previewDiv = document.getElementById('question-preview');
const previewHeader = document.getElementById('preview-header');
const previewBody = document.getElementById('preview-body');
const tossupForm = document.getElementById('tossup-form');
const bonusForm = document.getElementById('bonus-form');

function escapeHTML (text) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}

function showPreview (type, data) {
previewDiv.classList.remove('d-none');
if (type === 'tossup') {
const tossup = data;
const alternateSubcategory = tossup.alternate_subcategory ? ' (' + tossup.alternate_subcategory + ')' : '';
previewHeader.innerHTML = `<b>${escapeHTML(tossup.set.name)} | ${escapeHTML(tossup.category)} | ${escapeHTML(tossup.subcategory)}${escapeHTML(alternateSubcategory)} | ${tossup.difficulty}</b>`;
previewBody.innerHTML = `<span>${tossup.question}</span><hr><div><b>ANSWER:</b> ${tossup.answer}</div>`;
} else {
const bonus = data;
const alternateSubcategory = bonus.alternate_subcategory ? ' (' + bonus.alternate_subcategory + ')' : '';
previewHeader.innerHTML = `<b>${escapeHTML(bonus.set.name)} | ${escapeHTML(bonus.category)} | ${escapeHTML(bonus.subcategory)}${escapeHTML(alternateSubcategory)} | ${bonus.difficulty}</b>`;
let bodyHTML = `<div>${bonus.leadin}</div>`;
for (let i = 0; i < bonus.parts.length; i++) {
bodyHTML += `<div>[10] ${bonus.parts[i]}</div>`;
bodyHTML += `<div><b>ANSWER:</b> ${bonus.answers[i]}</div>`;
}
previewBody.innerHTML = bodyHTML;
}
}

loadButton.addEventListener('click', async function () {
const questionType = questionTypeSelect.value;
const questionId = questionIdInput.value.trim();

if (!questionId) {
questionIdInput.classList.add('is-invalid');
return;
}
questionIdInput.classList.remove('is-invalid');

tossupForm.classList.add('d-none');
bonusForm.classList.add('d-none');
previewDiv.classList.add('d-none');

const endpoint = questionType === 'tossup' ? '/api/tossup' : '/api/bonus';
const response = await fetch(`${endpoint}?_id=${encodeURIComponent(questionId)}`);
if (!response.ok) {
window.alert(`Error loading question: ${await response.text()}`);
return;
}

const data = await response.json();

if (questionType === 'tossup') {
const tossup = data.tossup;
showPreview('tossup', tossup);
document.getElementById('tossup-question').value = tossup.question;
document.getElementById('tossup-answer').value = tossup.answer;
tossupForm.classList.remove('d-none');
} else {
const bonus = data.bonus;
showPreview('bonus', bonus);
document.getElementById('bonus-leadin').value = bonus.leadin;
for (let i = 0; i < bonus.parts.length; i++) {
document.getElementById(`bonus-part-${i}`).value = bonus.parts[i];
document.getElementById(`bonus-answer-${i}`).value = bonus.answers[i];
}
bonusForm.classList.remove('d-none');
}
});

tossupForm.addEventListener('submit', async function (event) {
event.preventDefault();
event.stopPropagation();

const questionId = questionIdInput.value.trim();
const question = document.getElementById('tossup-question').value;
const answer = document.getElementById('tossup-answer').value;

document.getElementById('tossup-submit').disabled = true;
document.getElementById('tossup-submit').textContent = 'Saving...';

const response = await fetch('/api/admin/question-management/question/update-tossup', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ _id: questionId, question, answer })
});

if (response.ok) {
window.alert('Tossup updated successfully');
loadButton.click();
} else {
window.alert(`Error updating tossup: ${await response.text()}`);
}

document.getElementById('tossup-submit').disabled = false;
document.getElementById('tossup-submit').textContent = 'Save Changes';
});

bonusForm.addEventListener('submit', async function (event) {
event.preventDefault();
event.stopPropagation();

const questionId = questionIdInput.value.trim();
const leadin = document.getElementById('bonus-leadin').value;
const parts = [];
const answers = [];
for (let i = 0; i < 3; i++) {
parts.push(document.getElementById(`bonus-part-${i}`).value);
answers.push(document.getElementById(`bonus-answer-${i}`).value);
}

document.getElementById('bonus-submit').disabled = true;
document.getElementById('bonus-submit').textContent = 'Saving...';

const response = await fetch('/api/admin/question-management/question/update-bonus', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ _id: questionId, leadin, parts, answers })
});

if (response.ok) {
window.alert('Bonus updated successfully');
loadButton.click();
} else {
window.alert(`Error updating bonus: ${await response.text()}`);
}

document.getElementById('bonus-submit').disabled = false;
document.getElementById('bonus-submit').textContent = 'Save Changes';
});
Loading