Fix login flash, quiz in presentations, revert viewer body, feed labels
Login flash:
- Auth screen now hidden by CSS default (#auth-screen{display:none !important})
- auth.js shows it only when there is no valid token or token is expired
- No more flash of login page on refresh for logged-in users
Presentation quiz option (AI Generate panel):
- Presentation type now shows optional quiz question toggle (same as article)
- Backend: when questionCount>0, returns JSON {marpMarkdown, questions[]}
instead of plain Marp markdown
- applyAiContent fills both the Marp textarea and question blocks for presentations
Viewer revert:
- Removed the viewer presentation card change (user wants markdown as-is)
- Removed dangling viewer button wiring
Feed/CMS list:
- Presentation type shows correct icon (fa-presentation-screen) and
label "Slides" in the CMS content list
This commit is contained in:
parent
23d39dec65
commit
01f6a38ee2
4 changed files with 85 additions and 25 deletions
|
|
@ -21,8 +21,8 @@
|
|||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16.png">
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192.png">
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512.png">
|
||||
<script>if (localStorage.getItem('ped_scribe_token')) { document.documentElement.classList.add('has-session'); }</script>
|
||||
<style>html.has-session #auth-screen { display: none !important; }</style>
|
||||
<!-- Auth screen hidden by default — auth.js shows it only when no valid session exists -->
|
||||
<style>#auth-screen { display: none !important; }</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
// Make sure forms are visible/hidden correctly on load
|
||||
showLoginForm();
|
||||
|
||||
// Check for saved session via localStorage token
|
||||
// Auth screen is hidden by CSS default — only show it when there is no valid session
|
||||
function showAuthScreen() {
|
||||
if (authScreen) authScreen.style.display = 'flex';
|
||||
}
|
||||
|
||||
var savedToken = localStorage.getItem(TOKEN_KEY);
|
||||
if (savedToken) {
|
||||
authScreen.classList.add('hidden');
|
||||
// Token exists — verify it silently; auth screen stays hidden during check
|
||||
fetch('/api/auth/me', {
|
||||
headers: { 'Authorization': 'Bearer ' + savedToken }
|
||||
})
|
||||
|
|
@ -39,14 +43,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
if (data && data.user) {
|
||||
enterApp(data.user, savedToken);
|
||||
} else {
|
||||
authScreen.classList.remove('hidden');
|
||||
showAuthScreen();
|
||||
clearSession();
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
authScreen.classList.remove('hidden');
|
||||
showAuthScreen();
|
||||
clearSession();
|
||||
});
|
||||
} else {
|
||||
// No token — show login immediately
|
||||
showAuthScreen();
|
||||
}
|
||||
|
||||
// ---- HELPER FUNCTIONS ----
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
var dot = e.target.closest('.slide-dot');
|
||||
if (dot) { renderSlide(parseInt(dot.dataset.idx)); return; }
|
||||
|
||||
|
||||
// Feed item click
|
||||
var feedItem = e.target.closest('.lh-feed-item');
|
||||
if (feedItem && feedItem.dataset.slug) {
|
||||
|
|
@ -213,8 +214,8 @@
|
|||
}
|
||||
|
||||
feedEl.innerHTML = items.map(function(item) {
|
||||
var typeIcon = item.content_type === 'quiz' ? 'fa-clipboard-question' : item.content_type === 'pearl' ? 'fa-gem' : 'fa-file-alt';
|
||||
var typeColor = item.content_type === 'quiz' ? 'var(--amber)' : item.content_type === 'pearl' ? 'var(--purple, #8b5cf6)' : 'var(--blue)';
|
||||
var typeIcon = item.content_type === 'quiz' ? 'fa-clipboard-question' : item.content_type === 'pearl' ? 'fa-gem' : item.content_type === 'presentation' ? 'fa-presentation-screen' : 'fa-file-alt';
|
||||
var typeColor = item.content_type === 'quiz' ? 'var(--amber)' : item.content_type === 'pearl' ? 'var(--purple, #8b5cf6)' : item.content_type === 'presentation' ? '#065f46' : 'var(--blue)';
|
||||
var quizBadge = item.question_count > 0 ? '<span class="lh-badge lh-badge-quiz"><i class="fas fa-clipboard-question"></i> ' + item.question_count + ' Q</span>' : '';
|
||||
var catBadge = item.category_name ? '<span class="lh-badge">' + esc(item.category_name) + '</span>' : '<span class="lh-badge" style="opacity:0.5;">Uncategorized</span>';
|
||||
var date = item.created_at ? new Date(item.created_at).toLocaleDateString() : '';
|
||||
|
|
@ -287,7 +288,7 @@
|
|||
metaEl.textContent = parts.join(' | ');
|
||||
}
|
||||
if (bodyEl) {
|
||||
// Render body as HTML (admin creates it)
|
||||
// Render body as HTML (admin creates it); presentations store Marp markdown
|
||||
bodyEl.innerHTML = sanitizeHtml(item.body || '');
|
||||
}
|
||||
|
||||
|
|
@ -568,9 +569,13 @@
|
|||
if (toggleCb) { toggleCb.checked = true; toggleCb.disabled = true; }
|
||||
if (countWrap) countWrap.style.display = 'flex';
|
||||
} else if (type === 'presentation') {
|
||||
// Presentation: no questions
|
||||
if (quizRow) quizRow.style.display = 'none';
|
||||
if (toggleCb) { toggleCb.checked = false; toggleCb.disabled = true; }
|
||||
// Presentation: optional questions (same as article)
|
||||
if (quizRow) quizRow.style.display = 'block';
|
||||
if (cardTitle) cardTitle.textContent = 'Quiz Questions (optional)';
|
||||
if (toggleLabel) toggleLabel.style.display = 'flex';
|
||||
if (toggleCb) toggleCb.disabled = false;
|
||||
var checkedP = toggleCb ? toggleCb.checked : false;
|
||||
if (countWrap) countWrap.style.display = checkedP ? 'flex' : 'none';
|
||||
} else {
|
||||
// Article / pearl: optional questions via toggle
|
||||
if (quizRow) quizRow.style.display = 'block';
|
||||
|
|
@ -771,6 +776,14 @@
|
|||
var titleMatch = content.marpMarkdown.match(/^#\s+(.+)$/m);
|
||||
var titleEl = document.getElementById('lh-cms-edit-title');
|
||||
if (titleEl && titleMatch) titleEl.value = titleMatch[1];
|
||||
// Fill questions if any were generated alongside slides
|
||||
var qContainerP = document.getElementById('lh-cms-questions');
|
||||
if (qContainerP) {
|
||||
qContainerP.innerHTML = '';
|
||||
if (content.questions && Array.isArray(content.questions)) {
|
||||
content.questions.forEach(function(q) { addQuestionBlock(q); });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -951,7 +964,7 @@
|
|||
var statusBadge = item.published
|
||||
? '<span class="cms-badge cms-badge-pub">Published</span>'
|
||||
: '<span class="cms-badge cms-badge-draft">Draft</span>';
|
||||
var typeLabel = item.content_type === 'quiz' ? 'Quiz' : item.content_type === 'pearl' ? 'Pearl' : 'Article';
|
||||
var typeLabel = item.content_type === 'quiz' ? 'Quiz' : item.content_type === 'pearl' ? 'Pearl' : item.content_type === 'presentation' ? 'Slides' : 'Article';
|
||||
var qBadge = item.question_count > 0 ? ' <span style="color:var(--amber);">' + item.question_count + 'Q</span>' : '';
|
||||
var date = item.updated_at ? new Date(item.updated_at).toLocaleDateString() : '';
|
||||
|
||||
|
|
|
|||
|
|
@ -60,12 +60,16 @@ function buildGeneratePrompt(opts) {
|
|||
|
||||
var refineInstr = refinement ? '\n\nAdditional instructions for tone/style/focus: ' + refinement : '';
|
||||
|
||||
// ── Presentation: return Marp markdown, not JSON ──
|
||||
// ── Presentation ──
|
||||
if (contentType === 'presentation') {
|
||||
var slideHint = slideCount ? slideCount + ' slides' : '8-12 slides';
|
||||
return source + '\n' +
|
||||
'Create a professional Marp presentation (' + slideHint + ') suitable for medical education. ' +
|
||||
'Each slide should be focused and readable.' + refineInstr + `
|
||||
var hasQuestions = parseInt(questionCount) > 0;
|
||||
|
||||
if (!hasQuestions) {
|
||||
// No questions — return raw Marp markdown only
|
||||
return source + '\n' +
|
||||
'Create a professional Marp presentation (' + slideHint + ') suitable for medical education. ' +
|
||||
'Each slide should be focused and readable.' + refineInstr + `
|
||||
|
||||
Return ONLY valid Marp markdown (no JSON, no code fences). Start with frontmatter:
|
||||
---
|
||||
|
|
@ -79,6 +83,34 @@ Then each slide separated by ---. Guidelines:
|
|||
- Use bullet points (- ) for lists, keep them concise (max 5 bullets per slide)
|
||||
- Include a summary/key takeaways slide at the end
|
||||
- Do NOT include HTML tags or inline styles`;
|
||||
}
|
||||
|
||||
// With questions — return JSON containing both Marp markdown and questions
|
||||
return source + '\n' +
|
||||
'Create a professional Marp presentation (' + slideHint + ') suitable for medical education, ' +
|
||||
'AND ' + questionCount + ' quiz questions based on the content.' + refineInstr + `
|
||||
|
||||
Return ONLY a valid JSON object (no markdown, no code fences) with this exact structure:
|
||||
{
|
||||
"title": "string",
|
||||
"marpMarkdown": "string (the complete Marp markdown starting with frontmatter ---\\nmarp: true\\ntheme: default\\n---)",
|
||||
"questions": [
|
||||
{
|
||||
"question_text": "string",
|
||||
"question_type": "mcq",
|
||||
"explanation": "string",
|
||||
"options": [
|
||||
{ "option_text": "string", "is_correct": false, "explanation": "string" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Marp guidelines inside marpMarkdown:
|
||||
- Start with: ---\\nmarp: true\\ntheme: default\\n---
|
||||
- Separate slides with \\n---\\n
|
||||
- First slide: title + subtitle. Use # for titles, bullets for content.
|
||||
- Each MCQ must have exactly 4 options, exactly 1 marked is_correct: true`;
|
||||
}
|
||||
|
||||
var wordHint = wordCount ? ' Target approximately ' + wordCount + ' words for the body.' : '';
|
||||
|
|
@ -176,16 +208,24 @@ router.post('/ai-generate', upload.single('file'), async function(req, res) {
|
|||
|
||||
var raw = result.content.trim();
|
||||
|
||||
// ── Presentation: return Marp markdown directly ──
|
||||
// ── Presentation ──
|
||||
if (contentType === 'presentation') {
|
||||
if (questionCount > 0) {
|
||||
// JSON response with marpMarkdown + questions
|
||||
var cleanRaw = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/, '');
|
||||
var parsedPres;
|
||||
try { parsedPres = JSON.parse(cleanRaw); }
|
||||
catch(e) {
|
||||
var m = cleanRaw.match(/\{[\s\S]*\}/);
|
||||
try { parsedPres = m ? JSON.parse(m[0]) : null; } catch(e2) { parsedPres = null; }
|
||||
}
|
||||
if (parsedPres && parsedPres.marpMarkdown) {
|
||||
return res.json({ success: true, contentType: 'presentation', marpMarkdown: parsedPres.marpMarkdown, questions: parsedPres.questions || [], model: result.model });
|
||||
}
|
||||
}
|
||||
// Plain Marp markdown (no questions requested, or parse failed)
|
||||
var marpMd = raw.replace(/^```(?:markdown|marp)?\s*/i, '').replace(/\s*```\s*$/, '');
|
||||
return res.json({
|
||||
success: true,
|
||||
contentType: 'presentation',
|
||||
marpMarkdown: marpMd,
|
||||
model: result.model,
|
||||
docLength: docText.length
|
||||
});
|
||||
return res.json({ success: true, contentType: 'presentation', marpMarkdown: marpMd, questions: [], model: result.model, docLength: docText.length });
|
||||
}
|
||||
|
||||
// Strip code fences if model added them anyway
|
||||
|
|
|
|||
Loading…
Reference in a new issue