add word lists

This commit is contained in:
nmemmert committed 2025-07-27 20:28:53 +00:00
1 parent d955f55020
commit b40e5136a1
8 files changed
+184 -40

No files matched your search

+13
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{
"name": "spelling",
"version": "1.0.0",
"name": "modern-spelling-practice",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "spelling",
"version": "1.0.0",
"name": "modern-spelling-practice",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "spelling",
"version": "1.0.0",
"name": "modern-spelling-practice",
"version": "2.0.0",
"license": "ISC",
"dependencies": {
"express": "^5.1.0"
+22 -14
View File
@@ -56,15 +56,15 @@
<!-- Admin Tabs -->
<div id="adminTabs">
<button class="active" onclick="switchTab('tabUsers')">👤 Users</button>
<button onclick="switchTab('tabWords')">📝 Word Lists</button>
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
<button onclick="switchTab('tabResults')">📋 Results</button>
<button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">📅 Sessions</button>
<button onclick="switchTab('tabBadges')">🏅 Badges</button>
<button onclick="switchTab('tabPasswords')">🔐 Passwords</button>
<button onclick="switchTab('tabTheme')">🎨 Themes</button>
<button class="active" data-tab="tabUsers">👤 Users</button>
<button data-tab="tabWords">📝 Word Lists</button>
<button data-tab="tabAnalytics">📊 Analytics</button>
<button data-tab="tabResults">📋 Results</button>
<button data-tab="tabReports">📘 Reports</button>
<button data-tab="tabSessions">📅 Sessions</button>
<button data-tab="tabBadges">🏅 Badges</button>
<button data-tab="tabPasswords">🔐 Passwords</button>
<button data-tab="tabTheme">🎨 Themes</button>
</div>
<!-- Users Tab -->
@@ -106,12 +106,20 @@
<div class="card">
<h4>Manage Word List</h4>
<textarea id="wordInput" placeholder="Enter one word per line..."></textarea>
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
<button onclick="saveWords()" class="btn-success">💾 Save Words</button>
<button onclick="clearWords()" class="btn-error">🗑️ Clear All</button>
<div id="weeksInputSection">
<h4>Enter Weeks</h4>
<div id="weeksContainer"></div>
<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 style="margin-top: 1.5rem;">
<h5>Upload Word File</h5>
<input accept=".txt,.csv" onchange="handleFileUpload(event)" type="file" style="margin: 0.5rem 0;"/>
+86 -14
View File
@@ -1,4 +1,81 @@
// 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 adminUsers = [];
@@ -675,28 +752,23 @@ function populatePasswordUserDropdown() {
}
// Add event listeners for admin tabs as backup to onclick
document.addEventListener('DOMContentLoaded', () => {
// Setup tab click listeners
window.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
const tabButtons = document.querySelectorAll('#adminTabs button');
tabButtons.forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
const onclick = button.getAttribute('onclick');
if (onclick) {
const match = onclick.match(/switchTab\('(.+?)'\)/);
if (match) {
const tabId = match[1];
console.log('Tab clicked:', tabId);
if (typeof window.switchTab === 'function') {
window.switchTab(tabId);
} else {
console.error('switchTab function not available');
}
const tabId = button.getAttribute('data-tab');
if (tabId) {
console.log('Tab clicked:', tabId);
if (typeof window.switchTab === 'function') {
window.switchTab(tabId);
} else {
console.error('switchTab function not available');
}
}
});
});
console.log('✅ Tab event listeners added to', tabButtons.length, 'buttons');
}, 300);
}, 500);
});
+13 -3
View File
@@ -100,14 +100,24 @@ window.startGame = async function() {
const data = await response.json();
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');
return;
}
// Start game
words = [...data.words];
typingWords = [...data.words]; // Initialize typing words too
words = [...weekWords];
typingWords = [...weekWords]; // Initialize typing words too
currentIndex = 0;
results = []; // Reset results array
showWord();
+4 -3
View File
@@ -98,11 +98,12 @@ function showAdmin() {
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(() => {
document.querySelectorAll('#adminTabs button').forEach(btn => btn.disabled = false);
if (typeof window.switchTab === 'function') {
window.switchTab("tabUsers");
console.log("✅ Default tab set");
window.switchTab("tabWords");
console.log("✅ Default tab set to tabWords");
} else {
console.error("switchTab function not available");
}
+40
View File
@@ -296,6 +296,46 @@ app.get('/getWordList', (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;
if (
typeof username !== 'string' ||