-
Notifications
You must be signed in to change notification settings - Fork 224
Feature/quiz feedback 704 #736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| /* =============================================== | ||
| Pop Quiz Feedback Styles | ||
| =============================================== */ | ||
|
|
||
| /* Disable pointer events on answered questions */ | ||
| .quiz-question.answered .quiz-answer { | ||
| cursor: not-allowed; | ||
| } | ||
|
|
||
| .quiz-question.answered .quiz-answer:not(.quiz-show-answer) { | ||
| opacity: 0.5; | ||
| } | ||
|
|
||
| /* Disabled answers */ | ||
| .quiz-answer.quiz-disabled { | ||
| pointer-events: none; | ||
| opacity: 0.5; | ||
| } | ||
|
|
||
| /* Enhanced correct answer styling */ | ||
| .quiz-answer.quiz-show-answer.quiz-true { | ||
| background-color: #d4edda !important; | ||
| border-color: #28a745 !important; | ||
| animation: correctPulse 0.5s ease; | ||
| } | ||
|
|
||
| /* Enhanced incorrect answer styling */ | ||
| .quiz-answer.quiz-show-answer.quiz-false { | ||
| background-color: #f8d7da !important; | ||
| border-color: #dc3545 !important; | ||
| animation: incorrectShake 0.5s ease; | ||
| } | ||
|
|
||
| /* Animations */ | ||
| @keyframes correctPulse { | ||
| 0%, 100% { transform: scale(1); } | ||
| 50% { transform: scale(1.02); } | ||
| } | ||
|
|
||
| @keyframes incorrectShake { | ||
| 0%, 100% { transform: translateX(0); } | ||
| 25% { transform: translateX(-5px); } | ||
| 75% { transform: translateX(5px); } | ||
| } | ||
|
|
||
| /* Feedback Container */ | ||
| .quiz-feedback-container { | ||
| margin-top: 1.5rem; | ||
| } | ||
|
|
||
| /* Feedback Box */ | ||
| .quiz-feedback { | ||
| padding: 1.25rem; | ||
| border-radius: 8px; | ||
| animation: fadeInUp 0.4s ease; | ||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); | ||
| margin-bottom: 1rem; | ||
| } | ||
|
|
||
| @keyframes fadeInUp { | ||
| from { | ||
| opacity: 0; | ||
| transform: translateY(10px); | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } | ||
| } | ||
|
|
||
| .feedback-correct { | ||
| background-color: #d4edda; | ||
| border: 1px solid #c3e6cb; | ||
| color: #155724; | ||
| } | ||
|
|
||
| .feedback-incorrect { | ||
| background-color: #f8d7da; | ||
| border: 1px solid #f5c6cb; | ||
| color: #721c24; | ||
| } | ||
|
|
||
| .feedback-header { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 0.5rem; | ||
| margin-bottom: 0.75rem; | ||
| font-weight: 600; | ||
| font-size: 1.1rem; | ||
| } | ||
|
|
||
| .feedback-icon { | ||
| font-size: 1.5rem; | ||
| font-weight: bold; | ||
| } | ||
|
|
||
| .feedback-correct .feedback-icon { | ||
| color: #28a745; | ||
| } | ||
|
|
||
| .feedback-incorrect .feedback-icon { | ||
| color: #dc3545; | ||
| } | ||
|
|
||
| .feedback-message { | ||
| line-height: 1.6; | ||
| font-size: 0.95rem; | ||
| margin-bottom: 1rem; | ||
| } | ||
|
|
||
| /* Reset button */ | ||
| .quiz-reset-btn { | ||
| padding: 0.5rem 1rem; | ||
| background-color: #6c757d; | ||
| color: white; | ||
| border: none; | ||
| border-radius: 4px; | ||
| cursor: pointer; | ||
| font-size: 0.9rem; | ||
| transition: background-color 0.2s ease; | ||
|
|
||
| &:hover { | ||
| background-color: #5a6268; | ||
| } | ||
|
|
||
| &:active { | ||
| transform: scale(0.98); | ||
| } | ||
| } | ||
|
|
||
| /* Dark mode adjustments */ | ||
| body.dark-mode { | ||
| .feedback-correct { | ||
| background-color: #1e3a28; | ||
| border-color: #28a745; | ||
| color: #7fdb9f; | ||
| } | ||
|
|
||
| .feedback-incorrect { | ||
| background-color: #3a1e1e; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Color literals like |
||
| border-color: #dc3545; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Color literals like |
||
| color: #f5b7bd; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Color literals like |
||
| } | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line contains trailing whitespace |
||
| .quiz-answer.quiz-show-answer.quiz-true { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line should be indented 2 spaces, but was indented 4 spaces |
||
| background-color: #1e3a28 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| border-color: #28a745 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| } | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line contains trailing whitespace |
||
| .quiz-answer.quiz-show-answer.quiz-false { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line should be indented 2 spaces, but was indented 4 spaces |
||
| background-color: #3a1e1e !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| border-color: #dc3545 !important; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !important should not be used |
||
| } | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line contains trailing whitespace |
||
| .quiz-feedback { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line should be indented 2 spaces, but was indented 4 spaces |
||
| box-shadow: 0 2px 8px rgba(255, 255, 255, 0.1); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Color literals like |
||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Files should end with a trailing newline |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| --- | ||
| --- | ||
| {% include css/just-the-docs.scss.liquid color_scheme="circuitverse" %} | ||
|
|
||
| @import "quiz-feedback"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,4 +4,6 @@ | |
|
|
||
| .text-grey-dk-100 { | ||
| color: white!important; | ||
| } | ||
| } | ||
|
|
||
| @import "quiz-feedback"; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,16 +15,28 @@ $(function() { | |
| question.append('<em>Question ' + questionNo + '</em>'); | ||
| question.append('<p>' + questionText + '</p>'); | ||
|
|
||
| // Get answers | ||
| // Get answers with explanations/hints | ||
| var answers = []; | ||
| $(this).find('ul').each(function() { | ||
|
|
||
| // Correct answers from <ol> with explanations | ||
| $(this).find('ol').each(function() { | ||
| $(this).children('li').each(function() { | ||
| answers.push([false, $(this).contents().get(0).nodeValue.trim()]); | ||
| var text = $(this).contents().get(0).nodeValue.trim(); | ||
| // Check for explanation in <em> tag, otherwise use default | ||
| var explanation = $(this).find('em').text() || | ||
| "Correct! Well done."; | ||
| answers.push([true, text, explanation]); | ||
| }); | ||
| }); | ||
| $(this).find('ol').each(function() { | ||
|
|
||
| // Incorrect answers from <ul> with hints | ||
| $(this).find('ul').each(function() { | ||
| $(this).children('li').each(function() { | ||
| answers.push([true, $(this).contents().get(0).nodeValue.trim()]); | ||
| var text = $(this).contents().get(0).nodeValue.trim(); | ||
| // Check for hint in <em> tag, otherwise use default | ||
| var hint = $(this).find('em').text() || | ||
| "That's not correct. Try again!"; | ||
| answers.push([false, text, hint]); | ||
| }); | ||
| }); | ||
|
Comment on lines
+18
to
41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden answer text extraction and escaping to avoid edgeβcase crashes and XSS The new logic for pulling answer text and explanations/hints largely meets the UX goals, but itβs brittle and slightly unsafe:
To make this more robust you can:
For example: - var text = $(this).contents().get(0).nodeValue.trim();
+ // Extract visible answer text, excluding any <em> explanation node
+ var $li = $(this).clone();
+ $li.children('em').remove();
+ var text = $li.text().trim();
@@
- var answerDiv = $('<div class="' + classes + '" data-feedback="' +
- escapeHtml(answer[2]) + '">' +
- answer[1] +
- '</div>');
+ var answerDiv = $('<div class="' + classes + '" data-feedback="' +
+ escapeHtml(answer[2]) + '">' +
+ escapeHtml(answer[1]) +
+ '</div>');This keeps the authoring experience the same but guards against malformed markup and unintended HTML interpretation. Also applies to: 57-63 π€ Prompt for AI Agents |
||
|
|
||
|
|
@@ -42,25 +54,121 @@ $(function() { | |
| classes += 'quiz-false'; | ||
| } | ||
|
|
||
| questionAnswers.append('<div class="' + classes + '" onclick="ShowQuizAnswer(this)">' + | ||
| answer[1] + | ||
| '</div>'); | ||
| var answerDiv = $('<div class="' + classes + '" data-feedback="' + | ||
| escapeHtml(answer[2]) + '">' + | ||
| answer[1] + | ||
| '</div>'); | ||
|
|
||
| questionAnswers.append(answerDiv); | ||
| }); | ||
|
|
||
| // Add feedback container | ||
| var feedbackContainer = $('<div class="quiz-feedback-container"></div>'); | ||
|
|
||
| question.append(questionAnswers); | ||
| question.append(feedbackContainer); | ||
|
|
||
| quiz.append(question); | ||
| }); | ||
|
|
||
| quizSettings.after('<br>'); | ||
| quizSettings.after(quiz); | ||
| quizSettings.remove(); | ||
|
|
||
| // Attach click handlers after DOM insertion | ||
| $('.quiz-answer').on('click', ShowQuizAnswer); | ||
| } | ||
| }); | ||
|
|
||
| function ShowQuizAnswer(element) { | ||
| if (!$(element).hasClass('quiz-show-answer')) { | ||
| $(element).addClass('quiz-show-answer'); | ||
| function ShowQuizAnswer(event) { | ||
| var element = $(event.currentTarget); | ||
| var questionContainer = element.closest('.quiz-question'); | ||
| var allAnswers = questionContainer.find('.quiz-answer'); | ||
| var feedbackContainer = questionContainer.find('.quiz-feedback-container'); | ||
|
|
||
| // Prevent re-selection | ||
| if (questionContainer.hasClass('answered')) { | ||
| return; | ||
| } | ||
|
|
||
| // Mark question as answered | ||
| questionContainer.addClass('answered'); | ||
|
|
||
| // Show the selected answer | ||
| if (!element.hasClass('quiz-show-answer')) { | ||
| element.addClass('quiz-show-answer'); | ||
| } | ||
|
|
||
| // Disable all other answers | ||
| allAnswers.not(element).addClass('quiz-disabled'); | ||
|
|
||
| // Determine if correct or incorrect | ||
| var isCorrect = element.hasClass('quiz-true'); | ||
| var feedbackText = element.data('feedback'); | ||
|
|
||
| // Create and show feedback | ||
| var feedbackDiv = $('<div class="quiz-feedback"></div>'); | ||
|
|
||
| if (isCorrect) { | ||
| feedbackDiv.addClass('feedback-correct'); | ||
| feedbackDiv.html( | ||
| '<div class="feedback-header">' + | ||
| '<span class="feedback-icon">β</span>' + | ||
| '<span class="feedback-title">Correct!</span>' + | ||
| '</div>' + | ||
| '<div class="feedback-message">' + feedbackText + '</div>' | ||
| ); | ||
| } else { | ||
| feedbackDiv.addClass('feedback-incorrect'); | ||
| feedbackDiv.html( | ||
| '<div class="feedback-header">' + | ||
| '<span class="feedback-icon">β</span>' + | ||
| '<span class="feedback-title">Not quite right</span>' + | ||
| '</div>' + | ||
| '<div class="feedback-message">' + feedbackText + '</div>' | ||
| ); | ||
| } | ||
|
|
||
| // Add reset button | ||
| var resetBtn = $('<button class="quiz-reset-btn">Try Again</button>'); | ||
| resetBtn.on('click', function() { | ||
| ResetQuiz(questionContainer); | ||
| }); | ||
| feedbackDiv.append(resetBtn); | ||
|
|
||
| // Show feedback with animation | ||
| feedbackContainer.html(feedbackDiv); | ||
|
|
||
| // Smooth scroll to feedback | ||
| setTimeout(function() { | ||
| feedbackDiv[0].scrollIntoView({ | ||
| behavior: 'smooth', | ||
| block: 'nearest' | ||
| }); | ||
| }, 100); | ||
| } | ||
|
|
||
| function ResetQuiz(questionContainer) { | ||
| // Remove answered state | ||
| questionContainer.removeClass('answered'); | ||
|
|
||
| // Reset all answers | ||
| var allAnswers = questionContainer.find('.quiz-answer'); | ||
| allAnswers.removeClass('quiz-show-answer quiz-disabled'); | ||
|
|
||
| // Clear feedback | ||
| questionContainer.find('.quiz-feedback-container').empty(); | ||
| } | ||
|
|
||
| function escapeHtml(text) { | ||
| var map = { | ||
| '&': '&', | ||
| '<': '<', | ||
| '>': '>', | ||
| '"': '"', | ||
| "'": ''' | ||
| }; | ||
| return text.replace(/[&<>"']/g, function(m) { return map[m]; }); | ||
| } | ||
|
|
||
| function FilterHtml(contents) { | ||
|
|
@@ -90,4 +198,4 @@ function FilterHtml(contents) { | |
| }); | ||
|
|
||
| return html; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line should be indented 2 spaces, but was indented 4 spaces