Add bulk import for spelling lists and flashcard decks
Paste multiple lists/decks in one shot using # Name headers: # Week 1 word1 word2 | example sentence # Week 2 ... An optional group field applies to all imported items at once. Progress shown inline; list/deck panel refreshes on completion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
24e709e038
commit
adf4bf4cae
2 files changed
+100
No files matched your search
@@ -298,6 +298,21 @@
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details id="list-bulk-details">
|
||||
<summary>Bulk import lists</summary>
|
||||
<div class="admin-card">
|
||||
<p class="hint" style="margin:0 0 0.5rem">Start each list with <strong># List name</strong> on its own line, then words below it. Optional group applies to all imported lists.</p>
|
||||
<input id="list-bulk-group" placeholder="Group (e.g. Bible 6) — optional" aria-label="Group for bulk import" list="list-groups-datalist">
|
||||
<textarea id="list-bulk-text" rows="12"
|
||||
placeholder="# Week 1 because enough friend # Week 2 receive | Did you receive my letter? achieve believe"></textarea>
|
||||
<div class="row-actions">
|
||||
<button type="button" id="list-bulk-save">Import all lists</button>
|
||||
<button type="button" id="list-bulk-clear" class="secondary">Clear</button>
|
||||
</div>
|
||||
<p id="list-bulk-status" class="hint" style="margin:0.4rem 0 0"></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<h2>Test results</h2>
|
||||
<div id="results-area"></div>
|
||||
</section>
|
||||
@@ -321,6 +336,21 @@
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details id="deck-bulk-details">
|
||||
<summary>Bulk import decks</summary>
|
||||
<div class="admin-card">
|
||||
<p class="hint" style="margin:0 0 0.5rem">Start each deck with <strong># Deck name</strong> on its own line, then cards below as <strong>front | back</strong>. Optional group applies to all imported decks.</p>
|
||||
<input id="deck-bulk-group" placeholder="Group (e.g. Bible 6) — optional" aria-label="Group for bulk import" list="deck-groups-datalist">
|
||||
<textarea id="deck-bulk-text" rows="12"
|
||||
placeholder="# Chapter 1 Vocab What is photosynthesis? | The process plants use to make food What does chlorophyll do? | Absorbs light # Chapter 2 Vocab What is mitosis? | Cell division producing identical cells"></textarea>
|
||||
<div class="row-actions">
|
||||
<button type="button" id="deck-bulk-save">Import all decks</button>
|
||||
<button type="button" id="deck-bulk-clear" class="secondary">Clear</button>
|
||||
</div>
|
||||
<p id="deck-bulk-status" class="hint" style="margin:0.4rem 0 0"></p>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<!-- Settings -->
|
||||
|
||||
@@ -1410,6 +1410,49 @@ function clearListForm() {
|
||||
$('#list-editor-title').textContent = 'New list';
|
||||
}
|
||||
|
||||
function parseBulkSections(text) {
|
||||
const sections = [];
|
||||
let current = null;
|
||||
for (const raw of text.split('\n')) {
|
||||
const line = raw.trim();
|
||||
if (line.startsWith('#')) {
|
||||
if (current) sections.push(current);
|
||||
current = { name: line.replace(/^#+\s*/, ''), lines: [] };
|
||||
} else if (current && line) {
|
||||
current.lines.push(line);
|
||||
}
|
||||
}
|
||||
if (current) sections.push(current);
|
||||
return sections;
|
||||
}
|
||||
|
||||
$('#list-bulk-save').addEventListener('click', async () => {
|
||||
const groupName = $('#list-bulk-group').value.trim();
|
||||
const sections = parseBulkSections($('#list-bulk-text').value);
|
||||
if (sections.length === 0) return ($('#list-bulk-status').textContent = 'Paste at least one # Section with words below it.');
|
||||
const statusEl = $('#list-bulk-status');
|
||||
statusEl.textContent = `Importing ${sections.length} list(s)…`;
|
||||
let done = 0;
|
||||
for (const sec of sections) {
|
||||
const words = sec.lines.map((line) => {
|
||||
const [word, sentence = ''] = line.split('|').map((p) => p.trim());
|
||||
return { word, sentence };
|
||||
}).filter((w) => w.word);
|
||||
if (!words.length) continue;
|
||||
await api('/api/lists', { method: 'POST', body: { name: sec.name, words, groupName } });
|
||||
done++;
|
||||
statusEl.textContent = `Imported ${done} of ${sections.length}…`;
|
||||
}
|
||||
statusEl.textContent = `Done! ${done} list(s) imported.`;
|
||||
$('#list-bulk-text').value = '';
|
||||
loadSpelling();
|
||||
});
|
||||
$('#list-bulk-clear').addEventListener('click', () => {
|
||||
$('#list-bulk-text').value = '';
|
||||
$('#list-bulk-group').value = '';
|
||||
$('#list-bulk-status').textContent = '';
|
||||
});
|
||||
|
||||
function renderResults(students) {
|
||||
$('#results-area').innerHTML = students
|
||||
.map((s) => {
|
||||
@@ -1559,6 +1602,33 @@ function clearDeckForm() {
|
||||
$('#deck-editor-title').textContent = 'New deck';
|
||||
}
|
||||
|
||||
$('#deck-bulk-save').addEventListener('click', async () => {
|
||||
const groupName = $('#deck-bulk-group').value.trim();
|
||||
const sections = parseBulkSections($('#deck-bulk-text').value);
|
||||
if (sections.length === 0) return ($('#deck-bulk-status').textContent = 'Paste at least one # Section with cards below it.');
|
||||
const statusEl = $('#deck-bulk-status');
|
||||
statusEl.textContent = `Importing ${sections.length} deck(s)…`;
|
||||
let done = 0;
|
||||
for (const sec of sections) {
|
||||
const cards = sec.lines.map((line) => {
|
||||
const [front, back = ''] = line.split('|').map((p) => p.trim());
|
||||
return { front, back };
|
||||
}).filter((c) => c.front);
|
||||
if (!cards.length) continue;
|
||||
await api('/api/decks', { method: 'POST', body: { name: sec.name, cards, groupName } });
|
||||
done++;
|
||||
statusEl.textContent = `Imported ${done} of ${sections.length}…`;
|
||||
}
|
||||
statusEl.textContent = `Done! ${done} deck(s) imported.`;
|
||||
$('#deck-bulk-text').value = '';
|
||||
loadDecks();
|
||||
});
|
||||
$('#deck-bulk-clear').addEventListener('click', () => {
|
||||
$('#deck-bulk-text').value = '';
|
||||
$('#deck-bulk-group').value = '';
|
||||
$('#deck-bulk-status').textContent = '';
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Settings
|
||||
// ============================================================
|
||||
|
||||
Reference in new issue
Block a user