Replace server-folder docx import with direct file picker
The old import scanned a hardcoded Seafile CloudStorage path that only worked on one specific machine. Replaced with a standard <input type=file> browser picker that reads the .docx as base64 and uploads it directly, so the feature works everywhere. Server endpoint now accepts either a base64 payload or the legacy path. Also fixed a crash where resetImportSection() still referenced the removed select element, which was breaking the item editor for all item types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
de598496bc
commit
b048f51d46
3 files changed
+33
-40
No files matched your search
+5
-4
@@ -169,12 +169,13 @@
|
||||
<p id="scan-status" class="hint"></p>
|
||||
</div>
|
||||
|
||||
<!-- Import from Homeschool folder: visible for lesson, assignment, quiz types -->
|
||||
<!-- Import from .docx file: visible for lesson, assignment, quiz types -->
|
||||
<div id="ie-import-section" class="scan-card" hidden>
|
||||
<strong>📂 Import from Homeschool folder</strong>
|
||||
<strong>📂 Import from .docx</strong>
|
||||
<input type="file" id="import-local-input" accept=".docx" hidden>
|
||||
<div class="scan-controls">
|
||||
<select id="import-file-select" style="flex:1"><option value="">— choose a file —</option></select>
|
||||
<button type="button" id="import-load-btn" class="secondary">↓ Load files</button>
|
||||
<button type="button" id="import-browse-btn" class="secondary">📂 Choose file…</button>
|
||||
<span id="import-local-name" class="hint" style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"></span>
|
||||
<button type="button" id="import-go-btn" class="secondary" disabled>📥 Import</button>
|
||||
</div>
|
||||
<p id="import-status" class="hint"></p>
|
||||
|
||||
+15
-27
@@ -504,7 +504,8 @@ $('#rte-youtube-btn').addEventListener('click', () => {
|
||||
let scanImageBase64 = null;
|
||||
|
||||
function resetImportSection() {
|
||||
$('#import-file-select').innerHTML = '<option value="">— choose a file —</option>';
|
||||
$('#import-local-input').value = '';
|
||||
$('#import-local-name').textContent = '';
|
||||
$('#import-go-btn').disabled = true;
|
||||
$('#import-status').textContent = '';
|
||||
}
|
||||
@@ -590,41 +591,28 @@ $('#scan-go-btn').addEventListener('click', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// ---------- import from Homeschool folder ----------
|
||||
// ---------- import from local .docx file ----------
|
||||
|
||||
$('#import-load-btn').addEventListener('click', async () => {
|
||||
$('#import-status').textContent = 'Loading files…';
|
||||
$('#import-file-select').innerHTML = '<option value="">— loading… —</option>';
|
||||
try {
|
||||
const { files } = await api('/api/admin/homeschool-files');
|
||||
if (!files.length) {
|
||||
$('#import-file-select').innerHTML = '<option value="">No .docx files found</option>';
|
||||
$('#import-status').textContent = '';
|
||||
return;
|
||||
}
|
||||
$('#import-file-select').innerHTML =
|
||||
'<option value="">— choose a file —</option>' +
|
||||
files.map((f) => `<option value="${esc(f.path)}">${esc(f.path)}</option>`).join('');
|
||||
$('#import-go-btn').disabled = false;
|
||||
$('#import-status').textContent = `${files.length} file(s) found.`;
|
||||
} catch (err) {
|
||||
$('#import-status').textContent = `❌ ${err.message}`;
|
||||
}
|
||||
});
|
||||
$('#import-browse-btn').addEventListener('click', () => $('#import-local-input').click());
|
||||
|
||||
$('#import-file-select').addEventListener('change', () => {
|
||||
$('#import-go-btn').disabled = !$('#import-file-select').value;
|
||||
$('#import-local-input').addEventListener('change', () => {
|
||||
const file = $('#import-local-input').files[0];
|
||||
$('#import-local-name').textContent = file ? file.name : '';
|
||||
$('#import-go-btn').disabled = !file;
|
||||
$('#import-status').textContent = '';
|
||||
});
|
||||
|
||||
$('#import-go-btn').addEventListener('click', async () => {
|
||||
const path = $('#import-file-select').value;
|
||||
if (!path) return;
|
||||
const file = $('#import-local-input').files[0];
|
||||
if (!file) return;
|
||||
const type = $('#ie-type').value;
|
||||
const mode = type === 'quiz' ? 'quiz' : type === 'lesson' ? 'lesson' : 'lesson';
|
||||
const mode = type === 'quiz' ? 'quiz' : 'lesson';
|
||||
$('#import-go-btn').disabled = true;
|
||||
$('#import-status').textContent = '🔍 Extracting content… this may take a minute.';
|
||||
try {
|
||||
const result = await api('/api/admin/import-docx', { method: 'POST', body: { path, mode } });
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
|
||||
const result = await api('/api/admin/import-docx', { method: 'POST', body: { base64, mode } });
|
||||
if (!$('#ie-title').value && result.title) $('#ie-title').value = result.title;
|
||||
if (mode === 'quiz' && result.questions) {
|
||||
$('#ie-questions').innerHTML = '';
|
||||
|
||||
@@ -1538,18 +1538,22 @@ Respond with ONLY a JSON object in exactly this shape, nothing else, no markdown
|
||||
};
|
||||
|
||||
app.post('/api/admin/import-docx', requirePin, async (req, res) => {
|
||||
const { path: relPath, mode } = req.body;
|
||||
if (!relPath || !TEXT_SCAN_PROMPTS[mode]) {
|
||||
return res.status(400).json({ error: 'path and mode (lesson or quiz) required' });
|
||||
}
|
||||
const fullPath = join(HOMESCHOOL_DIR, relPath);
|
||||
if (!fullPath.startsWith(HOMESCHOOL_DIR)) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
const { path: relPath, base64: b64, mode } = req.body;
|
||||
if (!TEXT_SCAN_PROMPTS[mode] || (!relPath && !b64)) {
|
||||
return res.status(400).json({ error: 'mode and either path or base64 required' });
|
||||
}
|
||||
|
||||
let buffer;
|
||||
try { buffer = await readFile(fullPath); }
|
||||
catch (err) { return res.status(404).json({ error: `Could not read file: ${err.message}` }); }
|
||||
if (b64) {
|
||||
buffer = Buffer.from(b64, 'base64');
|
||||
} else {
|
||||
const fullPath = join(HOMESCHOOL_DIR, relPath);
|
||||
if (!fullPath.startsWith(HOMESCHOOL_DIR)) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
try { buffer = await readFile(fullPath); }
|
||||
catch (err) { return res.status(404).json({ error: `Could not read file: ${err.message}` }); }
|
||||
}
|
||||
|
||||
let docText;
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user