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",
|
||||
"version": "1.0.0",
|
||||
"name": "modern-spelling-practice",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
Generated
+4
-4
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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' ||
|
||||
|
||||
Reference in new issue
Block a user