diff --git a/htdocs/js/Essay/essay.js b/htdocs/js/Essay/essay.js
index 2d0fab6b38..c3a6a59def 100644
--- a/htdocs/js/Essay/essay.js
+++ b/htdocs/js/Essay/essay.js
@@ -1,17 +1,28 @@
'use strict';
(() => {
- const addPreviewButton = (latexEntry) => {
- if (latexEntry.dataset.previewBtnAdded) return;
- latexEntry.dataset.previewBtnAdded = 'true';
+ const initializePreviewButton = (latexEntry) => {
+ if (latexEntry.dataset.previewBtnInitialized) return;
+ latexEntry.dataset.previewBtnInitialized = 'true';
- const buttonContainer = document.createElement('div');
- buttonContainer.classList.add('latexentry-button-container', 'mt-1');
+ const buttonContainer =
+ document.getElementById(`${latexEntry.id}-latexentry-button-container`) || document.createElement('div');
- const button = document.createElement('button');
- button.type = 'button';
- button.classList.add('latexentry-preview', 'btn', 'btn-secondary', 'btn-sm');
- button.textContent = 'Preview';
+ if (!buttonContainer.classList.contains('latexentry-button-container')) {
+ buttonContainer.classList.add('latexentry-button-container', 'mt-1');
+ buttonContainer.id = `${latexEntry.id}-latexentry-button-container`;
+ latexEntry.after(buttonContainer);
+ }
+
+ const button = buttonContainer.querySelector('.latexentry-preview') || document.createElement('button');
+
+ if (!button.classList.contains('latexentry-preview')) {
+ button.type = 'button';
+ button.classList.add('latexentry-preview', 'btn', 'btn-secondary', 'btn-sm');
+ button.textContent = 'Preview';
+
+ buttonContainer.append(button);
+ }
button.addEventListener('click', () => {
button.dataset.bsContent = latexEntry.value
@@ -49,19 +60,16 @@
popover.show();
}
});
-
- buttonContainer.append(button);
- latexEntry.after(buttonContainer);
};
- document.querySelectorAll('.latexentryfield').forEach(addPreviewButton);
+ document.querySelectorAll('.latexentryfield').forEach(initializePreviewButton);
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
for (const node of mutation.addedNodes) {
if (node instanceof Element) {
- if (node.classList.contains('latexentryfield')) addPreviewButton(node);
- else node.querySelectorAll('.latexentryfield').forEach(addPreviewButton);
+ if (node.classList.contains('latexentryfield')) initializePreviewButton(node);
+ else node.querySelectorAll('.latexentryfield').forEach(initializePreviewButton);
}
}
}
diff --git a/htdocs/js/GraphTool/graphtool.scss b/htdocs/js/GraphTool/graphtool.scss
index bb46301ea4..25d4ea5055 100644
--- a/htdocs/js/GraphTool/graphtool.scss
+++ b/htdocs/js/GraphTool/graphtool.scss
@@ -366,3 +366,15 @@
}
}
}
+
+.graphtool-outer-container {
+ position: relative;
+ width: fit-content;
+
+ .ww-feedback-btn {
+ position: absolute;
+ left: 100%;
+ top: 0;
+ margin-left: 0.25rem;
+ }
+}
diff --git a/htdocs/js/InputColor/color.js b/htdocs/js/InputColor/color.js
index 25fa134837..e7e125e731 100644
--- a/htdocs/js/InputColor/color.js
+++ b/htdocs/js/InputColor/color.js
@@ -11,7 +11,7 @@
// Color all of the inputs and selects associated with this answer. On the first pass radio inputs are
// collected into groups by name, and on the second pass the checked radio is highlighted, or if none are
// checked all are highlighted.
- document.querySelectorAll(`input[name*=${answerId}],select[name*=${answerId}`)
+ document.querySelectorAll(`input[name*=${answerId}],select[name*=${answerId}]`)
.forEach((input) => {
if (input.type.toLowerCase() === 'radio') {
if (!radioGroups[input.name]) radioGroups[input.name] = [];
@@ -46,6 +46,30 @@
}
};
+ for (const button of document.querySelectorAll('.ww-feedback-btn')) {
+ new bootstrap.Popover(button, { allowList: { ...bootstrap.Popover.Default.allowList, script: [/.*/] } });
+
+ // Render MathJax previews.
+ if (window.MathJax) {
+ button.addEventListener('show.bs.popover', () => {
+ MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise(['.popover-body']));
+ });
+ }
+
+ // Execute javascript in the answer preview.
+ button.addEventListener('shown.bs.popover', () => {
+ document
+ .querySelector('.popover.ww-result-popover')
+ ?.querySelectorAll('script')
+ .forEach((origScript) => {
+ const newScript = document.createElement('script');
+ Array.from(origScript.attributes).forEach((attr) => newScript.setAttribute(attr.name, attr.value));
+ newScript.appendChild(document.createTextNode(origScript.innerHTML));
+ origScript.parentNode.replaceChild(newScript, origScript);
+ });
+ });
+ }
+
// Color inputs already on the page.
document.querySelectorAll('td a[data-answer-id]').forEach(setupAnswerLink);
diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js
index 57ad5fa0e1..344ca64ad4 100644
--- a/htdocs/js/MathQuill/mqeditor.js
+++ b/htdocs/js/MathQuill/mqeditor.js
@@ -31,6 +31,14 @@
input.classList.add('mq-edit');
answerQuill.latexInput = mq_input;
+ // Give the mathquill answer box the correct/incorrect colors.
+ if (input.classList.contains('correct')) answerQuill.classList.add('correct');
+ if (input.classList.contains('incorrect')) answerQuill.classList.add('incorrect');
+ if (input.classList.contains('partially-correct')) answerQuill.classList.add('partically-correct');
+
+ const ariaDescribedBy = input.getAttribute('aria-describedby');
+ if (ariaDescribedBy) answerQuill.setAttribute('aria-describedby', ariaDescribedBy);
+
// Default options.
const cfgOptions = {
spaceBehavesLikeTab: true,
@@ -130,8 +138,8 @@
button.append(icon);
// Find the preview button container, and add the equation editor button to that.
- const buttonContainer = container.nextElementSibling;
- if (buttonContainer && buttonContainer.classList.contains('latexentry-button-container')) {
+ const buttonContainer = document.getElementById(`${answerLabel}-latexentry-button-container`);
+ if (buttonContainer) {
buttonContainer.classList.add('d-flex', 'gap-1');
buttonContainer.prepend(button);
innerContainer.append(buttonContainer);
@@ -494,26 +502,6 @@
answerQuill.mathField.latex(answerQuill.latexInput.value);
answerQuill.mathField.moveToLeftEnd();
answerQuill.mathField.blur();
-
- // Look for a result in the attempts table for this answer.
- for (const tableLink of document.querySelectorAll('td a[data-answer-id]')) {
- // Give the mathquill answer box the correct/incorrect colors.
- if (answerLabel.includes(tableLink.dataset.answerId)) {
- if (tableLink.parentNode.classList.contains('ResultsWithoutError'))
- answerQuill.classList.add('correct');
- else {
- if (answerQuill.input.value !== '') answerQuill.classList.add('incorrect');
- }
- }
-
- // Make a click on the results table link give focus to the mathquill answer box.
- if (answerLabel === tableLink.dataset.answerId) {
- tableLink.addEventListener('click', (e) => {
- e.preventDefault();
- answerQuill.textarea.focus();
- });
- }
- }
};
// Set up MathQuill inputs that are already in the page.
diff --git a/htdocs/js/MathQuill/mqeditor.scss b/htdocs/js/MathQuill/mqeditor.scss
index ec2f517c38..82341e521e 100644
--- a/htdocs/js/MathQuill/mqeditor.scss
+++ b/htdocs/js/MathQuill/mqeditor.scss
@@ -3,7 +3,7 @@
span[id^='mq-answer'] {
/*rtl:ignore*/
direction: ltr;
- padding: 4px 5px 2px 5px;
+ padding: 4px 5px;
border-radius: 4px !important;
background-color: white;
margin-right: 0;
diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss
index 20c10cf4b2..cb8a9be062 100644
--- a/htdocs/js/Problem/problem.scss
+++ b/htdocs/js/Problem/problem.scss
@@ -33,13 +33,18 @@
}
/* Problem elements */
- label, input[type=text], select, textarea {
+ label,
+ input[type='text'],
+ select,
+ textarea {
font-weight: normal;
line-height: 18px;
width: auto;
}
- select, textarea, input[type=text] {
+ select,
+ textarea,
+ input[type='text'] {
display: inline-block;
padding: 4px 6px;
margin-bottom: 0;
@@ -50,17 +55,21 @@
background-color: white;
}
- textarea, input[type=text] {
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ textarea,
+ input[type='text'] {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
- input[type=text] {
+ input[type='text'] {
height: 30px;
font-size: 14px;
line-height: 20px;
}
- select, input[type=text], input[type=radio], input[type=checkbox] {
+ select,
+ input[type='text'],
+ input[type='radio'],
+ input[type='checkbox'] {
&.correct {
border-color: rgba(81, 153, 81, 0.8); /* green */
outline: 0;
@@ -72,11 +81,12 @@
border-color: rgba(191, 84, 84, 0.8); /* red */
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px 2px rgba(191, 84, 84, 0.6);
- color:inherit;
+ color: inherit;
}
}
- input[type=text], span.mq-editable-field {
+ input[type='text'],
+ span.mq-editable-field {
background-size: 20px auto;
background-position: right 4px center;
background-repeat: no-repeat;
@@ -92,14 +102,15 @@
}
}
- input[type=radio] {
+ input[type='radio'] {
margin-right: 0.25rem;
}
select {
cursor: pointer;
- &[multiple], &[size] {
+ &[multiple],
+ &[size] {
height: auto;
}
}
@@ -108,7 +119,10 @@
max-width: 100%;
}
- input[type=text], input[type=radio], textarea, select {
+ input[type='text'],
+ input[type='radio'],
+ textarea,
+ select {
&:focus {
border-color: rgba(82, 168, 236, 0.8);
outline: 0;
@@ -118,7 +132,12 @@
.pg-table {
text-align: center;
- thead, tbody, tfoot, tr, td, th {
+ thead,
+ tbody,
+ tfoot,
+ tr,
+ td,
+ th {
padding: 0.25rem;
border: 1px solid black;
}
@@ -179,7 +198,8 @@ table.attemptResults {
border-bottom-right-radius: 4px;
}
- td, th {
+ td,
+ th {
border-style: inset;
border-width: 1px;
text-align: center;
@@ -230,7 +250,8 @@ table.attemptResults {
height: 100%;
}
- a, a span {
+ a,
+ a span {
color: #038;
text-decoration: none;
@@ -240,7 +261,9 @@ table.attemptResults {
}
}
-div, label, span {
+div,
+label,
+span {
&.ResultsWithoutError {
color: #0f5132; /* Dark Green */
background-color: #8f8; /* Light Green */
@@ -269,6 +292,67 @@ div, label, span {
}
}
+/* Feeback */
+.ww-result-popover {
+ --bs-popover-body-padding-x: 0;
+ --bs-popover-body-padding-y: 0;
+ --bs-popover-max-width: 400px;
+ --bs-popover-zindex: 18;
+
+ .popover-header {
+ text-align: center;
+ }
+
+ &.correct {
+ .popover-header {
+ --bs-popover-header-bg: #8f8;
+ }
+ }
+
+ &.incorrect {
+ .popover-header {
+ --bs-popover-header-bg: #d69191;
+ }
+ }
+
+ &.partially-correct {
+ .popover-header {
+ --bs-popover-header-bg: #ffc107;
+ }
+ }
+
+ .popover-body {
+ .card {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ --bs-card-spacer-y: 0.5rem;
+ --bs-card-cap-bg: #ddd;
+
+ .card-header {
+ border-radius: 0;
+
+ &:not(:first-child) {
+ border-top: var(--bs-card-border-width) solid var(--bs-card-border-color);
+ }
+ }
+
+ .card-body {
+ mjx-container {
+ margin: 0;
+ }
+
+ .parsehilight {
+ background-color: yellow;
+ }
+
+ &.feedback-message {
+ background-color: #ede275;
+ }
+ }
+ }
+ }
+}
+
/* Comments */
div.AuthorComment {
diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm
index 24234c65c1..29bb89af85 100644
--- a/lib/WeBWorK/PG.pm
+++ b/lib/WeBWorK/PG.pm
@@ -176,7 +176,6 @@ sub new_helper ($invocant, %options) {
# This must be done before the post processing, since the image tags output by the image generator initially
# include markers which are invalid html. Mojo::DOM will change these markers into attributes and this will fail.
if ($image_generator) {
- my $sourceFile = "$options{templateDirectory}$options{sourceFilePath}";
$image_generator->render(
refresh => $options{refreshMath2img} // 0,
body_text => $translator->r_text,
diff --git a/macros/PG.pl b/macros/PG.pl
index b3ad54d5e6..359375263c 100644
--- a/macros/PG.pl
+++ b/macros/PG.pl
@@ -910,6 +910,183 @@ sub ENDDOCUMENT {
$PG->{flags}{solutionExists} = $solutionExists // 0;
$PG->{flags}{comment} = $pgComment // '';
+ if ($main::displayMode =~ /HTML/i
+ && ($inputs_ref->{submitAnswers} || $inputs_ref->{previewAnswers} || $rh_envir->{forceShowAttemptFeedback}))
+ {
+ add_content_post_processor(sub {
+ my $problemContents = shift;
+ return if $main::displayMode eq 'TeX';
+
+ my $correct = 0;
+ for my $answerLabel (keys %{ $PG->{PG_ANSWERS_HASH} }) {
+ my $response_obj = $PG->{PG_ANSWERS_HASH}{$answerLabel}->response_obj;
+ for my $response ($response_obj->response_labels) {
+ next if ref($response_obj->{responses}{$response});
+ my $ansHash = $PG->{PG_ANSWERS_HASH}{$answerLabel}->{ans_eval}{rh_ans};
+
+ my $feedbackElement = $problemContents->at(
+ sprintf($ansHash->{feedback_element_selector} // 'input[name="%s"]', $response));
+ next unless $feedbackElement;
+
+ my $feedbackContainer = $problemContents->at(qq{[data-feedback-container-for="$response"]});
+
+ my $answerScore = $ansHash->{score} // 0;
+
+ my ($isEssay, $result, $resultClass, $btnClass) = (0, '', '', 'btn-secondary');
+ if ($answerScore >= 1) {
+ $result = maketext('Correct');
+ $resultClass = 'correct';
+ $btnClass = 'btn-success';
+ } elsif (($ansHash->{type} // '') eq 'essay') {
+ $isEssay = 1;
+ $result = maketext('Ungraded');
+ } elsif ($answerScore == 0) {
+ $resultClass = 'incorrect';
+ $result = maketext('Incorrect');
+ $btnClass = 'btn-danger';
+ } else {
+ $resultClass = 'partially-correct';
+ $result = maketext('[_1]% correct', round($answerScore * 100));
+ $btnClass = 'btn-warning';
+ }
+
+ $feedbackElement->attr(class => join(' ', $resultClass, $feedbackElement->attr->{class} || ()))
+ if $resultClass;
+ $feedbackElement->attr('aria-describedby' => "ww-feedback-$response");
+
+ my @lines;
+
+ sub previewAnswer {
+ my $ansHash = shift;
+
+ return
+ unless defined $ansHash->{preview_latex_string} && $ansHash->{preview_latex_string} =~ /\S/;
+
+ if ($main::displayMode eq 'HTML' || $ansHash->{non_tex_preview}) {
+ return $ansHash->{preview_latex_string};
+ } elsif ($main::displayMode eq 'HTML_dpng') {
+ return $rh_envir->{imagegen}->add($ansHash->{preview_latex_string});
+ } elsif ($main::displayMode eq 'HTML_MathJax') {
+ return "\\[$ansHash->{preview_latex_string}\\]";
+ }
+ }
+
+ sub previewCorrectAnswer {
+ my $ansHash = shift;
+
+ return $ansHash->{correct_ans}
+ unless defined $ansHash->{correct_ans_latex_string}
+ && $ansHash->{correct_ans_latex_string} =~ /\S/;
+
+ if ($main::displayMode eq 'HTML' || $ansHash->{non_tex_preview}) {
+ return $ansHash->{correct_ans_latex_string};
+ } elsif ($main::displayMode eq 'HTML_dpng') {
+ return $rh_envir->{imagegen}->add($ansHash->{correct_ans_latex_string});
+ } elsif ($main::displayMode eq 'HTML_MathJax') {
+ return "\\[$ansHash->{correct_ans_latex_string}\\]";
+ }
+ }
+
+ sub feedbackLine {
+ my ($title, $line, $class) = @_;
+ $class //= '';
+ return Mojo::DOM->new_tag(
+ 'div',
+ class => 'card-header text-center',
+ sub { Mojo::DOM->new_tag('h4', class => 'card-title fs-6 m-0', $title); }
+ )
+ . Mojo::DOM->new_tag(
+ 'div',
+ class => "card-body text-center $class",
+ sub { defined $line && $line =~ /\S/ ? $line : ' ' }
+ );
+ }
+
+ my $answerPreview = previewAnswer($ansHash);
+
+ my $feedback = (
+ # Add a visually hidden span to provide feedback to screen reader users immediately.
+ ($inputs_ref->{submitAnswers} && $PG->{flags}{showPartialCorrectAnswers})
+ || $rh_envir->{forceShowAttemptFeedback}
+ ? Mojo::DOM->new_tag(
+ 'span',
+ class => 'visually-hidden',
+ id => "ww-feedback-$response",
+ sub {
+ Mojo::DOM->new_tag('span', $result)
+ . ($rh_envir->{showMessages} && $ansHash->{ans_message}
+ ? Mojo::DOM->new_tag('span', $ansHash->{ans_message})
+ : '');
+ }
+ )->to_string
+ : ''
+ )
+ . Mojo::DOM->new_tag(
+ 'button',
+ type => 'button',
+ class => "ww-feedback-btn btn btn-sm $btnClass" . ($isEssay ? '' : ' ms-1'),
+ data => {
+ ($inputs_ref->{submitAnswers} && $PG->{flags}{showPartialCorrectAnswers})
+ || $rh_envir->{forceShowAttemptFeedback} ? (bs_title => $result) : (),
+ bs_toggle => 'popover',
+ bs_trigger => 'click',
+ bs_placement => 'bottom',
+ bs_html => 'true',
+ bs_custom_class => join(' ', 'ww-result-popover', $resultClass || ()),
+ bs_fallback_placements => '[]',
+ bs_content => Mojo::DOM->new_tag(
+ 'div',
+ id => "$response-feedback",
+ sub {
+ Mojo::DOM->new_tag(
+ 'div',
+ class => 'card',
+ sub {
+ ($rh_envir->{showAttemptAnswers} && !$isEssay
+ ? feedbackLine(maketext('Entered'), $ansHash->{student_ans})
+ : '')
+ . (
+ $rh_envir->{showAttemptPreviews}
+ ? feedbackLine(
+ maketext('Answer Preview'),
+ (
+ (defined $answerPreview && $answerPreview =~ /\S/)
+ || $rh_envir->{showAttemptAnswers}
+ ? $answerPreview
+ : $ansHash->{student_ans}
+ )
+ )
+ : ''
+ )
+ . (
+ $rh_envir->{showCorrectAnswers} && !$isEssay
+ ? feedbackLine(maketext('Correct Answer'),
+ previewCorrectAnswer($ansHash))
+ : ''
+ )
+ . (
+ ($rh_envir->{showMessages} && $ansHash->{ans_message})
+ ? feedbackLine(
+ maketext('Message'), $ansHash->{ans_message},
+ 'feedback-message'
+ )
+ : ''
+ );
+ }
+ );
+ }
+ )->to_string,
+ },
+ sub { Mojo::DOM->new_tag('i', class => 'fa-solid fa-caret-down') }
+ )->to_string;
+
+ if ($feedbackContainer) { $feedbackContainer->append_content($feedback); }
+ else { $feedbackElement->append($feedback); }
+ }
+ }
+ });
+ }
+
# Install problem grader.
# WeBWorK::PG::Translator will install its default problem grader if none of the conditions below are true.
if (defined($PG->{flags}{PROBLEM_GRADER_TO_USE})) {
diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl
index bd6368a50f..efd23f6163 100644
--- a/macros/core/PGbasicmacros.pl
+++ b/macros/core/PGbasicmacros.pl
@@ -279,51 +279,50 @@ sub NAMED_ANS_RULE {
my %options = @_;
$col = 20 unless not_null($col);
my $answer_value = '';
- $answer_value = ${$inputs_ref}{$name} if defined(${$inputs_ref}{$name});
+ $answer_value = $inputs_ref->{$name} if defined $inputs_ref->{$name};
+ $answer_value = [ split("\0", $answer_value) ] if $answer_value =~ /\0/;
- #FIXME -- code factoring needed
- if ($answer_value =~ /\0/) {
- my @answers = split("\0", $answer_value);
- $answer_value = shift(@answers); # use up the first answer
- $rh_sticky_answers->{$name} = \@answers;
- # store the rest -- beacuse this stores to a main:: variable
- # it must be evaluated at run time
- $answer_value = '' unless defined($answer_value);
- } elsif (ref($answer_value) eq 'ARRAY') {
- my @answers = @{$answer_value};
- $answer_value = shift(@answers); # use up the first answer
+ if (ref($answer_value) eq 'ARRAY') {
+ my @answers = @$answer_value;
+ $answer_value = shift(@answers); # Use up the first answer.
$rh_sticky_answers->{$name} = \@answers;
- # store the rest -- because this stores to a main:: variable
- # it must be evaluated at run time
+ # Store the rest. Since this stores to a main:: variable it must be evaluated at run time.
$answer_value = '' unless defined($answer_value);
}
- $answer_value =~ s/\s+/ /g; ## remove excessive whitespace from student answer
- $name = RECORD_ANS_NAME($name, $answer_value);
- $answer_value = encode_pg_and_html($answer_value);
+ $answer_value =~ s/\s+/ /g; ## remove excessive whitespace from student answer
+ $name = RECORD_ANS_NAME($name, $answer_value);
my $previous_name = "previous_$name";
$name = ($envir{use_opaque_prefix}) ? "%%IDPREFIX%%$name" : $name;
$previous_name = ($envir{use_opaque_prefix}) ? "%%IDPREFIX%%$previous_name" : $previous_name;
- my $label;
- if (defined($options{aria_label})) {
- $label = $options{aria_label};
- } else {
- $label = generate_aria_label($name);
- }
-
- my $tcol = $col / 2 > 3 ? $col / 2 : 3; ## get max
- $tcol = $tcol < 40 ? $tcol : 40; ## get min
+ my $tcol = $col / 2 > 3 ? $col / 2 : 3; ## get max
+ $tcol = $tcol < 40 ? $tcol : 40; ## get min
MODES(
TeX => "{\\answerRule[$name]{$tcol}}",
Latex2HTML => qq!\\begin{rawhtml}\\end{rawhtml}!,
-
# Note: codeshard is used in the css to identify input elements that come from pg
- HTML => qq!!
- . qq!!,
+ HTML => tag(
+ 'span',
+ class => 'text-nowrap',
+ data_feedback_container_for => $name,
+ tag(
+ 'input',
+ type => 'text',
+ class => 'codeshard',
+ size => $col,
+ name => $name,
+ id => $name,
+ aria_label => $options{aria_label} // generate_aria_label($name),
+ dir => 'auto',
+ autocomplete => 'off',
+ autocapitalize => 'off',
+ spellcheck => 'false',
+ value => $answer_value
+ )
+ )
+ . tag('input', type => 'hidden', name => $previous_name, value => $answer_value),
PTX => qq!
This is an essay answer text box. You can type your answer in here and, after you hit submit, + TeX => '', + HTML => qq! +
This is an essay answer text box. You can type your answer in here and, after you hit submit, it will be saved so that your instructor can grade it at a later date. If your instructor makes any comments on your answer those comments will appear on this page after the question has been graded. You can use LaTeX to make your math equations look pretty. LaTeX expressions should be enclosed using the parenthesis notation and not dollar signs. -
+ !, PTX => '', ); diff --git a/macros/core/scaffold.pl b/macros/core/scaffold.pl index 6953a9e186..c38048e0e7 100644 --- a/macros/core/scaffold.pl +++ b/macros/core/scaffold.pl @@ -571,16 +571,14 @@ sub add_container { # Nothing needs to be done for the PTX display mode. return if $Scaffold::isPTX; - # Essay answers never return as correct, so there is a special for case them. Also provide a "scaffold_force" - # option in the AnswerHash that can be used to force Scaffold to consider the score to be 1 (a bug in - # PGessaymacros.pl prevents it from working in essay_cmp() though -- it actually does work, both answer hashes - # defined in essay_cmp() need the setting though). + # Provide a "scaffold_force" option in the AnswerHash that can be used to force + # Scaffold to consider the score to be 1. This is used by PGessaymacros.pl. for (@{ $self->{ans_names} }) { next unless defined $PG_ANSWERS_HASH->{$_}; - $scaffold->{scores}{$_} = $PG_ANSWERS_HASH->{$_}{ans_eval}{rh_ans}{score}; - $scaffold->{scores}{$_} = 1 - if ($PG_ANSWERS_HASH->{$_}{ans_eval}{rh_ans}{type} || '') eq 'essay' - || $PG_ANSWERS_HASH->{$_}{ans_eval}{rh_ans}{scaffold_force}; + $scaffold->{scores}{$_} = + $PG_ANSWERS_HASH->{$_}{ans_eval}{rh_ans}{scaffold_force} + ? 1 + : $PG_ANSWERS_HASH->{$_}{ans_eval}{rh_ans}{score}; } # Set the active scaffold to the scaffold for this section so that is_correct, can_open, diff --git a/macros/graph/parserGraphTool.pl b/macros/graph/parserGraphTool.pl index aacf8d49a1..3b5fa96f17 100644 --- a/macros/graph/parserGraphTool.pl +++ b/macros/graph/parserGraphTool.pl @@ -1128,14 +1128,19 @@ sub ans_rule { return ''; } else { $self->constructJSXGraphOptions; - return main::tag('input', type => 'hidden', name => $ans_name, id => $ans_name, value => $answer_value) - . main::tag( - 'input', - type => 'hidden', - name => "previous_$ans_name", - id => "previous_$ans_name", - value => $answer_value - ) . <