From b40e5136a185015e5db7f74c8755853a46bb6ae4 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Sun, 27 Jul 2025 20:28:53 +0000 Subject: [PATCH] add word lists --- .vscode/tasks.json | 13 +++++ node_modules/.package-lock.json | 4 +- package-lock.json | 8 +-- public/index.html | 36 +++++++----- public/js/admin.js | 100 +++++++++++++++++++++++++++----- public/js/game.js | 16 ++++- public/js/ui.js | 7 ++- server.js | 40 +++++++++++++ 8 files changed, 184 insertions(+), 40 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..3459f51 --- /dev/null +++ b/.vscode/tasks.json @@ -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": [] + } + ] +} \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 521a230..3a89b43 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,6 +1,6 @@ { - "name": "spelling", - "version": "1.0.0", + "name": "modern-spelling-practice", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package-lock.json b/package-lock.json index 1314e45..019fea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/public/index.html b/public/index.html index 8d55886..8666c27 100644 --- a/public/index.html +++ b/public/index.html @@ -56,15 +56,15 @@
- - - - - - - - - + + + + + + + + +
@@ -106,12 +106,20 @@

Manage Word List

- -
- - +
+

Enter Weeks

+
+ +
+ + +
+
+ + + +
-
Upload Word File
diff --git a/public/js/admin.js b/public/js/admin.js index e43f9a4..4c504b8 100644 --- a/public/js/admin.js +++ b/public/js/admin.js @@ -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 = ` +
+
+ + `; + 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); }); \ No newline at end of file diff --git a/public/js/game.js b/public/js/game.js index 033389b..89ce94f 100644 --- a/public/js/game.js +++ b/public/js/game.js @@ -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(); diff --git a/public/js/ui.js b/public/js/ui.js index ea1edd5..fb7bb78 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -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"); } diff --git a/server.js b/server.js index 2ca5886..65fa3bc 100644 --- a/server.js +++ b/server.js @@ -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' ||