add word lists
This commit is contained in:
1 parent
d955f55020
commit
b40e5136a1
8 files changed
+184
-40
No files matched your search
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"label": "Run Spelling Practice App",
|
||||||
|
"command": "node server.js",
|
||||||
|
"group": "build",
|
||||||
|
"isBackground": true,
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "spelling",
|
"name": "modern-spelling-practice",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
Generated
+4
-4
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "spelling",
|
"name": "modern-spelling-practice",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "spelling",
|
"name": "modern-spelling-practice",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^5.1.0"
|
"express": "^5.1.0"
|
||||||
|
|||||||
+22
-14
@@ -56,15 +56,15 @@
|
|||||||
|
|
||||||
<!-- Admin Tabs -->
|
<!-- Admin Tabs -->
|
||||||
<div id="adminTabs">
|
<div id="adminTabs">
|
||||||
<button class="active" onclick="switchTab('tabUsers')">👤 Users</button>
|
<button class="active" data-tab="tabUsers">👤 Users</button>
|
||||||
<button onclick="switchTab('tabWords')">📝 Word Lists</button>
|
<button data-tab="tabWords">📝 Word Lists</button>
|
||||||
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
|
<button data-tab="tabAnalytics">📊 Analytics</button>
|
||||||
<button onclick="switchTab('tabResults')">📋 Results</button>
|
<button data-tab="tabResults">📋 Results</button>
|
||||||
<button onclick="switchTab('tabReports')">📘 Reports</button>
|
<button data-tab="tabReports">📘 Reports</button>
|
||||||
<button onclick="switchTab('tabSessions')">📅 Sessions</button>
|
<button data-tab="tabSessions">📅 Sessions</button>
|
||||||
<button onclick="switchTab('tabBadges')">🏅 Badges</button>
|
<button data-tab="tabBadges">🏅 Badges</button>
|
||||||
<button onclick="switchTab('tabPasswords')">🔐 Passwords</button>
|
<button data-tab="tabPasswords">🔐 Passwords</button>
|
||||||
<button onclick="switchTab('tabTheme')">🎨 Themes</button>
|
<button data-tab="tabTheme">🎨 Themes</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Users Tab -->
|
<!-- Users Tab -->
|
||||||
@@ -106,12 +106,20 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h4>Manage Word List</h4>
|
<h4>Manage Word List</h4>
|
||||||
<textarea id="wordInput" placeholder="Enter one word per line..."></textarea>
|
<div id="weeksInputSection">
|
||||||
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
|
<h4>Enter Weeks</h4>
|
||||||
<button onclick="saveWords()" class="btn-success">💾 Save Words</button>
|
<div id="weeksContainer"></div>
|
||||||
<button onclick="clearWords()" class="btn-error">🗑️ Clear All</button>
|
<button onclick="addWeekInput()" class="btn-secondary" style="margin-top: 1rem;">➕ Add Week</button>
|
||||||
|
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
|
||||||
|
<button onclick="saveWeeks()" class="btn-success">💾 Save Weeks</button>
|
||||||
|
<button onclick="clearWeeks()" class="btn-error">🗑️ Clear All Weeks</button>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:2rem;">
|
||||||
|
<label for="activeWeekSelect"><strong>Set Active Week for Student:</strong></label>
|
||||||
|
<select id="activeWeekSelect" style="width:100%; margin-top:0.5rem;"></select>
|
||||||
|
<button onclick="setActiveWeek()" class="btn-primary" style="margin-top:0.5rem;">Set Active Week</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 1.5rem;">
|
<div style="margin-top: 1.5rem;">
|
||||||
<h5>Upload Word File</h5>
|
<h5>Upload Word File</h5>
|
||||||
<input accept=".txt,.csv" onchange="handleFileUpload(event)" type="file" style="margin: 0.5rem 0;"/>
|
<input accept=".txt,.csv" onchange="handleFileUpload(event)" type="file" style="margin: 0.5rem 0;"/>
|
||||||
|
|||||||
+86
-14
@@ -1,4 +1,81 @@
|
|||||||
// Global variables for admin functionality
|
// Global variables for admin functionality
|
||||||
|
// --- Weeks input logic ---
|
||||||
|
// Populate active week dropdown from weeksContainer
|
||||||
|
window.populateActiveWeekDropdown = function() {
|
||||||
|
const select = document.getElementById('activeWeekSelect');
|
||||||
|
select.innerHTML = '';
|
||||||
|
document.querySelectorAll('#weeksContainer .week-input-card').forEach(card => {
|
||||||
|
const date = card.querySelector('.week-date').value;
|
||||||
|
if (date) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = date;
|
||||||
|
option.textContent = date;
|
||||||
|
select.appendChild(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call populateActiveWeekDropdown whenever weeks change
|
||||||
|
const observer = new MutationObserver(() => populateActiveWeekDropdown());
|
||||||
|
observer.observe(document.getElementById('weeksContainer'), { childList: true, subtree: true });
|
||||||
|
|
||||||
|
window.setActiveWeek = async function() {
|
||||||
|
const selectedUser = document.getElementById('wordUserSelect')?.value;
|
||||||
|
const activeWeek = document.getElementById('activeWeekSelect')?.value;
|
||||||
|
if (!selectedUser || !activeWeek) {
|
||||||
|
alert('⚠️ Please select a user and week.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetch('/setActiveWeek', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username: selectedUser, activeWeek })
|
||||||
|
});
|
||||||
|
alert(`✅ Active week set for ${selectedUser}: ${activeWeek}`);
|
||||||
|
}
|
||||||
|
window.addWeekInput = function() {
|
||||||
|
const container = document.getElementById('weeksContainer');
|
||||||
|
const weekIdx = container.children.length;
|
||||||
|
const weekDiv = document.createElement('div');
|
||||||
|
weekDiv.className = 'week-input-card';
|
||||||
|
weekDiv.style = 'border:1px solid #ccc; padding:1rem; margin-bottom:1rem;';
|
||||||
|
weekDiv.innerHTML = `
|
||||||
|
<label>Date: <input type="date" class="week-date" /></label><br/>
|
||||||
|
<textarea class="week-words" placeholder="Enter one word per line..."></textarea><br/>
|
||||||
|
<button type="button" onclick="this.parentElement.remove()" class="btn-error" style="margin-top:0.5rem;">Remove Week</button>
|
||||||
|
`;
|
||||||
|
container.appendChild(weekDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.saveWeeks = async function() {
|
||||||
|
const selectedUser = document.getElementById('wordUserSelect')?.value;
|
||||||
|
if (!selectedUser) {
|
||||||
|
alert('⚠️ Please select a user before saving.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const weeks = [];
|
||||||
|
document.querySelectorAll('#weeksContainer .week-input-card').forEach(card => {
|
||||||
|
const date = card.querySelector('.week-date').value;
|
||||||
|
const words = card.querySelector('.week-words').value.split('\n').map(w => w.trim()).filter(Boolean);
|
||||||
|
if (date && words.length) {
|
||||||
|
weeks.push({ date, words });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!weeks.length) {
|
||||||
|
alert('⚠️ Please enter at least one week with a date and words.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetch('/saveWeeksWordList', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ username: selectedUser, weeks })
|
||||||
|
});
|
||||||
|
alert(`✅ Weeks saved for ${selectedUser}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.clearWeeks = function() {
|
||||||
|
document.getElementById('weeksContainer').innerHTML = '';
|
||||||
|
}
|
||||||
let allWords = [];
|
let allWords = [];
|
||||||
let adminUsers = [];
|
let adminUsers = [];
|
||||||
|
|
||||||
@@ -675,28 +752,23 @@ function populatePasswordUserDropdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add event listeners for admin tabs as backup to onclick
|
// Add event listeners for admin tabs as backup to onclick
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
// Setup tab click listeners
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const tabButtons = document.querySelectorAll('#adminTabs button');
|
const tabButtons = document.querySelectorAll('#adminTabs button');
|
||||||
tabButtons.forEach(button => {
|
tabButtons.forEach(button => {
|
||||||
button.addEventListener('click', (e) => {
|
button.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const onclick = button.getAttribute('onclick');
|
const tabId = button.getAttribute('data-tab');
|
||||||
if (onclick) {
|
if (tabId) {
|
||||||
const match = onclick.match(/switchTab\('(.+?)'\)/);
|
console.log('Tab clicked:', tabId);
|
||||||
if (match) {
|
if (typeof window.switchTab === 'function') {
|
||||||
const tabId = match[1];
|
window.switchTab(tabId);
|
||||||
console.log('Tab clicked:', tabId);
|
} else {
|
||||||
if (typeof window.switchTab === 'function') {
|
console.error('switchTab function not available');
|
||||||
window.switchTab(tabId);
|
|
||||||
} else {
|
|
||||||
console.error('switchTab function not available');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.log('✅ Tab event listeners added to', tabButtons.length, 'buttons');
|
console.log('✅ Tab event listeners added to', tabButtons.length, 'buttons');
|
||||||
}, 300);
|
}, 500);
|
||||||
});
|
});
|
||||||
+13
-3
@@ -100,14 +100,24 @@ window.startGame = async function() {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Received words:', data);
|
console.log('Received words:', data);
|
||||||
|
|
||||||
if (!data.words || data.words.length === 0) {
|
let weekWords = [];
|
||||||
|
// If weeks format and activeWeek is set, use only that week's words
|
||||||
|
if (data.words && Array.isArray(data.words)) {
|
||||||
|
weekWords = data.words;
|
||||||
|
} else if (data.words && data.words.weeks && data.words.activeWeek) {
|
||||||
|
const active = data.words.activeWeek;
|
||||||
|
const found = data.words.weeks.find(w => w.date === active);
|
||||||
|
if (found) weekWords = found.words;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!weekWords.length) {
|
||||||
alert('No words found for practice');
|
alert('No words found for practice');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start game
|
// Start game
|
||||||
words = [...data.words];
|
words = [...weekWords];
|
||||||
typingWords = [...data.words]; // Initialize typing words too
|
typingWords = [...weekWords]; // Initialize typing words too
|
||||||
currentIndex = 0;
|
currentIndex = 0;
|
||||||
results = []; // Reset results array
|
results = []; // Reset results array
|
||||||
showWord();
|
showWord();
|
||||||
|
|||||||
+4
-3
@@ -98,11 +98,12 @@ function showAdmin() {
|
|||||||
console.warn("Could not load user dropdowns:", e);
|
console.warn("Could not load user dropdowns:", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set default tab - use a slight delay to ensure DOM is ready
|
// Set default tab to tabWords and enable tab buttons
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
document.querySelectorAll('#adminTabs button').forEach(btn => btn.disabled = false);
|
||||||
if (typeof window.switchTab === 'function') {
|
if (typeof window.switchTab === 'function') {
|
||||||
window.switchTab("tabUsers");
|
window.switchTab("tabWords");
|
||||||
console.log("✅ Default tab set");
|
console.log("✅ Default tab set to tabWords");
|
||||||
} else {
|
} else {
|
||||||
console.error("switchTab function not available");
|
console.error("switchTab function not available");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,6 +296,46 @@ app.get('/getWordList', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/saveWordList', (req, res) => {
|
app.post('/saveWordList', (req, res) => {
|
||||||
|
// Save multiple weeks with dates for a user
|
||||||
|
app.post('/saveWeeksWordList', (req, res) => {
|
||||||
|
// Set active week for a user
|
||||||
|
app.post('/setActiveWeek', (req, res) => {
|
||||||
|
const { username, activeWeek } = req.body;
|
||||||
|
if (typeof username !== 'string' || typeof activeWeek !== 'string') {
|
||||||
|
return res.status(400).send('Invalid data');
|
||||||
|
}
|
||||||
|
const wordlistsPath = path.join(DATA_DIR, files.wordlists);
|
||||||
|
const wordlists = readJsonSafe(wordlistsPath);
|
||||||
|
if (!wordlists[username]) {
|
||||||
|
return res.status(404).send('User not found');
|
||||||
|
}
|
||||||
|
if (typeof wordlists[username] === 'object') {
|
||||||
|
wordlists[username].activeWeek = activeWeek;
|
||||||
|
} else {
|
||||||
|
wordlists[username] = { weeks: [], activeWeek };
|
||||||
|
}
|
||||||
|
fs.writeFileSync(wordlistsPath, JSON.stringify(wordlists, null, 2));
|
||||||
|
res.send(`✅ Active week set for ${username}: ${activeWeek}`);
|
||||||
|
});
|
||||||
|
const { username, weeks } = req.body;
|
||||||
|
if (
|
||||||
|
typeof username !== 'string' ||
|
||||||
|
!Array.isArray(weeks) ||
|
||||||
|
weeks.length > 100 ||
|
||||||
|
weeks.some(w => typeof w.date !== 'string' || !Array.isArray(w.words) || w.words.some(word => typeof word !== 'string'))
|
||||||
|
) return res.status(400).send('Invalid weeks data');
|
||||||
|
|
||||||
|
const userList = readJsonSafe(path.join(DATA_DIR, files.users), []);
|
||||||
|
if (!userList.find(u => u.username === username)) {
|
||||||
|
return res.status(404).send('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const wordlistsPath = path.join(DATA_DIR, files.wordlists);
|
||||||
|
const wordlists = readJsonSafe(wordlistsPath);
|
||||||
|
wordlists[username] = { weeks };
|
||||||
|
fs.writeFileSync(wordlistsPath, JSON.stringify(wordlists, null, 2));
|
||||||
|
res.send(`✅ Weeks word list saved for ${username}`);
|
||||||
|
});
|
||||||
const { username, words } = req.body;
|
const { username, words } = req.body;
|
||||||
if (
|
if (
|
||||||
typeof username !== 'string' ||
|
typeof username !== 'string' ||
|
||||||
|
|||||||
Reference in new issue
Block a user