Fix editor full-screen and add parent-side doc viewer
Build and Push Docker Image / build-and-push (push) Successful in 45s

Student editor:
- Add body.docs-editor-open class when editor opens; hide hero, nav,
  confetti so nothing bleeds behind the fixed overlay

Admin docs panel:
- New "Documents" sidebar entry listing all submitted docs with student
  name, date, and unread badge
- Click any doc to open it in OnlyOffice in view (read-only) mode
- Download button for offline access
- /api/docs/bykey/:key/editor-config server route for admin viewer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-10-19 14:54:38 -04:00
1 parent 6a5ac46294
commit ad3e0bc7be
5 files changed
+128 -2

No files matched your search

+80 -2
View File
@@ -120,7 +120,7 @@ document.querySelectorAll('[data-nav]').forEach((btn) =>
const PANEL_TITLES = {
dashboard: 'Dashboard', kids: 'Kids', courses: 'Courses', planner: 'Planner',
grading: 'Grading', messages: 'Messages', gradebook: 'Gradebook',
spelling: 'Spelling', decks: 'Flashcards', settings: 'Settings',
spelling: 'Spelling', decks: 'Flashcards', docs: 'Documents', settings: 'Settings',
'course-detail': 'Course Detail', 'item-editor': 'Item Editor',
};
@@ -132,7 +132,7 @@ function showPanel(name) {
const loaders = {
dashboard: loadDashboard, kids: loadKids, courses: loadCourses, planner: loadPlannerPanel,
grading: loadGrading, messages: loadMessages, gradebook: loadGradebookPanel, spelling: loadSpelling, decks: loadDecks,
settings: loadSettings,
docs: loadAdminDocs, settings: loadSettings,
};
if (loaders[name]) loaders[name]();
if (name === 'course-detail') openCourseDetail(currentCourseId);
@@ -3290,3 +3290,81 @@ $('#app-settings-form').addEventListener('submit', async (e) => {
msg('Settings saved.');
});
// ── Admin: Submitted Documents ────────────────────────────────────────────────
let adminDocsEditorInstance = null;
async function loadAdminDocs() {
const { docs } = await api('/api/admin/docs');
const el = $('#admin-docs-list');
if (!docs.length) {
el.innerHTML = '<p class="hint">No submitted documents yet.</p>';
return;
}
el.innerHTML = docs.map((d) => `
<div class="doc-card${!d.parent_read ? ' inbox-new' : ''}" style="cursor:pointer"
data-id="${d.id}" data-key="${esc(d.doc_key)}" data-filename="${esc(d.filename)}" data-title="${esc(d.title)}">
<div class="doc-card-icon">📄</div>
<div class="doc-card-info">
<div class="doc-card-title">${esc(d.title)}${!d.parent_read ? ' <span class="feedback-badge">New</span>' : ''}</div>
<div class="doc-card-meta">${esc(d.student_name)} · Submitted ${new Date(d.submitted_at + 'Z').toLocaleDateString()}</div>
</div>
</div>
`).join('');
el.querySelectorAll('.doc-card').forEach((card) => {
card.addEventListener('click', () => openAdminDocViewer(
+card.dataset.id, card.dataset.key, card.dataset.filename, card.dataset.title
));
});
}
async function openAdminDocViewer(id, key, filename, title) {
await api(`/api/admin/docs/${id}/read`, { method: 'PUT' });
$('#admin-docs-list').hidden = true;
$('#admin-doc-viewer').hidden = false;
$('#admin-doc-viewer-title').textContent = title;
$('#admin-doc-download').href = `/api/docs/file/${filename}`;
$('#admin-doc-download').download = title + '.docx';
const frame = $('#admin-doc-frame');
frame.innerHTML = '';
if (adminDocsEditorInstance) {
try { adminDocsEditorInstance.destroyEditor(); } catch { /* ignore */ }
adminDocsEditorInstance = null;
}
const { onlyofficeUrl, config } = await api(`/api/docs/bykey/${key}/editor-config`).catch(() => null) || {};
if (!onlyofficeUrl || !config) {
frame.innerHTML = `<div class="docs-error">Could not load document config.</div>`;
return;
}
// Parent views in read mode
config.editorConfig = config.editorConfig || {};
config.editorConfig.mode = 'view';
function initEditor() {
adminDocsEditorInstance = new window.DocsAPI.DocEditor('admin-doc-frame', config);
}
if (window.DocsAPI) {
initEditor();
} else {
const s = document.createElement('script');
s.src = `${onlyofficeUrl}/web-apps/apps/api/documents/api.js`;
s.onload = initEditor;
s.onerror = () => { frame.innerHTML = `<div class="docs-error">Could not reach OnlyOffice server.</div>`; };
document.head.appendChild(s);
}
}
document.getElementById('btn-admin-doc-back')?.addEventListener('click', () => {
if (adminDocsEditorInstance) {
try { adminDocsEditorInstance.destroyEditor(); } catch { /* ignore */ }
adminDocsEditorInstance = null;
}
$('#admin-doc-frame').innerHTML = '';
$('#admin-doc-viewer').hidden = true;
$('#admin-docs-list').hidden = false;
loadAdminDocs();
});