@0m0g1/griot 0.1.16 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/blocks/BlockRenderer.js +40 -157
package/package.json
CHANGED
|
@@ -432,184 +432,67 @@ function _renderQuiz(block, opts) {
|
|
|
432
432
|
submitBtn.textContent = 'Check answers';
|
|
433
433
|
submitBtn.addEventListener('click', () => {
|
|
434
434
|
let score = 0;
|
|
435
|
+
let answered = 0;
|
|
435
436
|
const answers = {};
|
|
437
|
+
|
|
436
438
|
questions.forEach((q, idx) => {
|
|
437
439
|
const qid = q.id || `q${idx}`;
|
|
438
440
|
const radios = form.querySelectorAll(`input[name="quiz_${block.id}_${qid}"]`);
|
|
439
441
|
let selected = null;
|
|
440
442
|
radios.forEach((r, i) => { if (r.checked) selected = i; });
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (
|
|
448
|
-
|
|
443
|
+
|
|
444
|
+
// Only count attempted questions
|
|
445
|
+
if (selected !== null) {
|
|
446
|
+
answered++;
|
|
447
|
+
answers[qid] = selected;
|
|
448
|
+
const isCorrect = selected === q.correctOption;
|
|
449
|
+
if (isCorrect) score++;
|
|
450
|
+
|
|
451
|
+
const feedbackDiv = form.querySelector(`fieldset[data-index="${idx}"] .griot-quiz__feedback`);
|
|
452
|
+
if (feedbackDiv) {
|
|
453
|
+
if (isCorrect) {
|
|
454
|
+
feedbackDiv.textContent = '✓ Correct!';
|
|
455
|
+
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--correct';
|
|
456
|
+
} else {
|
|
457
|
+
const correctAnswerText = q.options[q.correctOption];
|
|
458
|
+
feedbackDiv.innerHTML = `✗ Incorrect. Correct answer: ${escapeHtml(correctAnswerText)}. ${escapeHtml(q.explanation || '')}`;
|
|
459
|
+
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--wrong';
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
// Unanswered — show skipped, don't penalise
|
|
464
|
+
const feedbackDiv = form.querySelector(`fieldset[data-index="${idx}"] .griot-quiz__feedback`);
|
|
465
|
+
if (feedbackDiv) {
|
|
466
|
+
feedbackDiv.textContent = '— Skipped';
|
|
449
467
|
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--missing';
|
|
450
|
-
} else if (isCorrect) {
|
|
451
|
-
feedbackDiv.textContent = '✓ Correct!';
|
|
452
|
-
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--correct';
|
|
453
|
-
} else {
|
|
454
|
-
const correctAnswerText = q.options[q.correctOption];
|
|
455
|
-
feedbackDiv.innerHTML = `✗ Incorrect. Correct answer: ${escapeHtml(correctAnswerText)}. ${escapeHtml(q.explanation || '')}`;
|
|
456
|
-
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--wrong';
|
|
457
468
|
}
|
|
458
469
|
}
|
|
459
470
|
});
|
|
460
|
-
const totalScore = questions.length;
|
|
461
471
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
scoreDiv.className = 'griot-quiz__score';
|
|
466
|
-
scoreDiv.textContent = `You scored ${score} out of ${totalScore}.`;
|
|
467
|
-
container.appendChild(scoreDiv);
|
|
468
|
-
|
|
469
|
-
if (typeof opts.onQuizSubmit === 'function') {
|
|
470
|
-
opts.onQuizSubmit(block.id, score, totalScore, answers);
|
|
472
|
+
if (answered === 0) {
|
|
473
|
+
alert('Please answer at least one question.');
|
|
474
|
+
return;
|
|
471
475
|
}
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
form.appendChild(submitBtn);
|
|
475
|
-
container.appendChild(form);
|
|
476
|
-
return container;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function _renderQuiz(block, opts) {
|
|
480
|
-
const { meta = {} } = block;
|
|
481
|
-
const title = meta.title || '';
|
|
482
|
-
const questions = Array.isArray(meta.questions) ? meta.questions : [];
|
|
483
|
-
const answers = meta._submittedAnswers || {}; // optional: store last answers
|
|
484
|
-
|
|
485
|
-
const container = document.createElement('div');
|
|
486
|
-
container.className = 'griot-block griot-quiz';
|
|
487
|
-
|
|
488
|
-
if (title) {
|
|
489
|
-
const titleEl = document.createElement('h4');
|
|
490
|
-
titleEl.className = 'griot-quiz__title';
|
|
491
|
-
titleEl.textContent = title;
|
|
492
|
-
container.appendChild(titleEl);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
if (questions.length === 0) {
|
|
496
|
-
const empty = document.createElement('p');
|
|
497
|
-
empty.className = 'griot-quiz__empty';
|
|
498
|
-
empty.textContent = 'No questions yet.';
|
|
499
|
-
container.appendChild(empty);
|
|
500
|
-
return container;
|
|
501
|
-
}
|
|
502
476
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
questions.forEach((q, idx) => {
|
|
510
|
-
const qid = q.id || `q${idx}`;
|
|
511
|
-
const text = q.text || `Question ${idx + 1}`;
|
|
512
|
-
const options = Array.isArray(q.options) ? q.options : [];
|
|
513
|
-
const correctIdx = q.correctOption; // 0‑based index
|
|
514
|
-
const explanation = q.explanation || '';
|
|
515
|
-
const userChoice = answers[qid] !== undefined ? answers[qid] : null;
|
|
516
|
-
|
|
517
|
-
const fieldset = document.createElement('fieldset');
|
|
518
|
-
fieldset.className = 'griot-quiz__question';
|
|
519
|
-
fieldset.dataset.index = idx;
|
|
520
|
-
|
|
521
|
-
const legend = document.createElement('legend');
|
|
522
|
-
legend.className = 'griot-quiz__question-text';
|
|
523
|
-
legend.innerHTML = `<span class="griot-quiz__q-num">${idx + 1}.</span> ${escapeHtml(text)}`;
|
|
524
|
-
fieldset.appendChild(legend);
|
|
525
|
-
|
|
526
|
-
const optionsContainer = document.createElement('div');
|
|
527
|
-
optionsContainer.className = 'griot-quiz__options';
|
|
528
|
-
|
|
529
|
-
options.forEach((opt, optIdx) => {
|
|
530
|
-
const label = document.createElement('label');
|
|
531
|
-
label.className = 'griot-quiz__option';
|
|
532
|
-
|
|
533
|
-
const radio = document.createElement('input');
|
|
534
|
-
radio.type = 'radio';
|
|
535
|
-
radio.name = `quiz_${block.id}_${qid}`;
|
|
536
|
-
radio.value = optIdx;
|
|
537
|
-
if (userChoice === optIdx) radio.checked = true;
|
|
538
|
-
radio.addEventListener('change', () => {
|
|
539
|
-
// Update stored answers in meta (optional – allows preserving after submit)
|
|
540
|
-
const newAnswers = { ...(block.meta._submittedAnswers || {}), [qid]: optIdx };
|
|
541
|
-
// We'll trigger an external callback later – for now just store in meta
|
|
542
|
-
// But meta updates must go through the editor, not viewer. So we only calculate on‑the‑fly.
|
|
543
|
-
// Instead, we call a user callback when the quiz is submitted.
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
const span = document.createElement('span');
|
|
547
|
-
span.textContent = `${String.fromCharCode(65 + optIdx)}. ${escapeHtml(opt)}`;
|
|
548
|
-
|
|
549
|
-
label.appendChild(radio);
|
|
550
|
-
label.appendChild(span);
|
|
551
|
-
optionsContainer.appendChild(label);
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
fieldset.appendChild(optionsContainer);
|
|
555
|
-
|
|
556
|
-
// Show correct / wrong feedback after evaluation
|
|
557
|
-
const feedback = document.createElement('div');
|
|
558
|
-
feedback.className = 'griot-quiz__feedback';
|
|
559
|
-
fieldset.appendChild(feedback);
|
|
560
|
-
|
|
561
|
-
form.appendChild(fieldset);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const submitBtn = document.createElement('button');
|
|
565
|
-
submitBtn.type = 'button';
|
|
566
|
-
submitBtn.className = 'griot-quiz__submit';
|
|
567
|
-
submitBtn.textContent = 'Check answers';
|
|
568
|
-
submitBtn.addEventListener('click', () => {
|
|
569
|
-
let score = 0;
|
|
570
|
-
const newAnswers = {};
|
|
571
|
-
questions.forEach((q, idx) => {
|
|
572
|
-
const qid = q.id || `q${idx}`;
|
|
573
|
-
const radios = form.querySelectorAll(`input[name="quiz_${block.id}_${qid}"]`);
|
|
574
|
-
let selected = null;
|
|
575
|
-
radios.forEach((r, i) => { if (r.checked) selected = i; });
|
|
576
|
-
newAnswers[qid] = selected;
|
|
577
|
-
const isCorrect = (selected !== null && selected === q.correctOption);
|
|
578
|
-
if (isCorrect) score++;
|
|
579
|
-
|
|
580
|
-
// Show feedback per question
|
|
581
|
-
const feedbackDiv = form.querySelector(`fieldset[data-index="${idx}"] .griot-quiz__feedback`);
|
|
582
|
-
if (feedbackDiv) {
|
|
583
|
-
if (selected === null) {
|
|
584
|
-
feedbackDiv.textContent = '❓ No answer selected.';
|
|
585
|
-
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--missing';
|
|
586
|
-
} else if (isCorrect) {
|
|
587
|
-
feedbackDiv.textContent = '✓ Correct!';
|
|
588
|
-
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--correct';
|
|
589
|
-
} else {
|
|
590
|
-
const correctAnswerText = q.options[q.correctOption];
|
|
591
|
-
feedbackDiv.innerHTML = `✗ Incorrect. Correct answer: ${escapeHtml(correctAnswerText)}. ${escapeHtml(q.explanation || '')}`;
|
|
592
|
-
feedbackDiv.className = 'griot-quiz__feedback griot-quiz__feedback--wrong';
|
|
593
|
-
}
|
|
594
|
-
if (q.explanation && selected !== null && !isCorrect) {
|
|
595
|
-
// Already added above
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
totalScore = questions.length;
|
|
600
|
-
userScore = score;
|
|
477
|
+
// Lock the form — prevent resubmit
|
|
478
|
+
form.querySelectorAll('input[type="radio"]').forEach(r => r.disabled = true);
|
|
479
|
+
submitBtn.disabled = true;
|
|
480
|
+
submitBtn.textContent = 'Submitted';
|
|
481
|
+
submitBtn.style.opacity = '0.5';
|
|
482
|
+
submitBtn.style.cursor = 'default';
|
|
601
483
|
|
|
602
|
-
//
|
|
484
|
+
// Show score
|
|
603
485
|
const existingScore = container.querySelector('.griot-quiz__score');
|
|
604
486
|
if (existingScore) existingScore.remove();
|
|
605
487
|
const scoreDiv = document.createElement('div');
|
|
606
488
|
scoreDiv.className = 'griot-quiz__score';
|
|
607
|
-
scoreDiv.textContent =
|
|
489
|
+
scoreDiv.textContent = answered < questions.length
|
|
490
|
+
? `You scored ${score} out of ${answered} answered (${questions.length - answered} skipped).`
|
|
491
|
+
: `You scored ${score} out of ${answered}.`;
|
|
608
492
|
container.appendChild(scoreDiv);
|
|
609
493
|
|
|
610
|
-
// Fire user callback if provided
|
|
611
494
|
if (typeof opts.onQuizSubmit === 'function') {
|
|
612
|
-
opts.onQuizSubmit(block.id, score,
|
|
495
|
+
opts.onQuizSubmit(block.id, score, answered, answers);
|
|
613
496
|
}
|
|
614
497
|
});
|
|
615
498
|
|