From 9fea99ef3f68095cb4fdced99b772c1f6b10766d Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 3 Aug 2023 12:55:33 -0500 Subject: [PATCH 01/12] Add a text post processing phase to the translator. Macros or problems can add a hook by calling `add_content_post_processor` with a subroutine that will be called after the translator has processed answers. For all display modes except TeX, the subroutine will be passed the problem text parsed into a Mojo::DOM object and a reference to the header text. For the TeX display mode a reference to the problem text is passed. The subroutine can then modify the problem DOM or text, and header text. The resulting Mojo::DOM object will be converted back to a string and the problem text reference returned by the translator set to point to that string. Note that the `$PG_PROBLEM_TEXT_ARRAY_REF` has been removed from the translator code. It is never used and is redundant with the `$PG_PROBLEM_TEXT_REF` which is all that is used. It was becoming annoying to maintain both in a compatible manner. The hidden MathQuill inputs for the latex string is added in this post processing stage (see the ENDDOCUMENT method in PG.pl). --- .github/workflows/unit-tests.yml | 1 + cpanfile | 1 + docker/pg.Dockerfile | 1 + htdocs/js/InputColor/color.js | 2 +- lib/PGcore.pm | 7 + lib/WeBWorK/PG.pm | 6 +- lib/WeBWorK/PG/ImageGenerator.pm | 2 +- lib/WeBWorK/PG/Translator.pm | 266 ++++++++++++++++++------------- macros/PG.pl | 194 ++++++++++------------ t/pg_problems/problem_file.t | 10 +- 10 files changed, 255 insertions(+), 235 deletions(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index e785aafdac..50650a7571 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -29,6 +29,7 @@ jobs: libjson-perl \ libjson-xs-perl \ liblocale-maketext-lexicon-perl \ + libmojolicious-perl \ libtest2-suite-perl \ libtie-ixhash-perl \ libuuid-tiny-perl \ diff --git a/cpanfile b/cpanfile index 46f288e3fd..ca6cbe1f03 100644 --- a/cpanfile +++ b/cpanfile @@ -18,6 +18,7 @@ on runtime => sub { requires 'JSON::XS'; requires 'Locale::Maketext'; requires 'Locale::Maketext::Lexicon'; + requires 'Mojolicious'; requires 'Tie::IxHash'; requires 'Types::Serialiser'; requires 'UUID::Tiny'; diff --git a/docker/pg.Dockerfile b/docker/pg.Dockerfile index ffcf22c772..36741fc5ff 100644 --- a/docker/pg.Dockerfile +++ b/docker/pg.Dockerfile @@ -21,6 +21,7 @@ RUN apt-get update \ libjson-perl \ libjson-xs-perl \ liblocale-maketext-lexicon-perl \ + libmojolicious-perl \ libtest2-suite-perl \ libtie-ixhash-perl \ libuuid-tiny-perl \ diff --git a/htdocs/js/InputColor/color.js b/htdocs/js/InputColor/color.js index 025301ac18..25fa134837 100644 --- a/htdocs/js/InputColor/color.js +++ b/htdocs/js/InputColor/color.js @@ -42,7 +42,7 @@ answerInput.focus(); }); } else { - answerLink.href = ''; + answerLink.removeAttribute('href'); } }; diff --git a/lib/PGcore.pm b/lib/PGcore.pm index 130639db9a..d5fe2c5195 100755 --- a/lib/PGcore.pm +++ b/lib/PGcore.pm @@ -97,6 +97,7 @@ sub new { PG_alias => undef, PG_problem_grader => undef, displayMode => undef, + content_post_processors => [], envir => $envir, WARNING_messages => [], DEBUG_messages => [], @@ -561,6 +562,12 @@ sub get_persistent_data { return $self->{PERSISTENCE_HASH}{$label}; } +sub add_content_post_processor { + my ($self, $handler) = @_; + push(@{ $self->{content_post_processors} }, $handler) if ref($handler) eq 'CODE'; + return; +} + sub check_answer_hash { my $self = shift; foreach my $key (keys %{ $self->{PG_ANSWERS_HASH} }) { diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index bf9561914c..d3e5dc654c 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -140,7 +140,7 @@ sub new_helper ($invocant, %options) { } } - $translator->translate(); + $translator->translate; # IMPORTANT: The translator environment should not be trusted after the problem code runs. @@ -174,6 +174,8 @@ sub new_helper ($invocant, %options) { } # HTML_dpng uses an ImageGenerator. We have to render the queued equations. + # 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( @@ -182,6 +184,8 @@ sub new_helper ($invocant, %options) { ); } + $translator->post_process_content if ref($translator->{rh_pgcore}) eq 'PGcore'; + return bless { translator => $translator, head_text => ${ $translator->r_header }, diff --git a/lib/WeBWorK/PG/ImageGenerator.pm b/lib/WeBWorK/PG/ImageGenerator.pm index 1bb134e62c..7354cdf030 100644 --- a/lib/WeBWorK/PG/ImageGenerator.pm +++ b/lib/WeBWorK/PG/ImageGenerator.pm @@ -477,7 +477,7 @@ sub fix_markers ($self) { my %depths = %{ $self->{depths} }; for my $depthkey (keys %depths) { if ($depths{$depthkey} eq 'none') { - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:"$self->{dvipng_align}"/g; + ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:$self->{dvipng_align}"/g; } else { my $ndepth = 0 - $depths{$depthkey}; ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:${ndepth}px"/g; diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index e56eaf4210..21c8249811 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -15,50 +15,40 @@ package WeBWorK::PG::Translator; -use strict; -use warnings; - -use utf8; -use v5.12; -binmode(STDOUT, ":encoding(UTF-8)"); - -use Opcode; -use Carp; - -use WWSafe; -use PGUtil qw(pretty_print); -use WeBWorK::PG::IO qw(fileFromPath); - =head1 NAME WeBWorK::PG::Translator - Evaluate PG code and evaluate answers safely =head1 SYNPOSIS - my $pt = new WeBWorK::PG::Translator; # create a translator + my $pt = WeBWorK::PG::Translator->new; # create a translator $pt->environment(\%envir); # provide the environment variable for the problem - $pt->initialize(); # initialize the translator - $pt-> set_mask(); # set the operation mask for the translator safe compartment + $pt->initialize; # initialize the translator + $pt->set_mask; # set the operation mask for the translator safe compartment + $pt->source_string($source); # provide the source string for the problem + # or + $pt->source_file($sourceFilePath); # provide the proble file containing the source # Load the unprotected macro files. # These files are evaluated with the Safe compartment wide open. # Other macros are loaded from within the problem using loadMacros. - $pt->unrestricted_load("${courseScriptsDirectory}PG.pl"); + # This should not be done if the safe cache is used which is only the case if $ENV{MOJO_MODE} exists. + $pt->unrestricted_load("${pgMacrosDirectory}PG.pl"); - $pt->translate(); # translate the problem (the following pieces of information are created) + $pt->translate; # translate the problem (the following pieces of information are created) - $PG_PROBLEM_TEXT_ARRAY_REF = $pt->ra_text(); # output text for the body of the HTML file (in array form) - $PG_PROBLEM_TEXT_REF = $pt->r_text(); # output text for the body of the HTML file - $PG_HEADER_TEXT_REF = $pt->r_header; # text for the header of the HTML file - $PG_POST_HEADER_TEXT_REF = $pt->r_post_header + $PG_PROBLEM_TEXT_REF = $pt->r_text; # reference to output text for the body of problem + $PG_HEADER_TEXT_REF = $pt->r_header; # reference to text for the header in HTML output + $PG_POST_HEADER_TEXT_REF = $pt->r_post_header; $PG_ANSWER_HASH_REF = $pt->rh_correct_answers; # a hash of answer evaluators $PG_FLAGS_REF = $pt->rh_flags; # misc. status flags. - $pt->process_answers; # evaluates all of the answers + $pt->process_answers; # evaluates all of the answers + my $rh_answer_results = $pt->rh_evaluated_answers; # provides a hash of the results of evaluating the answers. + my $rh_problem_result = $pt->grade_problem(%options); # grades the problem. - my $rh_answer_results = $pt->rh_evaluated_answers; # provides a hash of the results of evaluating the answers. - my $rh_problem_result = $pt->grade_problem; # grades the problem using the default problem grading method. + $pt->post_process_content; # Execute macro or problem hooks that further modify the problem content. =head1 DESCRIPTION @@ -66,6 +56,21 @@ This module defines an object which will translate a problem written in the Prob =cut +use strict; +use warnings; + +use utf8; +use v5.12; +binmode(STDOUT, ":encoding(UTF-8)"); + +use Opcode; +use Carp; +use Mojo::DOM; + +use WWSafe; +use PGUtil qw(pretty_print); +use WeBWorK::PG::IO qw(fileFromPath); + =head2 be_strict This creates a substitute for C which cannot be used in PG problem @@ -146,15 +151,18 @@ BEGIN { } # Also define in Main:: for PG modules. - sub Main::be_strict { return &be_strict; } + sub Main::be_strict { return be_strict(); } } =head2 evaluate_modules - Usage: $obj->evaluate_modules('WWPlot', 'Fun', 'Circle'); +Adds modules to the list of modules which can be used by the PG problems. + +For example, + + $obj->evaluate_modules('LaTeXImage', 'DragNDrop'); -Adds the modules WWPlot.pm, Fun.pm and Circle.pm in the courseScripts directory to the list of modules -which can be used by the PG problems. +adds modules to the C and C modules. =cut @@ -179,13 +187,13 @@ sub evaluate_modules { =head2 load_extra_packages - Usage: $obj->load_extra_packages('AlgParserWithImplicitExpand', - 'Expr','ExprWithImplicitExpand'); +Loads extra packages for modules that contain more than one package. Works in +conjunction with evaluate_modules. It is assumed that the file containing the +extra packages (along with the base package name which is the same as the name +of the file minus the .pm extension) has already been loaded using +evaluate_modules. -Loads extra packages for modules that contain more than one package. Works in conjunction with -evaluate_modules. It is assumed that the file containing the extra packages (along with the base -package name which is the same as the name of the file minus the .pm extension) has already been -loaded using evaluate_modules + Usage: $obj->load_extra_packages('AlgParserWithImplicitExpand', 'ExprWithImplicitExpand'); =cut @@ -209,7 +217,7 @@ sub load_extra_packages { =head2 new - Creates the translator object. +Creates the translator object. =cut @@ -220,25 +228,24 @@ sub new { my $safe_cmpt = exists($ENV{MOJO_MODE}) ? $WeBWorK::Translator::safeCache : WWSafe->new; my $self = { - preprocess_code => \&default_preprocess_code, - postprocess_code => \&default_postprocess_code, - envir => undef, - PG_PROBLEM_TEXT_ARRAY_REF => [], - PG_PROBLEM_TEXT_REF => 0, - PG_HEADER_TEXT_REF => 0, - PG_POST_HEADER_TEXT_REF => 0, - PG_ANSWER_HASH_REF => {}, - PG_FLAGS_REF => {}, - rh_pgcore => undef, - safe => $safe_cmpt, - safe_compartment_name => $safe_cmpt->root, - errors => '', - source => '', - rh_correct_answers => {}, - rh_student_answers => {}, - rh_evaluated_answers => {}, - rh_problem_result => {}, - rh_problem_state => { + preprocess_code => \&default_preprocess_code, + postprocess_code => \&default_postprocess_code, + envir => undef, + PG_PROBLEM_TEXT_REF => 0, + PG_HEADER_TEXT_REF => 0, + PG_POST_HEADER_TEXT_REF => 0, + PG_ANSWER_HASH_REF => {}, + PG_FLAGS_REF => {}, + rh_pgcore => undef, + safe => $safe_cmpt, + safe_compartment_name => $safe_cmpt->root, + errors => '', + source => '', + rh_correct_answers => {}, + rh_student_answers => {}, + rh_evaluated_answers => {}, + rh_problem_result => {}, + rh_problem_state => { recorded_score => 0, num_of_correct_ans => 0, num_of_incorrect_ans => 0, @@ -249,38 +256,19 @@ sub new { return bless $self, $class; } -=pod +=head2 initialize -(b) The following routines defined within the PG module are shared: +The following translator methods are shared to the safe compartment: - &be_strict - &read_whole_problem_file - &surePathToTmpFile - &fileFromPath - &directoryFromPath - &PG_answer_eval - &PG_restricted_eval - &send_mail_to - -In addition the environment hash C<%envir> is shared. This variable is unpacked -when PG.pl is run and provides most of the environment variables for each problem -template. - -=for html - environment variables - -(c) Sharing macros: + &PG_answer_eval + &PG_restricted_eval + &PG_macro_file_eval + &be_strict -The macros shared with the safe compartment are +Also all methods that are exported by WeBWorK::PG::IO are shared. - '&read_whole_problem_file' - '&surePathToTmpFile' - '&fileFromPath' - '&directoryFromPath' - '&PG_answer_eval' - '&PG_restricted_eval' - '&be_strict' - '&send_mail_to' +In addition the environment hash C<%envir> is shared. This variable is unpacked +when PG.pl is run. =cut @@ -443,11 +431,6 @@ sub nameSpace { return $self->{safe}->root; } -sub a_text { - my $self = shift; - return @{ $self->{PG_PROBLEM_TEXT_ARRAY_REF} }; -} - sub header { my $self = shift; return ${ $self->{PG_HEADER_TEXT_REF} }; @@ -473,11 +456,6 @@ sub h_answers { return %{ $self->{PG_ANSWER_HASH_REF} }; } -sub ra_text { - my $self = shift; - return $self->{PG_PROBLEM_TEXT_ARRAY_REF}; -} - sub r_text { my $self = shift; return $self->{PG_PROBLEM_TEXT_REF}; @@ -524,10 +502,10 @@ sub errors { =head2 set_mask -(e) Now we close the safe compartment. Only the certain operations can be used -within PG problems and the PG macro files. These include the subroutines -shared with the safe compartment as defined above and most Perl commands which -do not involve file access, access to the system or evaluation. +Limit allowed operations in the safe compartment. Only the certain operations +can be used within PG problems and the PG macro files. These include the +subroutines shared with the safe compartment as defined above and most Perl +commands which do not involve file access, access to the system or evaluation. Specifically the following are allowed: @@ -644,7 +622,7 @@ sub PG_errorMessage { =head2 Translate -(3) B +B The input text is subjected to some global replacements. @@ -701,26 +679,23 @@ Note that there are several other replacements that are now done that are not documented here. See the C method for all replacements that are done. -(4) B +B Evaluate the text within the safe compartment. Save the errors. The safe compartment is a new one unless the $safeCompartment was set to zero in which case the previously defined safe compartment is used. (See item 1.) -(5) B +B The error provided by Perl is truncated slightly and returned. In the text string which would normally contain the rendered problem. The original text string is given line numbers and concatenated to the errors. -(6) B +B Sets the following hash keys of the translator object: - PG_PROBLEM_TEXT_ARRAY_REF: Reference to an array of strings containing the - rendered text. - PG_PROBLEM_TEXT_REF: Reference to a string resulting from joining the above - array with the empty string. + PG_PROBLEM_TEXT_REF: Reference to a string containing the rendered text. PG_HEADER_TEXT_REF: Reference to a string containing material to be placed in the header. PG_POST_HEADER_TEXT_REF: Reference to a string containing material to @@ -737,10 +712,9 @@ Sets the following hash keys of the translator object: my %XML = ('&' => '&', '<' => '<', '>' => '>', '"' => '"', '\'' => '''); sub translate { - my $self = shift; - my @PROBLEM_TEXT_OUTPUT = (); - my $safe_cmpt = $self->{safe}; - my $evalString = $self->{source}; + my $self = shift; + my $safe_cmpt = $self->{safe}; + my $evalString = $self->{source}; $self->{errors} .= qq{ERROR: This problem file was empty!\n} unless ($evalString); $self->{errors} .= qq{ERROR: You must define the environment before translating.} unless defined($self->{envir}); @@ -783,6 +757,7 @@ sub translate { # WARNING and DEBUG tracks are being handled elsewhere (in Problem.pm?) $self->{errors} .= "ERRORS from evaluating PG file:\n$@\n" if $@; + my @PROBLEM_TEXT_OUTPUT; push(@PROBLEM_TEXT_OUTPUT, split(/^/, $$PG_PROBLEM_TEXT_REF)) if ref($PG_PROBLEM_TEXT_REF) eq 'SCALAR'; # This is better than using defined($$PG_PROBLEM_TEXT_REF) # Because more pleasant feedback is given when the problem doesn't render. @@ -836,11 +811,10 @@ sub translate { } } - $PG_FLAGS_REF->{'error_flag'} = 1 if $self->{errors}; + $PG_FLAGS_REF->{error_flag} = 1 if $self->{errors}; my $PG_PROBLEM_TEXT = join("", @PROBLEM_TEXT_OUTPUT); - $self->{PG_PROBLEM_TEXT_REF} = \$PG_PROBLEM_TEXT; - $self->{PG_PROBLEM_TEXT_ARRAY_REF} = \@PROBLEM_TEXT_OUTPUT; + $self->{PG_PROBLEM_TEXT_REF} = \$PG_PROBLEM_TEXT; # Make sure that these variables are defined. If the eval failed with # errors, one or more of these variables won't be defined. @@ -858,7 +832,7 @@ sub translate { =cut -=head3 access methods +=head3 access methods $obj->rh_student_answers @@ -1177,6 +1151,68 @@ sub avg_problem_grader { return (\%problem_result, \%problem_state); } +=head2 post_process_content + +Call hooks added via macros or the problem via C to +post process content. Hooks are called in the order they were added. + +This method should be called in the rendering process after answer processing +has occurred. + +If the display mode is TeX, then each hook subroutine is passed a reference to +the problem text string generated in the C method. + +For all other display modes each hook subroutine is passed two Mojo::DOM +objects. The first containing the parsed problem text string, and the second +contains the parsed header text string, both of which were generated in the +C method. After all hooks are called and modifications are made to +the Mojo::DOM contents by the hooks, the Mojo::DOM objects are converted back to +strings and the translator problem text and header references are updated with +the contents of those strings. + +=cut + +sub post_process_content { + my $self = shift; + + my $outer_sig_warn = $SIG{__WARN__}; + my @warnings; + local $SIG{__WARN__} = sub { push(@warnings, $_[0]) }; + + my $outer_sig_die = $SIG{__DIE__}; + local $SIG{__DIE__} = sub { + ref $outer_sig_die eq "CODE" + ? $outer_sig_die->(PG_errorMessage('traceback', $_[0])) + : die PG_errorMessage('traceback', $_[0]); + }; + + if ($self->{rh_pgcore}{displayMode} eq 'TeX') { + our $PG_PROBLEM_TEXT_REF = $self->{PG_PROBLEM_TEXT_REF}; + $self->{safe}->share('$PG_PROBLEM_TEXT_REF'); + $self->{safe}->reval('for (@{ $main::PG->{content_post_processors} }) { $_->($PG_PROBLEM_TEXT_REF); }', 1); + warn "ERRORS from post processing PG text:\n$@\n" if $@; + } else { + $self->{safe}->share_from('main', [qw(%Mojo::Base:: %Mojo::Collection:: %Mojo::DOM::)]); + our $problemDOM = Mojo::DOM->new(${ $self->{PG_PROBLEM_TEXT_REF} }); + $problemDOM->xml(1) if $self->{rh_pgcore}{displayMode} eq 'PTX'; + our $pageHeader = Mojo::DOM->new(${ $self->{PG_HEADER_TEXT_REF} }); + $self->{safe}->share('$problemDOM', '$pageHeader'); + $self->{safe}->reval('for (@{ $main::PG->{content_post_processors} }) { $_->($problemDOM, $pageHeader); }', 1); + warn "ERRORS from post processing PG text:\n$@\n" if $@; + + $self->{PG_PROBLEM_TEXT_REF} = \($problemDOM->to_string); + $self->{PG_HEADER_TEXT_REF} = \($pageHeader->to_string); + } + + if (@warnings) { + ref $outer_sig_warn eq "CODE" + ? $outer_sig_warn->(PG_errorMessage('message', @warnings)) + : warn PG_errorMessage('message', @warnings); + } + + return; +} + =head2 PG_restricted_eval PG_restricted_eval($string) @@ -1205,7 +1241,7 @@ sub PG_restricted_eval { my $out = PG_restricted_eval_helper($string); my $err = $@; - my $err_report = $err if $err =~ /\S/; + my $err_report = $err =~ /\S/ ? $err : undef; return wantarray ? ($out, $err, $err_report) : $out; } diff --git a/macros/PG.pl b/macros/PG.pl index 350292c8bd..bf797189ad 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -581,6 +581,12 @@ sub get_persistent_data { return $PG->get_persistent_data($label); } +sub add_content_post_processor { + my $handler = shift; + $PG->add_content_post_processor($handler); + return; +} + =head2 RECORD_FORM_LABEL Stores the label of a form field in the "extra" answers list. This is used to @@ -829,7 +835,7 @@ =head2 ENDDOCUMENT sub ENDDOCUMENT { # Insert MathQuill responses if MathQuill is enabled. Add responses to each answer's response group that store the # latex form of the students' answers and add corresponding hidden input boxes to the page. - if ($envir{useMathQuill}) { + if ($envir{useMathQuill} && $main::displayMode =~ /HTML/i) { for my $answerLabel (keys %{ $PG->{PG_ANSWERS_HASH} }) { my $answerGroup = $PG->{PG_ANSWERS_HASH}{$answerLabel}; my $mq_opts = $answerGroup->{ans_eval}{rh_ans}{mathQuillOpts} // {}; @@ -874,151 +880,115 @@ sub ENDDOCUMENT { $mq_part_opts->{rootsAreExponents} = 0 if $context && $context->functions->get('root') && !defined $mq_part_opts->{rootsAreExponents}; - my $name = "MaThQuIlL_$response"; - my $answer_value = ''; - $answer_value = $inputs_ref->{$name} if defined($inputs_ref->{$name}); + my $name = "MaThQuIlL_$response"; RECORD_EXTRA_ANSWERS($name); - $answer_value = encode_pg_and_html($answer_value); - my $data_mq_opts = - scalar(keys %$mq_part_opts) - ? qq!data-mq-opts="@{[encode_pg_and_html(JSON->new->encode($mq_part_opts))]}"! - : ""; - TEXT(MODES( - TeX => "", - PTX => "", - HTML => qq!! - )); + + add_content_post_processor(sub { + my $problemContents = shift; + my $input = $problemContents->at(qq{input[name="$response"]}) + || $problemContents->at(qq{textarea[name="$response"]}); + return unless $input; + $input->append( + Mojo::DOM->new_tag( + 'input', + type => 'hidden', + name => $name, + id => $name, + value => $inputs_ref->{$name} // '', + scalar(keys %$mq_part_opts) + ? (data => { mq_opts => JSON->new->encode($mq_part_opts) }) + : '' + )->to_string + ); + }); } } } - # check that answers match - # gather up PG_FLAGS elements - - $PG->{flags}->{showPartialCorrectAnswers} = defined($showPartialCorrectAnswers) ? $showPartialCorrectAnswers : 1; - $PG->{flags}->{recordSubmittedAnswers} = defined($recordSubmittedAnswers) ? $recordSubmittedAnswers : 1; - $PG->{flags}->{refreshCachedImages} = defined($refreshCachedImages) ? $refreshCachedImages : 0; - $PG->{flags}->{hintExists} = defined($hintExists) ? $hintExists : 0; - $PG->{flags}->{solutionExists} = defined($solutionExists) ? $solutionExists : 0; - $PG->{flags}->{comment} = defined($pgComment) ? $pgComment : ''; - - # install problem grader - if (defined($PG->{flags}->{PROBLEM_GRADER_TO_USE})) { - # problem grader defined within problem -- no further action needed + # Gather flags + $PG->{flags}{showPartialCorrectAnswers} = $showPartialCorrectAnswers // 1; + $PG->{flags}{recordSubmittedAnswers} = $recordSubmittedAnswers // 1; + $PG->{flags}{refreshCachedImages} = $refreshCachedImages // 0; + $PG->{flags}{hintExists} = $hintExists // 0; + $PG->{flags}{solutionExists} = $solutionExists // 0; + $PG->{flags}{comment} = $pgComment // ''; + + # 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})) { + # Problem grader defined within problem. No further action needed. } elsif (defined($rh_envir->{PROBLEM_GRADER_TO_USE})) { - if (ref($rh_envir->{PROBLEM_GRADER_TO_USE}) eq 'CODE') { # user defined grader - $PG->{flags}->{PROBLEM_GRADER_TO_USE} = $rh_envir->{PROBLEM_GRADER_TO_USE}; + if (ref($rh_envir->{PROBLEM_GRADER_TO_USE}) eq 'CODE') { + # User defined grader. + $PG->{flags}{PROBLEM_GRADER_TO_USE} = $rh_envir->{PROBLEM_GRADER_TO_USE}; } elsif ($rh_envir->{PROBLEM_GRADER_TO_USE} eq 'std_problem_grader') { - if (defined(&std_problem_grader)) { - $PG->{flags}->{PROBLEM_GRADER_TO_USE} = \&std_problem_grader; # defined in PGanswermacros.pl - } # std_problem_grader is the default in any case so don't give a warning. + $PG->{flags}{PROBLEM_GRADER_TO_USE} = \&std_problem_grader if (defined(&std_problem_grader)); } elsif ($rh_envir->{PROBLEM_GRADER_TO_USE} eq 'avg_problem_grader') { - if (defined(&avg_problem_grader)) { - $PG->{flags}->{PROBLEM_GRADER_TO_USE} = \&avg_problem_grader; # defined in PGanswermacros.pl - } + $PG->{flags}{PROBLEM_GRADER_TO_USE} = \&avg_problem_grader if (defined(&avg_problem_grader)); } else { - warn "Error: " . $PG->{flags}->{PROBLEM_GRADER_TO_USE} . "is not a known program grader."; + warn "Error: $PG->{flags}{PROBLEM_GRADER_TO_USE} is not a known problem grader."; } } elsif (defined(&std_problem_grader)) { - $PG->{flags}->{PROBLEM_GRADER_TO_USE} = \&std_problem_grader; # defined in PGanswermacros.pl - } else { - # PGtranslator will install its default problem grader + $PG->{flags}{PROBLEM_GRADER_TO_USE} = \&std_problem_grader; } - # add javaScripts - if ($rh_envir->{displayMode} eq 'HTML_jsMath') { - TEXT(''); - } elsif ($rh_envir->{displayMode} eq 'HTML_asciimath') { - TEXT(''); - my $STRING = join("", @{ $PG->{HEADER_ARRAY} }); - unless ($STRING =~ m/mathplayer/) { - HEADER_TEXT('' . "\n" - . ''); - } - - } TEXT(MODES(%{ $rh_envir->{problemPostamble} })); - @PG_ANSWERS = (); if ($inputs_ref->{showResourceInfo} && $rh_envir->{show_resource_info}) { - my %resources = %{ $PG->{PG_alias}->{resource_list} }; - my $str = ''; - my @resource_names = (); - foreach my $key (keys %resources) { - $str .= knowlLink("$key$BR", value => "$key$BR" . pretty_print($resources{$key}) . "$BR$BR", base64 => 0); - push @resource_names, $key; - } - if ($str eq '') { - $str = "No auxiliary resources
"; + if (keys %{ $PG->{PG_alias}{resource_list} }) { + $PG->debug_message( + '

Resources

    ' . join( + '', + map { + '
  • ' . knowlLink($_, value => pretty_print($PG->{PG_alias}{resource_list}{$_})) . '
  • ' + } + sort keys %{ $PG->{PG_alias}{resource_list} } + ) + . '
' + ); } else { - my $summary = "## RESOURCES('" . join("','", @resource_names) . "')$BR\n"; - $PG->debug_message($summary . $str); + $PG->debug_message('No auxiliary resources.'); } } + if ($inputs_ref->{showPGInfo} && $rh_envir->{show_pg_info}) { my $context = $$Value::context->{flags}; $PG->debug_message( - $HR, "Form variables", $BR, pretty_print($inputs_ref), $HR, "Environment variables", - $BR, pretty_print(\%envir), $HR, "Context flags", $BR, pretty_print($context), + "$HR

Form variables

" . pretty_print($inputs_ref) . '
', + "$HR

Environment variables

" . pretty_print(\%envir) . '
', + "$HR

Context flags

" . pretty_print($context) . '
' ); } - #warn keys %{ $PG->{PG_ANSWERS_HASH} }; - @PG_ANSWER_ENTRY_ORDER = (); - my $ans_debug = 0; - foreach my $key (keys %{ $PG->{PG_ANSWERS_HASH} }) { - $answergroup = $PG->{PG_ANSWERS_HASH}->{$key}; - #warn "$key is defined =", defined($answergroup), "PG object is $PG"; - ################# + my (%PG_ANSWERS_HASH, @PG_ANSWER_ENTRY_ORDER); + for my $key (keys %{ $PG->{PG_ANSWERS_HASH} }) { + my $answergroup = $PG->{PG_ANSWERS_HASH}{$key}; + # EXTRA ANSWERS KLUDGE - ################# - # The first response in each answer group is placed in @PG_ANSER_ENTRY_ORDER and %PG_ANSWERS_HASH - # The remainder of the response keys are placed in the EXTRA ANSWERS ARRAY - if (defined($answergroup)) { - my @response_keys = $answergroup->{response}->response_labels; + # The first response label in each answer group is placed in the @PG_ANSWER_ENTRY_ORDER array, and the first + # response evaluator is placed in %PG_ANSWERS_HASH identified by its label. The remainder of the response + # labels are placed in the @KEPT_EXTRA_ANSWERS array. + if (defined $answergroup) { if ($inputs_ref->{showAnsGroupInfo} && $rh_envir->{show_answer_group_info}) { $PG->debug_message(pretty_print($answergroup)); $PG->debug_message(pretty_print($answergroup->{response})); } - my $response_key = $response_keys[0]; - my $answer_key = $answergroup->{ans_label}; - #unshift @response_keys, $response_key unless ($response_key eq $answer_group->{ans_label}); - # don't save the first response key if it is the same as the ans_label - # maybe we should insure that the first response key is always the same as the answer label? - # warn "first response key label and answer key label don't agree" - # unless ($response_key eq $answer_key); - - # even if no answer blank is printed for it? or a hidden answer blank? - # this is still a KLUDGE - # for compatibility the first response key is closer to the old method than the $ans_label - # this is because a response key might indicate an array but an answer label won't - #push @PG_ANSWERS, $response_key,$answergroup->{ans_eval}; - $PG_ANSWERS_HASH{$answer_key} = $answergroup->{ans_eval}; - push @PG_ANSWER_ENTRY_ORDER, $answer_key; - # @KEPT_EXTRA_ANSWERS could be replaced by saving all of the responses for this answergroup - push @KEPT_EXTRA_ANSWERS, @response_keys; + + $PG_ANSWERS_HASH{ $answergroup->{ans_label} } = $answergroup->{ans_eval}; + push @PG_ANSWER_ENTRY_ORDER, $answergroup->{ans_label}; + + push @KEPT_EXTRA_ANSWERS, $answergroup->{response}->response_labels; } else { - warn "$key is ", join("|", %{ $PG->{PG_ANSWERS_HASH}->{$key} }); + warn "$key does not have a valid answer group."; } } - $PG->{flags}->{KEPT_EXTRA_ANSWERS} = \@KEPT_EXTRA_ANSWERS; - $PG->{flags}->{ANSWER_ENTRY_ORDER} = \@PG_ANSWER_ENTRY_ORDER; - - # these should not be needed any longer since PG_alias warning queue is attached to PGcore's - # $PG->warning_message( @{ $PG->{PG_alias}->{flags}->{WARNING_messages}} ); - # $PG->debug_message( @{ $PG->{PG_alias}->{flags}->{DEBUG_messages}} ); - - warn "KEPT_EXTRA_ANSWERS", join(" ", @KEPT_EXTRA_ANSWERS), $BR if $ans_debug == 1; - warn "PG_ANSWER_ENTRY_ORDER", join(" ", @PG_ANSWER_ENTRY_ORDER), $BR if $ans_debug == 1; - # not needed for the moment: - # warn "DEBUG messages", join( "$BR",@{$PG->get_debug_messages} ) if $ans_debug==1; - warn "INTERNAL_DEBUG messages", join("$BR", @{ $PG->get_internal_debug_messages }) if $ans_debug == 1; - $STRINGforOUTPUT = join("", @{ $PG->{OUTPUT_ARRAY} }); - $STRINGforHEADER_TEXT = join("", @{ $PG->{HEADER_ARRAY} }); - $STRINGforPOSTHEADER_TEXT = join("", @{ $PG->{POST_HEADER_ARRAY} }); - # warn pretty_print($PG->{PG_ANSWERS_HASH}); - #warn "printing another warning"; + $PG->{flags}{KEPT_EXTRA_ANSWERS} = \@KEPT_EXTRA_ANSWERS; + $PG->{flags}{ANSWER_ENTRY_ORDER} = \@PG_ANSWER_ENTRY_ORDER; + + my $STRINGforOUTPUT = join('', @{ $PG->{OUTPUT_ARRAY} }); + my $STRINGforHEADER_TEXT = join('', @{ $PG->{HEADER_ARRAY} }); + my $STRINGforPOSTHEADER_TEXT = join('', @{ $PG->{POST_HEADER_ARRAY} }); (\$STRINGforOUTPUT, \$STRINGforHEADER_TEXT, \$STRINGforPOSTHEADER_TEXT, \%PG_ANSWERS_HASH, $PG->{flags}, $PG); } diff --git a/t/pg_problems/problem_file.t b/t/pg_problems/problem_file.t index 03ab3842a0..c823c31591 100644 --- a/t/pg_problems/problem_file.t +++ b/t/pg_problems/problem_file.t @@ -23,11 +23,11 @@ is( qq{
\n} . qq{Enter a value for .\n} . qq{
\n} - . qq{} - . qq{\n} - . qq{
\n} - . qq{}, + . qq{} + . qq{} + . qq{\n} + . qq{\n}, 'body_text has correct content' ); From b5b2eb0f702e04b4d57597553104d3e9c6a4b667 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sat, 5 Aug 2023 17:02:29 -0500 Subject: [PATCH 02/12] Update the scaffold.pl macro to use post processing. This means that answers are no longer evaluated by the macro. Instead the scaffold sections are finalized after the answers are processed normally by the translator. --- macros/core/scaffold.pl | 536 +++++++++++++++++++--------------------- 1 file changed, 257 insertions(+), 279 deletions(-) diff --git a/macros/core/scaffold.pl b/macros/core/scaffold.pl index bce37b3525..6953a9e186 100644 --- a/macros/core/scaffold.pl +++ b/macros/core/scaffold.pl @@ -44,17 +44,15 @@ =head1 DESCRIPTION Scaffold::Begin(); Section::Begin("Part 1: The first part"); - BEGIN_TEXT - This is the text for part 1. \(1+1\) = \{ans_rule\} - END_TEXT - ANS(Real(2)->cmp); + BEGIN_PGML + This is the text for part 1. [`1+1 =`] [_]{Real(2)} + END_PGML Section::End(); Section::Begin("Part 2: The second part"); - BEGIN_TEXT - This is text for the second part. \(2*2\) = \{ans_rule\} - END_TEXT - ANS(Real(4)->cmp); + BEGIN_PGML + This is text for the second part. [`2*2 =`] [_]{Real(4)} + END_PGML Section::End(); Scaffold::End(); @@ -75,7 +73,7 @@ =head1 DESCRIPTION answer blank to be considered correct by using the C option on the answer checker. For example: - ANS(Real(123)->cmp(scaffold_force => 1)); + [_]{ Real(123)->cmp(scaffold_force => 1) }; would mean that this answer would not have to be correct for the section to be considered correct. @@ -85,7 +83,7 @@ =head1 DESCRIPTION last one, if you wish. That material would always be showing, regardless of which sections are open. So, for example, you could put a data table in the area before the first section, so that it would be -visible throughtout the problem, no matter which section the student +visible through out the problem, no matter which section the student is working on. The C function accepts optional parameters that @@ -264,11 +262,9 @@ sub _scaffold_init { # Load style and javascript for opening and closing the scaffolds. ADD_CSS_FILE("js/Scaffold/scaffold.css"); ADD_JS_FILE("js/Scaffold/scaffold.js", 0, { defer => undef }); + return; } -# -# The Scaffoling package -# package Scaffold; our $forceOpen = $main::envir{forceScaffoldsOpen}; @@ -277,31 +273,30 @@ package Scaffold; our $isPTX = $main::displayMode eq "PTX"; our $afterAnswerDate = $main::envir{answersAvailable}; -our $scaffold; # the active scaffold (set by Begin() below) -my @scaffolds = (); # array of nested scaffolds -my $scaffold_no = 0; # each scaffold gets a unique number -my $scaffold_depth = 1; # each scaffold has a nesting depth +our $scaffold; # the active scaffold (set by Begin() below) +my @scaffolds; # array of nested scaffolds +my $scaffold_no = 0; # each scaffold gets a unique number +my $scaffold_depth = 1; # each scaffold has a nesting depth our $PG_ANSWERS_HASH = $main::PG->{PG_ANSWERS_HASH}; # where PG stores answer evaluators our $PG_OUTPUT = $main::PG->{OUTPUT_ARRAY}; # where PG stores the TEXT() output our $PREFIX = "$main::envir{QUIZ_PREFIX}Prob-$main::envir{probNum}"; +# Scaffold::Begin() is used to start a new scaffold section, passing +# it any options that need to be overridden (e.g. is_open, can_open, +# open_first_section, etc). # -# Scaffold::Begin() is used to start a new scaffold section, passing -# it any options that need to be overriden (e.g. is_open, can_open, -# open_first_section, etc). -# -# Problems can include more than one scaffold, if desired, -# and they can be nested. -# -# We save the current PG_OUTPUT, which will be put back durring -# the Scaffold::End() call. The sections use PG_OUTPUT to create -# their own text, which is added to the $scaffold->{output} -# during the Section::End() call. +# Problems can include more than one scaffold, if desired, +# and they can be nested. # +# We save the current PG_OUTPUT, which will be put back during +# the Scaffold::End() call. The sections use PG_OUTPUT to create +# their own text, which is added to the $scaffold->{output} +# during the Section::End() call. sub Begin { - my $self = Scaffold->new(@_); + my %options = @_; + my $self = Scaffold->new(%options); unshift(@scaffolds, $self); $scaffold = $self; $scaffold_depth++; @@ -311,47 +306,49 @@ sub Begin { return $self; } +# Scaffold::End() is used to end the scaffold. # -# Scaffold::End() is used to end the scaffold. -# -# This puts the scaffold into the page output -# and opens the sections that should be open. -# Then the next nested scaffold (if any) is poped off -# the stack and returned. -# +# This puts the scaffold into the page output and opens the sections that should be open. +# Then the next nested scaffold (if any) is popped off the stack and returned. sub End { Scaffold->Error("Scaffold::End() without a corresponding Scaffold::Begin") unless @scaffolds; Scaffold->Error("Scaffold ended with section was still open") if $self->{current_section}; my $self = $scaffold; - push(@{ $self->{output} }, splice(@$PG_OUTPUT, 0)); # collect any final non-section output - $self->hide_other_results(@{ $self->{open} }); # hide results of unnopened sections in the results table - push(@$PG_OUTPUT, @{ $self->{previous_output} }, @{ $self->{output} }) - ; # put back original output and scaffold output + + # collect any final non-section output + push(@{ $self->{output} }, splice(@$PG_OUTPUT, 0)); + + # hide results of unopened sections in the results table + main::add_content_post_processor(sub { + my ($problemContents, $headerContents) = @_; + return if $main::displayMode eq 'TeX'; + $self->hide_other_results($headerContents, @{ $self->{open} }); + }); + + # put back original output and scaffold output + push(@$PG_OUTPUT, @{ $self->{previous_output} }, @{ $self->{output} }); + delete $self->{previous_output}; - delete $self->{output}; # don't need these any more + delete $self->{output}; shift(@scaffolds); $scaffold = $scaffolds[0]; $scaffold_depth--; return $scaffold; } -# -# Report an error and die -# +# Report an error and die sub Error { my $self = shift; my $error = shift; die $error; } +# Create a new Scaffold object. # -# Create a new Scaffold object. -# -# Set the defaults for can_open, is_open, etc., but allow -# the author to override them. -# +# Set the defaults for can_open, is_open, etc., but allow +# the author to override them. sub new { - my $class = shift; + my ($class, %options) = @_; $class = ref($class) if ref($class); my $self = bless { can_open => "when_previous_correct", @@ -361,7 +358,7 @@ sub new { hardcopy_is_open => "always", # open all possible sections in hardcopy open_first_section => 1, # 0 means don't open any sections initially numbered => 0, # 1 means sections will be printed with their number - @_, + %options, number => ++$scaffold_no, # the number for this scaffold depth => $scaffold_depth, # the nesting depth for this scaffold sections => {}, # the sections within this scaffold @@ -373,11 +370,9 @@ sub new { return $self; } -# -# Add a section to the scaffold and give it a unique number (within -# the scaffold). Determine its label and save it as current_section -# so that we know which section is active. -# +# Add a section to the scaffold and give it a unique number (within +# the scaffold). Determine its label and save it as current_section +# so that we know which section is active. sub start_section { my $self = shift; my $section = shift; @@ -389,127 +384,84 @@ sub start_section { return $section; } -# -# Add the content from the current section into the scaffold's output -# and remove the current_section (so we can tell that no sectionis open). -# +# Add the content from the current section into the scaffold's output +# and remove the current_section (so we can tell that no section is open). sub end_section { my $self = shift; push(@{ $self->{output} }, splice(@{$PG_OUTPUT}, 0)); # save the section output delete $self->{current_section}; + return; } -# -# Record the answers for a section, and evaluate them, if non-empty, -# keeping the scores for future reference. -# +# Record the answers for a section. +# Scores are obtained when post processing is done. sub section_answers { - my $self = shift; - my %answers; - # - # MultiAnswer objects can set the answer hash score when the last answer is evaluated, - # so save the hashes and look up the scores after they have all been called. - # Essay answers never return as correct, so special case them, and provide a - # "scaffold_force" option in the AnswerHash that can be used to force Scaffold - # to consider the score to be 1 (bug in PGessaymacros.pl prevents us from using - # it for essay_cmp(), though). - # - push(@{ $self->{ans_names} }, @_); - foreach my $name (@_) { - my $input = $main::inputs_ref->{$name}; - my $evaluator = $PG_ANSWERS_HASH->{$name}->ans_eval; - Parser::Eval(sub { $answers{$name} = $evaluator->evaluate($input) }) if defined($input) && $input ne ""; - $answers{$name}{score} = 1 - if $answers{$name} && (($answers{$name}{type} || "") eq "essay" || $answers{$name}{"scaffold_force"}); - $evaluator->{rh_ans}{ans_message} = ""; - delete $evaluator->{rh_ans}{error_message}; - } - foreach my $name (@_) { $self->{scores}{$name} = $answers{$name}{score} if $answers{$name} } + my ($self, @section_answers) = @_; + push(@{ $self->{ans_names} }, @section_answers); + return; } -# -# Add the given sections to the list of sections to be openned -# for this scaffold -# +# Add the given sections to the list of sections to be opened for this scaffold. sub is_open { - my $self = shift; - push(@{ $self->{open} }, map { $_->{number} } @_) if @_; + my ($self, @open_sections) = @_; + push(@{ $self->{open} }, map { $_->{number} } @open_sections) if @open_sections; return $self->{open}; } -# -# Add CSS to dim the rows of the table that are not in the open -# section. (When a section is marked correct, the next section will -# be opened, so the correct answers will be dimmed, and the new -# section's blank rows will be active. That may be a downside to the -# dimming.) -# +# Add CSS to dim the rows of the table that are not in the open section and that are not correct. +# This is run in post processing after all of the section post processing has been completed. So at this point +# everything is known about the status of all answers in this scaffold. sub hide_other_results { - my $self = shift; - # - # Record the row for each answer evaluator, and - # mark which sections to show - # + my ($self, $headerContents, @openSections) = @_; + + # Record the row for each answer evaluator, and mark which sections to show. my %row; my $i = 2; - foreach my $name (keys %{$PG_ANSWERS_HASH}) { $row{$name} = $i; $i++ }; # record the rows for all answers + for (keys %{$PG_ANSWERS_HASH}) { $row{$_} = $i; ++$i }; # record the rows for all answers my %show; - map { $show{$_} = 1 } @_; - # - # Get the row numbers for the answers from OTHER sections - # - my @hide = (); - foreach $i (keys %{ $self->{sections} }) { - push(@hide, map { $row{$_} } @{ $self->{sections}{$i}{ans_names} }) if !$show{$i}; + map { $show{$_} = 1 } @openSections; + + # Get the row numbers for the answers from OTHER sections + my @hide; + for my $section (keys %{ $self->{sections} }) { + push(@hide, map { $row{$_} } @{ $self->{sections}{$section}{ans_names} }) + if !$show{$section} && !$self->{sections}{$section}{is_correct}; } - # - # Add styles that dim the hidden rows - # (the other possibility would be to use display:none) - # + + # Add styles that dim the hidden rows that are not correct (the other possibility would be to use display:none) if (@hide) { - my @styles = (map {".attemptResults > tbody > tr:nth-child($_) {opacity:.5}"} @hide); - main::HEADER_TEXT(''); + $headerContents->append_content(''); } -} -# -# Check if a scaffold is completely correct. -# (Must be called after the last section is ended.) -# -sub is_correct { - my $self = shift; - my $scores = $self->{scores}; - foreach my $name (@{ $self->{ans_names} }) { return 0 unless ($scores->{$name} || 0) >= 1 } - return 1; + return; } package Section; -# -# Shortcuts for Scaffold data -# +# Shortcuts for Scaffold data $PG_ANSWERS_HASH = $Scaffold::PG_ANSWERS_HASH; $PG_OUTPUT = $Scaffold::PG_OUTPUT; +# Section::Begin() is used to start a section in the scaffolding, +# passing it the name of the section and any options (e.g., can_open, +# is_open, etc.). # -# Section::Begin() is used to start a section in the scaffolding, -# passing it the name of the section and any options (e.g., can_open, -# is_open, etc.). -# -# The section is added to the scaffold, and the names of the answer -# blanks for previous sections are recorded, along with information -# about the answer blanks that have evaluators assigned (so we can -# see which answers belong to this section when it closes). -# +# The section is added to the scaffold, and the names of the answer +# blanks for previous sections are recorded, along with information +# about the answer blanks that have evaluators assigned (so we can +# see which answers belong to this section when it closes). sub Begin { + my ($section_name, %options) = @_; my $scaffold = $Scaffold::scaffold; Scaffold->Error("Sections must appear within a Scaffold") unless $scaffold; Scaffold->Error("Section::Begin() while a section is already open") if $scaffold->{current_section}; - my $self = $scaffold->start_section(Section->new(@_)); + my $self = $scaffold->start_section(Section->new($section_name, %options)); my $number = $self->{number}; my $number_at_depth = $number; # Convert the number (e.g. 2) into a depth-styled version (e.g. b, ii, B) - # Supports numbers up to 99 and depth up to 3 but then leaves in arabic + # Supports numbers up to 99 and depth up to 3 but then leaves in Arabic if ($scaffold->{depth} == 1 && $number <= 99) { $number_at_depth = ('a' .. 'cu')[ $number - 1 ]; } elsif ($scaffold->{depth} == 2 && $number <= 99) { @@ -538,19 +490,17 @@ sub Begin { $number_at_depth = ('A' .. 'CU')[ $number - 1 ]; } $self->{number_at_depth} = $number_at_depth; - $self->{previous_ans} = [ @{ $scaffold->{ans_names} } ], # copy of current list of answers in the scaffold - $self->{assigned_ans} = [ $self->assigned_ans ], # array indicating which answers have evaluators - return $self; + $self->{previous_ans} = [ @{ $scaffold->{ans_names} } ]; # copy of current list of answers in the scaffold + $self->{assigned_ans} = [ $self->assigned_ans ]; # array indicating which answers have evaluators + return $self; } +# Section::End() is used to end the active section. # -# Section::End() is used to end the active section. -# -# We get the names of the answer blanks that are in this section, -# then add the HTML around the section that is used by jQuery -# for showing/hiding the section, and finally tell the scaffold -# that the section is complete (it adds the content to its output). -# +# We get the names of the answer blanks that are in this section, +# then add the HTML around the section that is used by JavaScript +# for showing/hiding the section, and finally tell the scaffold +# that the section is complete (it adds the content to its output). sub End { my $scaffold = $Scaffold::scaffold; Scaffold->Error("Sections must appear within a Scaffold") unless $scaffold; @@ -560,19 +510,17 @@ sub End { $scaffold->section_answers(@{ $self->{ans_names} }); $self->add_container(); $scaffold->end_section(); + return; } +# Create a new Section object. # -# Create a new Section object. -# -# It takes default values for can_open, is_open, etc. -# from the active scaffold. These can be overridden -# by the author. -# +# It takes default values for can_open, is_open, etc. +# from the active scaffold. These can be overridden +# by the author. sub new { - my $class = shift; + my ($class, $name, %options) = @_; $class = ref($class) if ref($class); - my $name = shift; my $scaffold = $Scaffold::scaffold; my $self = bless { name => $name, @@ -581,23 +529,16 @@ sub new { after_AnswerDate_can_open => $scaffold->{after_AnswerDate_can_open}, is_open => $scaffold->{is_open}, hardcopy_is_open => $scaffold->{hardcopy_is_open}, - @_, + %options }, $class; return $self; } -# -# Adds the necessary HTML around the content of the section. -# -# First, determine the is_correct and can_open status and save them. -# Then check if the section is to be openned, and if so, add it -# to the open list of the scaffold. -# -# The $PG_OUTPUT variable holds just the contents of this section, -# so we unshift the openning tags onto the front, and push -# the closing tags onto the back. (This is added to the scaffold -# output when $scaffold->end_section() is called.) -# +# Adds the necessary HTML around the content of the section. Initially a temporary "scaffold-section" tag is added that +# wraps the content, and that is replaced with the correct HTML in post processing. The content is also removed in post +# processing if the scaffold can not be opened and is not correct. The $PG_OUTPUT variable holds just the contents of +# this section, so unshift the opening tags onto the front, and push the closing tags onto the back. (This is added to +# the scaffold output when $scaffold->end_section() is called.) sub add_container { my $self = shift; my $scaffold = $Scaffold::scaffold; @@ -605,63 +546,123 @@ sub add_container { my $name = $self->{name} // ''; my $title = ($name || $scaffold->{numbered}) ? $name : "Part $self->{number}:"; my $number = ($scaffold->{numbered} ? $self->{number_at_depth} . '.' : ''); - my ($iscorrect, $canopen, $isopen); - $iscorrect = $self->{is_correct} = $self->is_correct; - $canopen = $self->{can_open} = $self->can_open; - $isopen = $self->is_open; - - $scaffold->is_open($self) if $isopen; - splice(@$PG_OUTPUT, 0, scalar(@$PG_OUTPUT)) - if !($canopen || $iscorrect || $Scaffold::isPTX) || (!$isopen && $Scaffold::isHardcopy); unshift( @$PG_OUTPUT, - @{ - main::MODES( - HTML => [ - '
', - '
', - '
', - '', - '
', - qq{
}, - '
' - ], - TeX => ["\\par{\\bf $number $title}\\addtolength{\\leftskip}{15pt}\\par "], - PTX => $name ? [ "\n", "$name\n" ] : ["\n"], - ) - } + main::MODES( + HTML => qq{}, + TeX => + "\\par{\\bf $number $title}\\addtolength{\\leftskip}{15pt}\\par\n%scaffold-section-$label-start\n", + PTX => $name ? "\n$name\n" : "\n", + ) ); push( @$PG_OUTPUT, main::MODES( - HTML => '
', - TeX => "\\addtolength{\\leftskip}{-15pt}\\par ", + HTML => '', + TeX => "%scaffold-section-$label-end\n\\addtolength{\\leftskip}{-15pt}\\par ", PTX => "<\/task>\n", ) ); + + main::add_content_post_processor(sub { + my $problemContents = shift; + + # 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). + 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}; + } + + # Set the active scaffold to the scaffold for this section so that is_correct, can_open, + # and is_open methods use the correct one. + $Scaffold::scaffold = $scaffold; + + # Now, determine the is_correct and can_open status and save them. + # Then check if the section is to be opened, and if so, add it + # to the open list of the scaffold. + my $iscorrect = $self->{is_correct} = $self->is_correct; + my $canopen = $self->{can_open} = $self->can_open; + my $isopen = $self->is_open; + + $scaffold->is_open($self) if $isopen; + + if ($Scaffold::isHardcopy) { + $$problemContents =~ s/%scaffold-section-$label-start.*%scaffold-section-$label-end//ms + if !($canopen || $iscorrect) || !$isopen; + } else { + my $sectionElt = $problemContents->at(qq{scaffold-section[id="$label"]}); + return unless $sectionElt; + + $sectionElt->tag('div'); + delete $sectionElt->attr->{id}; + $sectionElt->attr(class => 'accordion'); + + $sectionElt->content('') if !($canopen || $iscorrect); + + $sectionElt->wrap_content( + Mojo::DOM->new_tag( + 'div', + class => 'accordion-item section-div', + sub { + Mojo::DOM->new_tag( + 'div', + id => $label, + class => 'accordion-collapse collapse' . ($isopen ? ' show' : ''), + 'aria-labelledby' => "$label-header", + sub { Mojo::DOM->new_tag('div', class => 'accordion-body') } + ); + } + )->to_string + ); + + $sectionElt->at('.accordion-item.section-div')->prepend_content( + Mojo::DOM->new_tag( + 'div', + class => 'accordion-header' + . ($iscorrect ? ' iscorrect' : ' iswrong') + . ($canopen ? ' canopen' : ' cannotopen'), + id => "$label-header", + sub { + Mojo::DOM->new_tag( + 'button', + class => 'accordion-button' . ($isopen ? '' : ' collapsed'), + type => 'button', + ( + $canopen + ? ( + data => { bs_target => "#$label", bs_toggle => 'collapse' }, + 'aria-controls' => $label + ) + : (tabindex => '-1') + ), + aria_expanded => ($isopen ? 'true' : 'false'), + sub { + Mojo::DOM->new_tag('span', class => 'section-number', $number) + . Mojo::DOM->new_tag('span', class => 'section-title', $title); + } + ); + } + )->to_string + ); + } + + return; + }); + + return; } -# -# Check if all the answers for this section are correct -# +# Check if all the answers for this section are correct sub is_correct { my $self = shift; my $scores = $Scaffold::scaffold->{scores}; @@ -669,10 +670,8 @@ sub is_correct { return 1; } -# -# Perform the can_open check for this section: -# If the author supplied code, use it, otherwise use the routine from Section::can_open. -# +# Perform the can_open check for this section: +# If the author supplied code, use it, otherwise use the routine from Section::can_open. sub can_open { my $self = shift; return 1 if $Scaffold::forceOpen; @@ -682,10 +681,8 @@ sub can_open { return &{$method}($self); } -# -# Peform the is_open check for this section: -# If the author supplied code, use it, otherwise use the routine from Section::is_open. -# +# Perform the is_open check for this section: +# If the author supplied code, use it, otherwise use the routine from Section::is_open. sub is_open { my $self = shift; return 1 if $Scaffold::forceOpen; @@ -696,28 +693,23 @@ sub is_open { return &{$method}($self); } -# -# Return a boolean array where a 1 means that answer blank has -# an answer evaluator assigned to it and 0 means not. -# +# Return a boolean array where a 1 means that answer blank has +# an answer evaluator assigned to it and 0 means not. sub assigned_ans { - my $self = shift; - my @answers = (); + my $self = shift; + my @answers; foreach my $name (keys %{$PG_ANSWERS_HASH}) { push(@answers, $PG_ANSWERS_HASH->{$name}->ans_eval ? 1 : 0); } return @answers; } -# -# Get the names of any of the original answer blanks that now have -# evaluators attached. -# +# Get the names of any of the original answer blanks that now have evaluators attached. sub new_answers { my $self = shift; my @assigned = @{ $self->{assigned_ans} }; # 0 if previously unassigned, 1 if assigned - my @answers = (); - my $i = 0; + my @answers; + my $i = 0; foreach my $name (keys %{$PG_ANSWERS_HASH}) { push(@answers, $name) if $PG_ANSWERS_HASH->{$name}->ans_eval && !$assigned[$i]; $i++; @@ -727,60 +719,49 @@ sub new_answers { } ######################################################################## -# -# Implements the possible values for the can_open option for scaffolds -# and sections -# +# Implements the possible values for the can_open option for scaffolds +# and sections + package Section::can_open; -# -# Always can be openned -# +# Always can be opened sub always { return 1 } -# -# Can be openned when all the answers from previous sections are correct -# + +# Can be opened when all the answers from previous sections are correct sub when_previous_correct { my $section = shift; my $scores = $Scaffold::scaffold->{scores}; foreach my $name (@{ $section->{previous_ans} }) { return 0 unless ($scores->{$name} || 0) >= 1 } return 1; } -# -# Can open when previous are correct but this one is not -# + +# Can open when previous are correct but this one is not sub first_incorrect { my $section = shift; return when_previous_correct($section) && !$section->{is_correct}; } -# -# Can open when incorrect -# + +# Can open when incorrect sub incorrect { my $section = shift; return !$section->{is_correct}; } -# -# Never can be openned -# + +# Never can be opened sub never { return 0 } ######################################################################## -# -# Implements the possible values for the is_open option for scaffolds -# and sections -# +# Implements the possible values for the is_open option for scaffolds +# and sections + package Section::is_open; -# -# Every section is open that can be -# +# Every section is open that can be sub always { return 1 } -# -# Every incorrect section is open that can be -# (unless it is the first one, and everything is blank, and -# the scaffold doesn't have open_first_section set) -# + +# Every incorrect section is open that can be +# (unless it is the first one, and everything is blank, and +# the scaffold doesn't have open_first_section set) sub incorrect { my $section = shift; my $scaffold = $Scaffold::scaffold; @@ -793,24 +774,21 @@ sub incorrect { } return 1; } -# -# The first incorrect section is open that can be -# + +# The first incorrect section is open that can be sub first_incorrect { my $section = shift; return Section::is_open::incorrect($section) && Section::can_open::when_previous_correct($section); } -# -# All correct sections and the first incorrect section -# are open (that are allowed to be open) -# + +# All correct sections and the first incorrect section +# are open (that are allowed to be open) sub correct_or_first_incorrect { my $section = shift; - return 1 if $section->{is_correct} || Section::is_open::first_incorrect($section); + return $section->{is_correct} || Section::is_open::first_incorrect($section); } -# -# No sections are open -# + +# No sections are open sub never { return 0 } 1; From c01a16b5f8b3aec66df64e69c04790d6a9d45871 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 15 Sep 2023 19:31:06 -0500 Subject: [PATCH 03/12] Move answer stringification after content post processing. This is so that the content post processor methods have access to the full answer objects. This gives this approach a whole lot more power than the (soon to be eliminated) results table has. --- lib/WeBWorK/PG.pm | 7 ++++--- lib/WeBWorK/PG/Translator.pm | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index d3e5dc654c..a89fb374c3 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -173,9 +173,9 @@ sub new_helper ($invocant, %options) { ); } - # HTML_dpng uses an ImageGenerator. We have to render the queued equations. - # 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. + # HTML_dpng uses an ImageGenerator. We have to render the queued equations. 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 with values and this will fail. if ($image_generator) { my $sourceFile = "$options{templateDirectory}$options{sourceFilePath}"; $image_generator->render( @@ -185,6 +185,7 @@ sub new_helper ($invocant, %options) { } $translator->post_process_content if ref($translator->{rh_pgcore}) eq 'PGcore'; + $translator->stringify_answers; return bless { translator => $translator, diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index 21c8249811..931c99e59b 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -49,6 +49,7 @@ WeBWorK::PG::Translator - Evaluate PG code and evaluate answers safely my $rh_problem_result = $pt->grade_problem(%options); # grades the problem. $pt->post_process_content; # Execute macro or problem hooks that further modify the problem content. + $pt->stringify_answers; # Convert objects to strings in the answer hash =head1 DESCRIPTION @@ -1036,7 +1037,6 @@ sub grade_problem { use strict; die $@ if $@; - $self->stringify_answers; return ($self->{rh_problem_result}, $self->{rh_problem_state}); } From 6bfcb6c092818dbf1ab4323153e9bc6e9d33e19c Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Fri, 27 Oct 2023 14:45:20 -0700 Subject: [PATCH 04/12] allow empty set symbol for empty solution sets --- macros/contexts/contextFiniteSolutionSets.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/macros/contexts/contextFiniteSolutionSets.pl b/macros/contexts/contextFiniteSolutionSets.pl index 942e3ff6a0..6976796162 100644 --- a/macros/contexts/contextFiniteSolutionSets.pl +++ b/macros/contexts/contextFiniteSolutionSets.pl @@ -103,6 +103,7 @@ sub _contextFiniteSolutionSets_init { "none" => { alias => 'no real solutions' }, "no solution" => { alias => 'no real solutions' }, "no solutions" => { alias => 'no real solutions' }, + "\x{2205}" => { alias => 'no real solutions' }, #Hack. Investigate making all of this be a constant. "{}" => { alias => 'no real solutions' }, "{ }" => { alias => 'no real solutions' }, From 48afc5a99c33fd81f94bcf7ea71b439b39be3d53 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 18 Sep 2023 08:35:48 -0500 Subject: [PATCH 05/12] Add feedback in the post processing phase which replaces the webwork2 attempts table. A feedback button is placed somewhere in the DOM for each response group. Default locations that should work well in most cases are determined in post processing. However, there is a ww-feedback-container css class that is provided in problem.scss, and a problem author can use it to override the default location by wrapping the responses in a div or span with that class. The classes "align-middle" and "align-bottom" can be added to the container in addition to align the feedback button. By default the button is aligned at the top. The result summary is also generated by PG and is returned in the result summary. The caller can decide to use it or not, and where to place it. --- htdocs/js/Essay/essay.js | 38 +- htdocs/js/Feedback/feedback.js | 45 ++ htdocs/js/InputColor/color.js | 69 -- htdocs/js/MathQuill/mqeditor.js | 32 +- htdocs/js/MathQuill/mqeditor.scss | 18 +- htdocs/js/Problem/problem.scss | 281 +++---- .../js/RadioMultiAnswer/RadioMultiAnswer.scss | 60 +- htdocs/js/Scaffold/scaffold.js | 15 +- lib/Applet.pm | 6 +- lib/DragNDrop.pm | 3 +- lib/Value/AnswerChecker.pm | 17 +- lib/WeBWorK/PG.pm | 54 +- macros/PG.pl | 532 +++++++++++++- macros/core/PGbasicmacros.pl | 685 ++++++++---------- macros/core/PGessaymacros.pl | 196 ++--- macros/core/scaffold.pl | 14 +- macros/graph/parserGraphTool.pl | 35 +- macros/math/draggableProof.pl | 10 +- macros/math/draggableSubsets.pl | 7 +- macros/parsers/parserCheckboxList.pl | 5 +- macros/parsers/parserPopUp.pl | 45 +- macros/parsers/parserRadioButtons.pl | 22 +- macros/parsers/parserRadioMultiAnswer.pl | 38 +- t/macros/basicmacros.t | 4 +- t/pg_problems/problem_file.t | 4 +- 25 files changed, 1401 insertions(+), 834 deletions(-) create mode 100644 htdocs/js/Feedback/feedback.js delete mode 100644 htdocs/js/InputColor/color.js 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/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js new file mode 100644 index 0000000000..ee39da494c --- /dev/null +++ b/htdocs/js/Feedback/feedback.js @@ -0,0 +1,45 @@ +(() => { + const initializeFeedback = (feedbackBtn) => { + if (feedbackBtn.dataset.popoverInitialized) return; + feedbackBtn.dataset.popoverInitialized = 'true'; + + new bootstrap.Popover(feedbackBtn, { sanitize: false }); + + // Render MathJax previews. + if (window.MathJax) { + feedbackBtn.addEventListener('show.bs.popover', () => { + MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise(['.popover-body'])); + }); + } + + // Execute javascript in the answer preview. + feedbackBtn.addEventListener('shown.bs.popover', () => { + const bsPopover = bootstrap.Popover.getInstance(feedbackBtn); + bsPopover.tip?.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); + }); + }); + }; + + // Setup feedback popovers already on the page. + document.querySelectorAll('.ww-feedback-btn').forEach(initializeFeedback); + + // Deal with feedback popovers that are added to the page later. + const observer = new MutationObserver((mutationsList) => { + mutationsList.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof Element) { + if (node.classList.contains('ww-feedback-btn')) initializeFeedback(node.firstElementChild); + else node.querySelectorAll('.ww-feedback-btn').forEach(initializeFeedback); + } + }); + }); + }); + observer.observe(document.body, { childList: true, subtree: true }); + + // Stop the mutation observer when the window is closed. + window.addEventListener('unload', () => observer.disconnect()); +})(); diff --git a/htdocs/js/InputColor/color.js b/htdocs/js/InputColor/color.js deleted file mode 100644 index 25fa134837..0000000000 --- a/htdocs/js/InputColor/color.js +++ /dev/null @@ -1,69 +0,0 @@ -// For coloring the input elements with the proper color based on whether they are correct or incorrect. - -(() => { - const setupAnswerLink = (answerLink) => { - const answerId = answerLink.dataset.answerId; - const answerInput = document.getElementById(answerId); - - const type = answerLink.parentNode.classList.contains('ResultsWithoutError') ? 'correct' : 'incorrect'; - const radioGroups = {}; - - // 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}`) - .forEach((input) => { - if (input.type.toLowerCase() === 'radio') { - if (!radioGroups[input.name]) radioGroups[input.name] = []; - radioGroups[input.name].push(input); - } else { - if (input.type.toLowerCase() === 'text') { - if (type === 'correct' || input.value !== '') input.classList.add(type); - } else - input.classList.add(type); - } - }); - - Object.values(radioGroups).forEach((group) => { - if (group.every((radio) => { - if (radio.checked) { - radio.classList.add(type); - return false; - } - return true; - })) { - group.forEach((radio) => radio.classList.add(type)); - } - }); - - if (answerInput) { - answerLink.addEventListener('click', (e) => { - e.preventDefault(); - answerInput.focus(); - }); - } else { - answerLink.removeAttribute('href'); - } - }; - - // Color inputs already on the page. - document.querySelectorAll('td a[data-answer-id]').forEach(setupAnswerLink); - - // Deal with inputs that are added to the page later. - const observer = new MutationObserver((mutationsList) => { - mutationsList.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node instanceof Element) { - if (node.type && node.type.toLowerCase() === 'td' && node.firstElementChild - && node.firstElementChild.type.toLowerCase() == 'a' && node.firstElementChild.dataset.answerId) - setupAnswerLink(node.firstElementChild); - else node.querySelectorAll('td a[data-answer-id]').forEach(setupAnswerLink); - } - }); - }); - }); - observer.observe(document.body, { childList: true, subtree: true }); - - // Stop the mutation observer when the window is closed. - window.addEventListener('unload', () => observer.disconnect()); -})(); diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 57ad5fa0e1..8c313a79f9 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('partially-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..81f3636a6e 100644 --- a/htdocs/js/MathQuill/mqeditor.scss +++ b/htdocs/js/MathQuill/mqeditor.scss @@ -3,27 +3,11 @@ 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; margin-left: 0; - - &.correct { - border-color: rgba(81, 153, 81, 0.8); - outline: 0; - outline: thin dotted \9; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(81, 153, 81, 0.6); - color: inherit; - } - - &.incorrect { - border-color: rgba(191, 84, 84, 0.8); - outline: 0; - outline: thin dotted \9; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(191, 84, 84, 0.6); - color: inherit; - } } input[type='text'].codeshard.mq-edit { diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 20c10cf4b2..7733bb3a36 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,23 @@ 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'], + span[id^='mq-answer'], + .graphtool-container { &.correct { border-color: rgba(81, 153, 81, 0.8); /* green */ outline: 0; @@ -72,11 +83,19 @@ 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; + } + + &.partially-correct { + border-color: rgba(255, 193, 7, 0.8); /* yellow */ + outline: 0; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px 2px rgba(255, 193, 7, 0.6); + 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; @@ -90,16 +109,22 @@ padding-right: 29px; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23AE5757'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23AE5757' stroke='none'/%3e%3c/svg%3e"); } + + &.partially-correct { + padding-right: 29px; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='%23FFC107' stroke='black'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='black' stroke='none'/%3e%3c/svg%3e"); + } } - input[type=radio] { + input[type='radio'] { margin-right: 0.25rem; } select { cursor: pointer; - &[multiple], &[size] { + &[multiple], + &[size] { height: auto; } } @@ -108,7 +133,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,11 +146,46 @@ .pg-table { text-align: center; - thead, tbody, tfoot, tr, td, th { + thead, + tbody, + tfoot, + tr, + td, + th { padding: 0.25rem; border: 1px solid black; } } + + .radio-buttons-container, + .checkboxes-container, + .applet-container, + .graphtool-outer-container, + .ww-feedback-container { + position: relative; + width: fit-content; + + .ww-feedback-btn { + position: absolute; + left: 100%; + top: 0; + margin-left: 0.25rem; + } + + &.align-middle { + .ww-feedback-btn { + top: 50%; + transform: translateY(-50%); + } + } + + &.align-bottom { + .ww-feedback-btn { + top: unset; + bottom: 0; + } + } + } } /* rtl:raw: @@ -131,141 +194,101 @@ } */ -/* Answer template */ +/* Feedback */ -.attemptResultsHeader { - margin-bottom: 0.5rem; - padding: 0; - font-size: 1.4875rem; - font-weight: bold; - line-height: 1.2; +.ResultsWithoutError { + color: #0f5132; /* Dark Green */ + background-color: #8f8; /* Light Green */ + padding: 0.25rem; + border: 1px solid transparent; + border-radius: 0.25rem; + box-shadow: 3px 3px 3px darkgray; } -table.attemptResults { - border-style: outset; - border-width: 1px; - margin-bottom: 1em; - border-spacing: 1px; - width: 100%; - border: 1px solid #ddd; - border-collapse: separate; - border-radius: 4px; - - thead:first-child tr:first-child > th:first-child, - tbody:first-child tr:first-child > td:first-child, - tbody:first-child tr:first-child > th:first-child { - border-top-left-radius: 4px; - } - - thead:first-child tr:first-child > th:last-child, - tbody:first-child tr:first-child > td:last-child, - tbody:first-child tr:first-child > th:last-child { - border-top-right-radius: 4px; - } +.ResultsWithError { + color: #400; /* Dark Red */ + background-color: #d69191; /* Light Red */ + padding: 0.25rem; + border: 1px solid transparent; + border-radius: 0.25rem; + box-shadow: 3px 3px 3px darkgray; +} - thead:last-child tr:last-child > th:first-child, - tbody:last-child tr:last-child > td:first-child, - tbody:last-child tr:last-child > th:first-child, - tfoot:last-child tr:last-child > td:first-child, - tfoot:last-child tr:last-child > th:first-child { - border-bottom-left-radius: 4px; - } +.ResultsAlert { + color: #0e038c; /* Dark Blue */ + background-color: #fbd2b8; /* Light Orange */ + padding: 0.25rem; + border: 1px solid transparent; + border-radius: 0.25rem; + box-shadow: 3px 3px 3px darkgray; +} - thead:last-child tr:last-child > th:last-child, - tbody:last-child tr:last-child > td:last-child, - tbody:last-child tr:last-child > th:last-child, - tfoot:last-child tr:last-child > td:last-child, - tfoot:last-child tr:last-child > th:last-child { - border-bottom-right-radius: 4px; - } +.ww-feedback-popover { + --bs-popover-body-padding-x: 0; + --bs-popover-body-padding-y: 0; + --bs-popover-max-width: 600px; + --bs-popover-zindex: 18; - td, th { - border-style: inset; - border-width: 1px; + .popover-header { text-align: center; - vertical-align: middle; - padding: 2px 5px; - color: inherit; - border-color: #ddd; - background-color: #ddd; } - .ArrayLayout { - td { - border-style: none; - border-width: 0px; - padding: 0px; - background-color: transparent; + &.correct { + .popover-header { + --bs-popover-header-bg: #8f8; } } - .parsehilight { - color: inherit; - background-color: yellow; - } - - .popover { - max-width: 100%; - } - - td { - &.FeedbackMessage { - background-color: #ede275; /* Harvest Gold */ - } - - &.ResultsWithoutError { - background-color: #8f8; - } - - &.ResultsWithError { - background-color: #d69191; /* Light Red */ - color: black; + &.incorrect { + .popover-header { + --bs-popover-header-bg: #d69191; } } - div.answer-preview, - span.answer-preview { - display: block; - width: 100%; - height: 100%; - } - - a, a span { - color: #038; - text-decoration: none; - - &:hover { - text-decoration: underline; + &.partially-correct { + .popover-header { + --bs-popover-header-bg: #ffc107; } } -} - -div, label, span { - &.ResultsWithoutError { - color: #0f5132; /* Dark Green */ - background-color: #8f8; /* Light Green */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; - } - &.ResultsWithError { - color: #400; /* Dark Red */ - background-color: #d69191; /* Light Red */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; - } - - &.ResultsAlert { - color: #0e038c; /* Dark Blue */ - background-color: #fbd2b8; /* Light Orange */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; + .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; + } + + .ArrayLayout { + td { + border-style: none; + border-width: 0px; + padding: 0px; + background-color: transparent; + } + } + + &.feedback-message { + background-color: #ede275; + } + } + } } } diff --git a/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss b/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss index 6d6d23d3c0..7242d95fe0 100644 --- a/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss +++ b/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss @@ -13,33 +13,47 @@ // # Artistic License for more details. // ################################################################################ -.radio-container { - display: flex; - align-items: baseline; - - .radio-content { - display: inline-block; - margin-left: 5px; - line-height: 26px; - } +.radio-multianswer-container { + position: relative; + width: fit-content; + max-width: calc(100% - 1rem - 25px); - input[type='radio'] { - flex-shrink: 0; - margin-left: 0; - line-height: 26px; - align-self: first baseline; - } + .radio-container { + display: flex; + align-items: baseline; + position: relative; + width: fit-content; - label { - flex-shrink: 0; - line-height: 26px; - } + .radio-content { + display: inline-block; + margin-left: 5px; + line-height: 26px; + } + + input[type='radio'] { + flex-shrink: 0; + margin-left: 0; + line-height: 26px; + align-self: first baseline; + } + + label { + flex-shrink: 0; + line-height: 26px; + } + + input[disabled] { + pointer-events: none; + } - input[disabled] { - pointer-events: none; + .rma-state-disabled { + opacity: 0.35; + } } - .rma-state-disabled { - opacity: 0.35; + .ww-feedback-btn { + position: absolute; + left: 100%; + top: 0; } } diff --git a/htdocs/js/Scaffold/scaffold.js b/htdocs/js/Scaffold/scaffold.js index d36b986291..060c00747b 100644 --- a/htdocs/js/Scaffold/scaffold.js +++ b/htdocs/js/Scaffold/scaffold.js @@ -4,12 +4,19 @@ section.addEventListener('shown.bs.collapse', () => { // Reflow MathQuill answer boxes so that their contents are rendered correctly if (window.answerQuills) { - Object.keys(answerQuills).forEach( - (quill) => { if (section.querySelector('#' + quill)) answerQuills[quill].mathField.reflow(); } - ); + Object.keys(answerQuills).forEach((quill) => { + if (section.querySelector('#' + quill)) answerQuills[quill].mathField.reflow(); + }); } }); - }) + + section.addEventListener('hide.bs.collapse', () => { + // Close any open feedback popovers in this scaffold. + for (const button of section.querySelectorAll('.ww-feedback-btn')) { + bootstrap.Popover.getInstance(button)?.hide(); + } + }); + }); }; // Set up any scaffolds already on the page. diff --git a/lib/Applet.pm b/lib/Applet.pm index ecdad095f6..cb65230a19 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -524,7 +524,11 @@ sub insertObject { my $objectText = $self->{objectText}; $objectText =~ s/(\$\w+)/$1/gee; - return $objectText; + return + qq{
' + . $objectText + . '
'; } # These methods are defined so that they can be used in the derived objects in the AppletObjects.pl macro file. diff --git a/lib/DragNDrop.pm b/lib/DragNDrop.pm index 3bcf18c084..7fede69171 100644 --- a/lib/DragNDrop.pm +++ b/lib/DragNDrop.pm @@ -160,7 +160,8 @@ sub HTML { $out .= qq{ data-label-format="$self->{bucketLabelFormat}"} if $self->{bucketLabelFormat}; $out .= '>'; - $out .= '
'; + $out .= '
}; $out .= qq{}; $out .= qq{} if ($self->{allowNewBuckets}); diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm index 7abbda7099..b845c3b7be 100644 --- a/lib/Value/AnswerChecker.pm +++ b/lib/Value/AnswerChecker.pm @@ -490,7 +490,14 @@ sub ans_matrix { } push(@array, [@row]); } - $self->format_matrix([@array], open => $open, close => $close, sep => $sep, top_labels => $toplabels); + $self->format_matrix( + [@array], + open => $open, + close => $close, + sep => $sep, + top_labels => $toplabels, + ans_last_name => ANS_NAME($ename, $rows - 1, $cols - 1) + ); } sub ANS_NAME { @@ -599,7 +606,13 @@ sub format_matrix_HTML { . $close . ''; } - return '' . $HTML . ''; + return '' + . $HTML + . ''; } sub EVALUATE { diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index a89fb374c3..c34153e1a2 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -177,7 +177,6 @@ sub new_helper ($invocant, %options) { # 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 with values 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, @@ -187,6 +186,12 @@ sub new_helper ($invocant, %options) { $translator->post_process_content if ref($translator->{rh_pgcore}) eq 'PGcore'; $translator->stringify_answers; + # Add the result summary set in post processing into the result. + $result->{summary} = $translator->{rh_pgcore}{result_summary} + if ref($translator->{rh_pgcore}) eq 'PGcore' + && $translator->{rh_pgcore}{result_summary} + && (!defined $result->{summary} || $result->{summary} !~ /\S/); + return bless { translator => $translator, head_text => ${ $translator->r_header }, @@ -250,6 +255,15 @@ sub defineProblemEnvironment ($pg_envir, $options = {}, $image_generator = undef isInstructor => $options->{isInstructor} // 0, PERSISTENCE_HASH => $options->{PERSISTENCE_HASH} // {}, + # Attempt Results + showFeedback => $options->{showFeedback} // 0, + showAttemptAnswers => $options->{showAttemptAnswers} // 1, + showAttemptPreviews => $options->{showAttemptPreviews} // 1, + forceShowAttemptResults => $options->{forceShowAttemptResults} // 0, + showAttemptResults => $options->{showAttemptResults} // 0, + showMessages => $options->{showMessages} // 1, + showCorrectAnswers => $options->{showCorrectAnswers} // 0, + # The next has marks what data was updated and needs to be saved # by the front end. PERSISTENCE_HASH_UPDATED => {}, @@ -465,6 +479,44 @@ This may contain the following keys (example values are shown) useBaseTenLog: 0 defaultDisplayMatrixStyle: '[s]' # left delimiter, middle line delimiters, right delimiter +=item showFeedback (boolean, default: 0) + +Determines if feedback will be shown for answers in the problem. Note that +feedback will be shown if forceShowAttemptResults is true regardless of +the value of this option. + +=item showAttemptAnswers (boolean, default: 1) + +Determines if the student's evaluated (i.e. "Entered") answers will be shown in feedback. + +=item showAttemptPreviews (boolean, default: 1) + +Determines if the student's answer previews will be shown in feedback. + +=item showAttemptResults (boolean, default: 0) + +Determines if attempt results will be revealed in feedback. In other words, +if the student's answers are correct, incorrect, or partially correct. This +honors the value of the PG C flag. If that flag is +false, then attempt results will still not be shown. + +If this is true, then a summary of results will also be generated. The +summary will be returned in the C key of the C hash. + +=item forceShowAttemptResults (boolean, default: 0) + +If this is true then feedback will be shown with attempt results. This ignores +the PG C flag and shows attempt results in any case. +The summary will also be generated if this is true. + +=item showMessages (boolean, default: 1) + +Determines if any messages generated in answer evaluation will be shown. + +=item showCorrectAnswers (boolean, default: 0) + +Determines if correct answers will be shown. + =item answerPrefix (string, default: '') A prefix to prepend to all answer labels. Note that other prefixes may be diff --git a/macros/PG.pl b/macros/PG.pl index bf797189ad..0348357011 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -395,7 +395,7 @@ sub ADD_JS_FILE { # Some problems use jquery-ui still, and so the requestor should also load the js for that if those problems are used, # although those problems should also be rewritten to not use jquery-ui. sub load_js() { - ADD_JS_FILE('js/InputColor/color.js', 0, { defer => undef }); + ADD_JS_FILE('js/Feedback/feedback.js', 0, { defer => undef }); ADD_JS_FILE('js/Base64/Base64.js', 0, { defer => undef }); ADD_JS_FILE('js/Knowls/knowl.js', 0, { defer => undef }); ADD_JS_FILE('js/ImageView/imageview.js', 0, { defer => undef }); @@ -732,9 +732,10 @@ sub EXTEND_RESPONSE { # for radio buttons and checkboxes =head2 ENDDOCUMENT When PG problems are evaluated, the result of evaluating the entire problem is -interpreted as the return value of C. Therefore, C -must be the last executable statement of every problem. It can only appear once. -It returns a list consisting of: +interpreted as the return value of C. Furthermore, a post +processing hook is added that injects feedback into the problem text. +Therefore, C must be the last executable statement of every +problem. It can only appear once. It returns a list consisting of: =over @@ -830,6 +831,177 @@ =head2 ENDDOCUMENT =back +The post processing hook added in this method adds a feedback button for each +answer response group that when clicked opens a popover containing feedback for +the answer. A result class is also added to each C (see this +option below) for coloring answer rules via CSS. In addition visually hidden +spans are added that provide feedback for screen reader users. Each +C will be C these spans. + +When and what feedback is shown is determined by translator options described in +L as well as options described below. The hook handles +standard answer types effectively, but macros that add special answer types and +in some case problems (particularly those that use C questions with +C true) may need to help the method for proper placement of the +feedback button and other aspects of feedback. + +There are several options that can be modified, and a few different ways to make +these modifications. Unfortunately, this is perhaps a little bit complicated to +understand, and that really can not be helped. The reason for this is the +extremely loose connection between answer rules, answer labels, and answer +evaluators in PG. + +How these options are set can be controlled in three ways. + +First, an answer hash can have the C key set to a CODE +reference. If this is the case, then the subroutine referenced by this key will +be called and passed the answer hash itself, a reference to the hash of options +described below (any of which can be modified by this subroutine), and a +Mojo::DOM object containing the problem text. Note that if this method sets the +C option, then the other ways of controlling how these options +are set will not be used. + +Second, an element can be added to the DOM that contains an answer rule that has +the class C, and if that answer rule is initially chosen +to be the C and that is not set by the C +method, then this added element will replace it as the C. + +Third, data attributes may be added to elements in the DOM will affect where the +feedback button will be placed. The following data attributes are honored. + +=over + +=item * + +C: If an element in the DOM has this data +attribute and the value of this attribute is the answer name (or label), then +the element that has this data attribute will be used for the C +option described below. + +=item * + +C: If the C is not set by the +C method of the answer hash, and the C also has +this attribute, then the value of this attribute will be used for the +C option described below. + +=item * + +C: If the C is not set by the +C method of the answer hash, and the C also has +this attribute, then the value of this attribute will be used for the +C option described below. + +=back + +The options that can be modified are as follows. + +=over + +=item * + +C: This is the title that is displayed in the feedback popover for +the answers in the response group. By default this is "Preview", "Correct", +"Incorrect", or "n% correct", depending on the status of the answer and the type +of submission. Those strings are translated via C. Usually this should +not be changed, but in some cases the default status titles are not appropriate +for certain types of answers. For example, the L macros +changes this to "Ungraded" for essay answers. + +=item * + +C: This is the CSS class that is added to each answer input in the +response group. By default it is set to the empty string, "correct", +"incorrect", or "partially-correct" depending on the status of the answer and +the type of submission. + +=item * + +C: This is the bootstrap button class added to the feedback button. +By default it is "btn-info", "btn-success", "btn-danger", or "btn-warning" +depending on the status of the answer and the type of submission. + +=item * + +C: This is a string containing additional space separated CSS +classes to add to the feedback button. This is "ms-1" by default. Macros can +change this to affect positioning of the button. This generally should not be +used to change the appearance of the button. + +=item * + +C: This is a Mojo::Collection of elements in the DOM to which +the feedback C and C attribute will be added. By +default this is all elements in the DOM that have a name in the list of response +labels for the response group. Note that for radio buttons and checkboxes, only +the checked elements will be in this collection by default. + +=item * + +C: This is the element in the DOM to insert the feedback button +in or around. How the element is inserted is determined by the C +option. How this option is set is slightly complicated. First, if this option +is set by the answer hash C method, then that is used. If the +C method does not exist or does not set this option, then +initially the last C is used for this. However, if that last +C is contained in another DOM element that has the +C class, then that is used for this instead. If such a +container is not found and there is an element in the DOM that has the +C attribute set whose value is equal to the name +of this last C, then that element is used for the +C instead. Finally, if the C determined as just +described happens to be a radio button or checkbox, then the C +will instead be the parent of the radio button or checkbox (which will hopefully +be the label for that input). + +=item * + +C: The Mojo::DOM method to use to insert the feedback button +relative to the C. It can be C (insert after the +C), C (insert as the last child of +C), C (insert before C), or +C (insert as the first child of C). + +=item * + +C: This is a boolean value that is 1 by default. If true and +the display mode is HTML_MathJax, then the answer previews are wrapped in a +math/tex type script tag. + +=item * + +C: This is a boolean value that is 1 by default. If true and the +translator option C is also true, then the student's +evaluated (or "Entered") answer is shown in the feedback popover if the student +has entered an answer. + +=item * + +C: This is a boolean value that is 1 by default. If true and the +translator option C is also true, then a preview of the +student's answer is shown in the feedback popover. Most likely this should +always be true, and most likely this option (and the translator option) +shouldn't even exist! + +=item * + +C: This is a boolean value that is 1 by default. If this is true +and the translator option C is also true, then a preview of +the correct answer is shown in the feedback popover. + +=item * + +C: This is a boolean value. This should be true if a student has +answered a question, and false otherwise. By default this is set to 1 if the +responses for all answers in the response group are non-empty, and 0 otherwise. +For radio buttons and checkboxes this is if one of the inputs are checked or +not. However, for some answers a non-empty response can occur even if a student +has not answered a question (for example, this occurs for answers to questions +created with the L macro) . So macros that create answers +with responses like that should override this. + +=back + =cut sub ENDDOCUMENT { @@ -913,6 +1085,358 @@ sub ENDDOCUMENT { $PG->{flags}{solutionExists} = $solutionExists // 0; $PG->{flags}{comment} = $pgComment // ''; + if ($main::displayMode =~ /HTML/i && ($rh_envir->{showFeedback} || $rh_envir->{forceShowAttemptResults})) { + add_content_post_processor(sub { + my $problemContents = shift; + + my $numCorrect = 0; + my $numBlank = 0; + my $numEssay = 0; + + my @answerNames = keys %{ $PG->{PG_ANSWERS_HASH} }; + + for my $answerLabel (@answerNames) { + my $response_obj = $PG->{PG_ANSWERS_HASH}{$answerLabel}->response_obj; + my $ansHash = $PG->{PG_ANSWERS_HASH}{$answerLabel}{ans_eval}{rh_ans}; + + my $answerScore = $ansHash->{score} // 0; + my $isEssay = ($ansHash->{type} // '') eq 'essay'; + ++$numCorrect if $answerScore >= 1; + ++$numEssay if $isEssay; + ++$numBlank unless $isEssay || (($ansHash->{student_ans} // '') =~ /\S/ || $answerScore >= 1); + + my %options = ( + resultTitle => maketext('Preview'), + resultClass => '', + btnClass => 'btn-info', + btnAddClass => 'ms-1', + feedbackElements => Mojo::Collection->new, + insertElement => undef, + insertMethod => 'append', # Can be append, append_content, prepend, or prepend_content. + wrapPreviewInTex => defined $ansHash->{non_tex_preview} ? !$ansHash->{non_tex_preview} : 1, + showEntered => 1, + showPreview => 1, + showCorrect => 1, + answerGiven => 0 + ); + + # Determine if the student gave an answer to any of the questions in this response group and find the + # inputs associated to this response group that the correct/incorrect/partially-correct feedback classes + # will be added to. + for my $responseLabel ($response_obj->response_labels) { + my $response = $response_obj->get_response($responseLabel); + my $elements = $problemContents->find(qq{[name="$responseLabel"]}); + + if (ref($response) eq 'ARRAY') { + # This is the case of checkboxes or radios. + # Feedback classes are added only to those that are checked. + for (@$response) { $options{answerGiven} = 1 if $_->[1] =~ /^checked$/i; } + my $checked = $elements->grep(sub { + my $element = $_; + grep { $_->[0] eq $element->attr('value') && $_->[1] =~ /^checked$/i } @$response; + }); + $elements = $checked if @$checked; + } else { + $options{answerGiven} = 1 if defined $response && $response =~ /\S/; + } + push(@{ $options{feedbackElements} }, @$elements); + } + + my $showResults = ($rh_envir->{showAttemptResults} && $PG->{flags}{showPartialCorrectAnswers}) + || $rh_envir->{forceShowAttemptResults}; + + if ($showResults) { + if ($answerScore >= 1) { + $options{resultTitle} = maketext('Correct'); + $options{resultClass} = 'correct'; + $options{btnClass} = 'btn-success'; + } elsif ($answerScore == 0) { + $options{resultTitle} = maketext('Incorrect'); + $options{resultClass} = 'incorrect'; + $options{btnClass} = 'btn-danger'; + } else { + $options{resultTitle} = maketext('[_1]% correct', round($answerScore * 100)); + $options{resultClass} = 'partially-correct'; + $options{btnClass} = 'btn-warning'; + } + } + + # If a feedback_options method is provided, it can override anything set above. + $ansHash->{feedback_options}->($ansHash, \%options, $problemContents) + if ref($ansHash->{feedback_options}) eq 'CODE'; + + # Don't show the results popover if there is nothing to show. + next + unless @{ $options{feedbackElements} } + && ($answerScore > 0 + || $options{answerGiven} + || $ansHash->{ans_message} + || $rh_envir->{showCorrectAnswers}); + + # Find an element to insert the button in or around if one has not been provided. + unless ($options{insertElement}) { + # Use the last feedback element by default. + $options{insertElement} = $options{feedbackElements}->last; + + # Check to see if the last feedback element is contained in a feedback container. If so use that. + # Note that this class should not be used by PG or macros directly. It is provided for authors to + # use as an override. + my $ancestorContainer = $options{insertElement}->ancestors('.ww-feedback-container')->first; + if ($ancestorContainer) { + $options{insertElement} = $ancestorContainer; + $options{insertMethod} = 'append_content'; + } else { + # Otherwise check to see if the last feedback element has a special element to attach the + # button to defined in its data attributes, and if so use that instead. + $options{insertElement} = $problemContents->at( + '[data-feedback-insert-element="' . $options{insertElement}->attr('name') . '"]') + || $options{insertElement}; + } + + # For radio or checkbox answers place the feedback button after the label by default. + if (lc($options{insertElement}->attr('type')) =~ /^(radio|checkbox)$/) { + $options{btnAddClass} = 'ms-3'; + $options{insertElement} = $options{insertElement}->parent; + } + + # Check to see if this element has details for placement defined in its data attributes. + $options{btnAddClass} = $options{insertElement}->attr->{'data-feedback-btn-add-class'} + if $options{insertElement} && $options{insertElement}->attr->{'data-feedback-btn-add-class'}; + $options{insertMethod} = $options{insertElement}->attr->{'data-feedback-insert-method'} + if $options{insertElement} && $options{insertElement}->attr->{'data-feedback-insert-method'}; + } + + # Add the correct/incorrect/partially-correct class and + # aria-described by attribute to the feedback elements. + for (@{ $options{feedbackElements} }) { + $_->attr(class => join(' ', $options{resultClass}, $_->attr->{class} || ())) + if $options{resultClass}; + $_->attr('aria-describedby' => "ww-feedback-$answerLabel"); + } + + sub previewAnswer { + my ($preview, $wrapPreviewInTex, $fallback) = @_; + + return $fallback unless defined $preview && $preview =~ /\S/; + + if ($main::displayMode eq 'HTML' || !$wrapPreviewInTex) { + return $preview; + } elsif ($main::displayMode eq 'HTML_dpng') { + return $rh_envir->{imagegen}->add($preview); + } elsif ($main::displayMode eq 'HTML_MathJax') { + return Mojo::DOM->new_tag('script', type => 'math/tex; mode=display', sub {$preview}) + ->to_string; + } + } + + sub feedbackLine { + my ($title, $line, $class) = @_; + $class //= ''; + return '' unless defined $line && $line =~ /\S/; + 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 {$line}); + } + + my $answerPreview = previewAnswer($ansHash->{preview_latex_string}, $options{wrapPreviewInTex}); + + # Create the screen reader only span holding the aria description, create the feedback button and + # popover, and insert the button at the requested location. + my $feedback = ( + # Add a visually hidden span to provide feedback to screen reader users immediately. + $showResults || ($rh_envir->{showMessages} && $ansHash->{ans_message}) + ? Mojo::DOM->new_tag( + 'span', + class => 'visually-hidden', + id => "ww-feedback-$answerLabel", + sub { + ($showResults ? Mojo::DOM->new_tag('span', $options{resultTitle}) : '') + . ($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 $options{btnClass} $options{btnAddClass}", + 'aria-label' => $options{resultTitle}, + data => { + $showResults && $options{resultTitle} ? (bs_title => $options{resultTitle}) : (), + bs_toggle => 'popover', + bs_trigger => 'click', + bs_placement => 'bottom', + bs_html => 'true', + bs_custom_class => join(' ', 'ww-feedback-popover', $options{resultClass} || ()), + bs_fallback_placements => '[]', + bs_content => Mojo::DOM->new_tag( + 'div', + id => "$answerLabel-feedback", + sub { + Mojo::DOM->new_tag( + 'div', + class => 'card', + sub { + ($rh_envir->{showAttemptAnswers} && $options{showEntered} + ? feedbackLine(maketext('You Entered'), $ansHash->{student_ans}) + : '') + . ( + $rh_envir->{showAttemptPreviews} && $options{showPreview} + ? feedbackLine( + maketext('Preview of Your Answer'), + ( + (defined $answerPreview && $answerPreview =~ /\S/) + || $rh_envir->{showAttemptAnswers} + ? $answerPreview + : $ansHash->{student_ans} + ) + ) + : '' + ) + . ( + $rh_envir->{showCorrectAnswers} && $options{showCorrect} + ? feedbackLine( + maketext('Correct Answer'), + previewAnswer( + $ansHash->{correct_ans_latex_string}, + $options{wrapPreviewInTex}, + $ansHash->{correct_ans} + ) + ) + : '' + ) + . ( + ($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 ($options{insertElement} && $options{insertElement}->can($options{insertMethod})) { + my $insertMethod = $options{insertMethod}; + $options{insertElement}->$insertMethod($feedback); + } + } + + # Generate the result summary if results are being shown. + # FIXME: This is set up to occur when it did previously. That is it ignores the value of + # $PG->{flags}{showPartialCorrectAnswers}. It seems that is incorrect, as it makes that setting rather + # pointless. The summary still reveals if the answer is correct or not. + if ($rh_envir->{showAttemptResults} || $rh_envir->{forceShowAttemptResults}) { + my @summary; + + if (@answerNames == 1) { + if ($numCorrect == 1) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsWithoutError mb-2', + maketext('The answer is correct.') + ) + ); + } elsif ($numEssay) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsAlert mb-2', + maketext('The answer will be graded later.') + ) + ); + } elsif ($numBlank) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsAlert mb-2', + maketext('The question has not been answered.') + ) + ); + } else { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsWithError mb-2', + maketext('The answer is NOT correct.') + ) + ); + } + } else { + if ($numCorrect + $numEssay == @answerNames) { + if ($numEssay) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsWithoutError mb-2', + maketext('All of the gradeable answers are correct.') + ) + ); + } else { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsWithoutError mb-2', + maketext('All of the answers are correct.') + ) + ); + } + } elsif ($numBlank + $numEssay + $numCorrect != @answerNames) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsWithError mb-2', + maketext( + '[_1] of the answers [plural,_1,is,are] NOT correct.', + @answerNames - $numBlank - $numCorrect - $numEssay + ) + ) + ); + } + if ($numBlank) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsAlert mb-2', + maketext( + '[quant,_1,of the questions remains,of the questions remain] unanswered.', + $numBlank + ) + ) + ); + } + if ($numEssay) { + push( + @summary, + Mojo::DOM->new_tag( + 'div', + class => 'ResultsAlert mb-2', + maketext('[_1] of the answers will be graded later.', $numEssay) + ) + ); + } + } + $PG->{result_summary} = join('', @summary); + } + }); + } + # 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 67c46f4bae..1670ff8491 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -267,126 +267,92 @@ =head2 Answer blank macros: =cut -sub labeled_ans_rule { # syntactic sugar for NAMED_ANS_RULE - my ($name, $col) = @_; - $col = 20 unless not_null($col); - NAMED_ANS_RULE($name, $col); +# Alias for NAMED_ANS_RULE +sub labeled_ans_rule { + my ($name, $col, %options) = @_; + return NAMED_ANS_RULE($name, $col, %options); } sub NAMED_ANS_RULE { - my $name = shift; - my $col = shift; - my %options = @_; - $col = 20 unless not_null($col); - my $answer_value = ''; - $answer_value = ${$inputs_ref}{$name} if defined(${$inputs_ref}{$name}); - - #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 - $rh_sticky_answers->{$name} = \@answers; - # store the rest -- because this stores to a main:: variable - # it must be evaluated at run time - $answer_value = '' unless defined($answer_value); + my ($name, $col, %options) = @_; + $col ||= 20; + my $answer_value = $inputs_ref->{$name} // ''; + $answer_value = [ split("\0", $answer_value) ] if $answer_value =~ /\0/; + + 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. } - $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!!, + return MODES( + TeX => "{\\answerRule[$name]{$tcol}}", + # Note: codeshard is used in the css to identify input elements that come from pg. + HTML => tag( + 'span', + class => 'text-nowrap', + 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!! ); } sub NAMED_HIDDEN_ANS_RULE { - # this is used to hold information being passed into and out of applets - # -- preserves state -- identical to NAMED_ANS_RULE except input type "hidden" my ($name, $col) = @_; - $col = 20 unless not_null($col); - my $answer_value = ''; - $answer_value = ${$inputs_ref}{$name} if defined(${$inputs_ref}{$name}); - 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 - $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); + $col ||= 20; + my $answer_value = $inputs_ref->{$name} // ''; + $answer_value = [ split("\0", $answer_value) ] if $answer_value =~ /\0/; + + 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. } - $answer_value =~ s/\s+/ /g; ## remove excessive whitespace from student answer + $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); + $name = RECORD_ANS_NAME($name, $answer_value); - 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}!, - HTML => qq!! - . qq!!, + return MODES( + TeX => "{\\answerRule[$name]{$tcol}}", + HTML => tag('input', type => 'hidden', name => $name, id => $name, value => $answer_value) + . tag('input', type => 'hidden', name => "previous_$name", id => "previous_$name", value => $answer_value), PTX => '', ); } -sub NAMED_ANS_RULE_OPTION { # deprecated - &NAMED_ANS_RULE_EXTENSION; -} - sub NAMED_ANS_RULE_EXTENSION { - my $name = shift; # this is the name of the response item - my $col = shift; - my %options = @_; + my ($name, $col, %options) = @_; - my $label; - if (defined($options{aria_label})) { - $label = $options{aria_label}; - } else { - $label = generate_aria_label($name); - } # $answer_group_name is the name of the parent answer group # the group name is usually the same as the answer blank name # when there is only one answer blank. - my $answer_group_name = $options{answer_group_name} // ''; unless ($answer_group_name) { WARN_MESSAGE( @@ -395,31 +361,36 @@ sub NAMED_ANS_RULE_EXTENSION { usually the same as the answer blank name. Answer blank name: $name" ); } - # warn "from named answer rule extension in PGbasic answer_group_name: |$answer_group_name|"; - my $answer_value = ''; - $answer_value = ${$inputs_ref}{$name} if defined(${$inputs_ref}{$name}); - if (defined($rh_sticky_answers->{$name})) { - $answer_value = shift(@{ $rh_sticky_answers->{$name} }); - $answer_value = '' unless defined($answer_value); + + my $answer_value = $inputs_ref->{$name} // ''; + if (defined $rh_sticky_answers->{$name}) { + $answer_value = shift(@{ $rh_sticky_answers->{$name} }) // ''; } - $answer_value =~ s/\s+/ /g; ## remove excessive whitespace from student answer - # warn "from NAMED_ANSWER_RULE_EXTENSION in PGbasic: - # answer_group_name: |$answer_group_name| name: |$name| answer value: |$answer_value|"; - INSERT_RESPONSE($answer_group_name, $name, $answer_value) - ; #FIXME hack -- this needs more work to decide how to make it work - $answer_value = encode_pg_and_html($answer_value); + $answer_value =~ s/\s+/ /g; # remove excessive whitespace from student answer - 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}\n\n\\end{rawhtml}\n!, - HTML => qq!! - . qq!!, + INSERT_RESPONSE($answer_group_name, $name, $answer_value); + + my $tcol = $col / 2 > 3 ? $col / 2 : 3; # get max + $tcol = $tcol < 40 ? $tcol : 40; # get min + + return MODES( + TeX => "{\\answerRule[$name]{$tcol}}", + HTML => 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", id => "previous_$name", value => $answer_value), PTX => qq!!, ); } @@ -427,46 +398,43 @@ sub NAMED_ANS_RULE_EXTENSION { sub ANS_RULE { #deprecated my ($number, $col) = @_; my $name = NEW_ANS_NAME($number); - NAMED_ANS_RULE($name, $col); + return NAMED_ANS_RULE($name, $col); } sub NAMED_ANS_BOX { - my $name = shift; - my $row = shift; - my $col = shift; - my %options = @_; - - $row = 10 unless defined($row); - $col = 80 unless defined($col); + my ($name, $row, $col, %options) = @_; + $row //= 10; + $col //= 80; my $height = .07 * $row; - my $answer_value = ''; - $answer_value = $inputs_ref->{$name} if defined($inputs_ref->{$name}); - $name = RECORD_ANS_NAME($name, $answer_value); - my $label; - if (defined($options{aria_label})) { - $label = $options{aria_label}; - } else { - $label = generate_aria_label($name); - } - # try to escape HTML entities to deal with xss stuff - $answer_value = encode_pg_and_html($answer_value); - my $out = MODES( - TeX => qq!\\vskip $height in \\hrulefill\\quad !, - Latex2HTML => qq!\\begin{rawhtml}\\end{rawhtml}!, - HTML => qq!! - . qq!!, + my $answer_value = $inputs_ref->{$name} // ''; + $name = RECORD_ANS_NAME($name, $answer_value); + my $label = $options{aria_label} // generate_aria_label($name); + + return MODES( + TeX => qq!\\vskip $height in \\hrulefill\\quad !, + HTML => tag( + 'span', + class => 'text-nowrap', + tag( + 'textarea', + name => $name, + id => $name, + rows => $row, + cols => $col, + aria_label => $label, + encode_pg_and_html($answer_value) + ) + ) + . tag('input', type => 'hidden', name => "previous_$name", value => $answer_value), PTX => '', ); - $out; } sub ANS_BOX { #deprecated my ($number, $row, $col) = @_; my $name = NEW_ANS_NAME(); - NAMED_ANS_BOX($name, $row, $col); + return NAMED_ANS_BOX($name, $row, $col); } sub NAMED_ANS_RADIO { @@ -483,9 +451,7 @@ sub NAMED_ANS_RADIO { INSERT_RESPONSE($options{answer_group_name}, $name, { $value => $checked }) if $extend; return MODES( - TeX => qq!\\item{$tag}\n!, - Latex2HTML => - qq!\\begin{rawhtml}\n\\end{rawhtml}$tag!, + TeX => qq!\\item{$tag}\n!, HTML => tag( 'label', tag( @@ -502,11 +468,6 @@ sub NAMED_ANS_RADIO { ), PTX => '
  • ' . "$tag" . '
  • ' . "\n", ); - -} - -sub NAMED_ANS_RADIO_OPTION { #deprecated - &NAMED_ANS_RADIO_EXTENSION; } sub NAMED_ANS_RADIO_EXTENSION { @@ -522,9 +483,7 @@ sub NAMED_ANS_RADIO_EXTENSION { EXTEND_RESPONSE($options{answer_group_name} // $name, $name, $value, $checked); return MODES( - TeX => qq!\\item{$tag}\n!, - Latex2HTML => - qq!\\begin{rawhtml}\n\\end{rawhtml}$tag!, + TeX => qq!\\item{$tag}\n!, HTML => tag( 'label', tag( @@ -541,58 +500,45 @@ sub NAMED_ANS_RADIO_EXTENSION { ), PTX => '
  • ' . "$tag" . '
  • ' . "\n", ); - } sub NAMED_ANS_RADIO_BUTTONS { - my $name = shift; - my $value = shift; - my $tag = shift; + my ($name, $value, $tag, @buttons) = @_; - my @out = (); + my @out; push(@out, NAMED_ANS_RADIO($name, $value, $tag)); - my @buttons = @_; - my $label = generate_aria_label($name); - my $count = 2; + my $label = generate_aria_label($name); + my $count = 2; while (@buttons) { $value = shift @buttons; $tag = shift @buttons; - push(@out, NAMED_ANS_RADIO_OPTION($name, $value, $tag, aria_label => $label . "option $count ")); + push(@out, NAMED_ANS_RADIO_EXTENSION($name, $value, $tag, aria_label => $label . "option $count ")); $count++; } - (wantarray) ? @out : join(" ", @out); + return wantarray ? @out : join(" ", @out); } sub ANS_RADIO { - my $number = shift; - my $value = shift; - my $tag = shift; - my $name = NEW_ANS_NAME(); - NAMED_ANS_RADIO($name, $value, $tag); + my ($number, $value, $tag) = @_; + return NAMED_ANS_RADIO(NEW_ANS_NAME(), $value, $tag); } sub ANS_RADIO_OPTION { - my $number = shift; - my $value = shift; - my $tag = shift; - my $name = ANS_NUM_TO_NAME($number); - NAMED_ANS_RADIO_OPTION($name, $value, $tag); + my ($number, $value, $tag) = @_; + return NAMED_ANS_RADIO_EXTENSION(ANS_NUM_TO_NAME($number), $value, $tag); } sub ANS_RADIO_BUTTONS { - my $number = shift; - my $value = shift; - my $tag = shift; + my ($number, $value, $tag, @buttons) = @_; - my @out = (); + my @out; push(@out, ANS_RADIO($number, $value, $tag)); - my @buttons = @_; while (@buttons) { $value = shift @buttons; $tag = shift @buttons; push(@out, ANS_RADIO_OPTION($number, $value, $tag)); } - (wantarray) ? @out : join(" ", @out); + return wantarray ? @out : join(" ", @out); } ############################################## @@ -682,10 +628,7 @@ sub NAMED_ANS_CHECKBOX { INSERT_RESPONSE($options{answer_group_name}, $name, { $value => $checked }) if $extend; return MODES( - TeX => qq!\\item{$tag}\n!, - Latex2HTML => qq!\\begin{rawhtml}\n! - . qq!! - . qq!\\end{rawhtml}$tag!, + TeX => qq!\\item{$tag}\n!, HTML => tag( 'label', tag( @@ -718,10 +661,7 @@ sub NAMED_ANS_CHECKBOX_OPTION { EXTEND_RESPONSE($options{answer_group_name} // $name, $name, $value, $checked); return MODES( - TeX => qq!\\item{$tag}\n!, - Latex2HTML => qq!\\begin{rawhtml}\n! - . qq!! - . qq!\\end{rawhtml}$tag!, + TeX => qq!\\item{$tag}\n!, HTML => tag( 'label', tag( @@ -741,15 +681,12 @@ sub NAMED_ANS_CHECKBOX_OPTION { } sub NAMED_ANS_CHECKBOX_BUTTONS { - my $name = shift; - my $value = shift; - my $tag = shift; + my ($name, $value, $tag, @buttons) = @_; - my @out = (); + my @out; push(@out, NAMED_ANS_CHECKBOX($name, $value, $tag)); - my $label = generate_aria_label($name); - my $count = 2; - my @buttons = @_; + my $label = generate_aria_label($name); + my $count = 2; while (@buttons) { $value = shift @buttons; $tag = shift @buttons; @@ -757,58 +694,42 @@ sub NAMED_ANS_CHECKBOX_BUTTONS { $count++; } - (wantarray) ? @out : join(" ", @out); + return wantarray ? @out : join(" ", @out); } sub ANS_CHECKBOX { - my $number = shift; - my $value = shift; - my $tag = shift; - my $name = NEW_ANS_NAME(); - - NAMED_ANS_CHECKBOX($name, $value, $tag); + my ($number, $value, $tag) = @_; + return NAMED_ANS_CHECKBOX(NEW_ANS_NAME(), $value, $tag); } sub ANS_CHECKBOX_OPTION { - my $number = shift; - my $value = shift; - my $tag = shift; - my $name = ANS_NUM_TO_NAME($number); - - NAMED_ANS_CHECKBOX_OPTION($name, $value, $tag); + my ($number, $value, $tag) = @_; + return NAMED_ANS_CHECKBOX_OPTION(ANS_NUM_TO_NAME($number), $value, $tag); } sub ANS_CHECKBOX_BUTTONS { - my $number = shift; - my $value = shift; - my $tag = shift; + my ($number, $value, $tag, @buttons) = @_; - my @out = (); + my @out; push(@out, ANS_CHECKBOX($number, $value, $tag)); - my @buttons = @_; while (@buttons) { $value = shift @buttons; $tag = shift @buttons; push(@out, ANS_CHECKBOX_OPTION($number, $value, $tag)); } - (wantarray) ? @out : join(" ", @out); + return wantarray ? @out : join(" ", @out); } sub ans_rule { - my $len = shift; # gives the optional length of the answer blank - $len = 20 unless $len; - my $name = NEW_ANS_NAME(); # increment is done internally - NAMED_ANS_RULE($name, $len); + my $len = shift || 20; + return NAMED_ANS_RULE(NEW_ANS_NAME(), $len); } sub ans_rule_extension { - my $len = shift; - $len = 20 unless $len; - # warn "ans_rule_extension may be misnumbering the answers"; - my $name = NEW_ANS_NAME($$r_ans_rule_count); # don't update the answer name - NAMED_ANS_RULE($name, $len); + my $len = shift || 20; + return NAMED_ANS_RULE(NEW_ANS_NAME(), $len); } sub ans_radio_buttons { @@ -821,12 +742,17 @@ sub ans_radio_buttons { } elsif ($displayMode eq 'PTX') { $radio_buttons[0] = '' . "\n" . $radio_buttons[0]; $radio_buttons[$#radio_buttons] .= ''; + } else { + $radio_buttons[0] = + qq{
    $radio_buttons[0]}; + $radio_buttons[-1] .= "
    "; } - (wantarray) ? @radio_buttons : join(" ", @radio_buttons); + return wantarray ? @radio_buttons : join(" ", @radio_buttons); } -#added 6/14/2000 by David Etlinger sub ans_checkbox { my $name = NEW_ANS_NAME(); my @checkboxes = NAMED_ANS_CHECKBOX_BUTTONS($name, @_); @@ -837,92 +763,74 @@ sub ans_checkbox { } elsif ($displayMode eq 'PTX') { $checkboxes[0] = '' . "\n" . $checkboxes[0]; $checkboxes[$#checkboxes] .= ''; + } else { + $checkboxes[0] = + qq{
    $checkboxes[0]}; + $checkboxes[-1] .= '
    '; } - (wantarray) ? @checkboxes : join(" ", @checkboxes); + return wantarray ? @checkboxes : join(" ", @checkboxes); } -## define a version of ans_rule which will work inside TeX math mode or display math mode -- at least for tth mode. -## This is great for displayed fractions. -## This will not work with latex2HTML mode since it creates gif equations. - +# define a version of ans_rule which will work inside TeX math mode or display math mode -- at least for tth mode. +# This is great for displayed fractions. sub tex_ans_rule { - my $len = shift; - $len = 20 unless $len; - my $name = NEW_ANS_NAME(); - my $answer_rule = NAMED_ANS_RULE($name, $len); # we don't want to create three answer rules in different modes. - my $out = MODES( - 'TeX' => $answer_rule, - 'Latex2HTML' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', - 'HTML_dpng' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule, - 'PTX' => 'Answer boxes cannot be placed inside typeset equations', + my $len = shift || 20; + # Call NAMED_ANS_RULE before MODES. Otherwise three answer rules will be created. + my $answer_rule = NAMED_ANS_RULE(NEW_ANS_NAME(), $len); + return MODES( + 'TeX' => $answer_rule, + 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', + 'HTML_dpng' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); - - $out; } sub tex_ans_rule_extension { - my $len = shift; - $len = 20 unless $len; - # warn "tex_ans_rule_extension may be missnumbering the answer"; - my $name = NEW_ANS_NAME($$r_ans_rule_count); - my $answer_rule = NAMED_ANS_RULE($name, $len); # we don't want to create three answer rules in different modes. - my $out = MODES( - 'TeX' => $answer_rule, - 'Latex2HTML' => '\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', - 'HTML_dpng' => '\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule, - 'PTX' => 'Answer boxes cannot be placed inside typeset equations', + my $len = shift || 20; + # Call NAMED_ANS_RULE before MODES. Otherwise three answer rules will be created. + my $answer_rule = NAMED_ANS_RULE(NEW_ANS_NAME($$r_ans_rule_count), $len); + return MODES( + 'TeX' => $answer_rule, + 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', + 'HTML_dpng' => '\fbox{Answer boxes cannot be placed inside typeset equations}', + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); - - $out; } -# still needs some cleanup. + sub NAMED_TEX_ANS_RULE { - my $name = shift; - my $len = shift; - $len = 20 unless $len; - my $answer_rule = NAMED_ANS_RULE($name, $len); # we don't want to create three answer rules in different modes. - my $out = MODES( - 'TeX' => $answer_rule, - 'Latex2HTML' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', - 'HTML_dpng' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule, - 'PTX' => 'Answer boxes cannot be placed inside typeset equations', + my ($name, $len) = @_; + # Call NAMED_ANS_RULE before MODES. Otherwise three answer rules will be created. + my $answer_rule = NAMED_ANS_RULE($name, $len || 20); + return MODES( + 'TeX' => $answer_rule, + 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', + 'HTML_dpng' => '\\fbox{Answer boxes cannot be placed inside typeset equations}', + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); - - $out; } sub NAMED_TEX_ANS_RULE_EXTENSION { - my $name = shift; - my $len = shift; - $len = 20 unless $len; - my $answer_rule = - NAMED_ANS_RULE_EXTENSION($name, $len); # we don't want to create three answer rules in different modes. - my $out = MODES( - 'TeX' => $answer_rule, - 'Latex2HTML' => '\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', - 'HTML_dpng' => '\fbox{Answer boxes cannot be placed inside typeset equations}', - 'HTML' => $answer_rule, - 'PTX' => 'Answer boxes cannot be placed inside typeset equations', + my ($name, $len) = @_; + # Call NAMED_ANS_RULE before MODES. Otherwise three answer rules will be created. + my $answer_rule = NAMED_ANS_RULE_EXTENSION($name, $len || 20); + return MODES( + 'TeX' => $answer_rule, + 'HTML_tth' => '\\begin{rawhtml} ' . $answer_rule . '\\end{rawhtml}', + 'HTML_dpng' => '\fbox{Answer boxes cannot be placed inside typeset equations}', + 'HTML' => $answer_rule, + 'PTX' => 'Answer boxes cannot be placed inside typeset equations', ); - - $out; } sub ans_box { - my $row = shift; - my $col = shift; - $row = 5 unless $row; - $col = 80 unless $col; - my $name = NEW_ANS_NAME(); - NAMED_ANS_BOX($name, $row, $col); + my ($row, $col) = @_; + return NAMED_ANS_BOX(NEW_ANS_NAME(), $row || 5, $col || 80); } # this is legacy code; use ans_checkbox instead @@ -932,60 +840,46 @@ sub checkbox { } sub NAMED_POP_UP_LIST { - my $name = shift; - my @list = @_; - if (ref($list[0]) eq 'ARRAY') { - my @list1 = @{ $list[0] }; - @list = map { $_ => $_ } @list1; - } - my $moodle_prefix = ($envir{use_opaque_prefix}) ? "%%IDPREFIX%%" : ''; + my ($name, @list) = @_; + + my %options = ref($list[0]) eq 'ARRAY' ? (map { $_ => $_ } @{ $list[0] }) : @list; + + my $moodle_prefix = $envir{use_opaque_prefix} ? '%%IDPREFIX%%' : ''; + + my $answer_value = $inputs_ref->{$name} // ''; + $name = RECORD_ANS_NAME($name, $answer_value); - my $answer_value = ''; - $answer_value = ${$inputs_ref}{$name} if defined(${$inputs_ref}{$name}); - my $out = ""; if ($displayMode eq 'HTML_MathJax' || $displayMode eq 'HTML_dpng' || $displayMode eq 'HTML' - || $displayMode eq 'HTML_tth' - || $displayMode eq 'HTML_jsMath' - || $displayMode eq 'HTML_asciimath' - || $displayMode eq 'HTML_LaTeXMathML' - || $displayMode eq 'HTML_img') + || $displayMode eq 'HTML_tth') { - $out = qq!\n"; - } elsif ($displayMode eq "Latex2HTML") { - $out = qq! \\begin{rawhtml}\\end{rawhtml}\n"; + return tag( + 'span', + class => 'text-nowrap', + tag( + 'select', + class => 'pg-select', + name => "$moodle_prefix$name", + id => "$moodle_prefix$name", + size => 1, + join( + '', + map { tag('option', value => $_, $_ eq $answer_value ? (selected => undef) : (), $options{$_}) } + keys %options + ) + ) + ); } elsif ($displayMode eq "TeX") { - $out .= "\\fbox{?}"; + return "\\fbox{?}"; } elsif ($displayMode eq "PTX") { - $out = '' . "\n"; - my $i; - foreach ($i = 0; $i < @list; $i = $i + 2) { - $out .= '
  • ' . $list[ $i + 1 ] . '
  • ' . "\n"; - } - $out .= '
    '; + return '' . "\n" . join('', map {"
  • $options{$_}
  • \n"} keys %options) . '
    '; } - $name = RECORD_ANS_NAME($name, $answer_value); # record answer name - $out; } sub pop_up_list { my @list = @_; - my $name = NEW_ANS_NAME(); # get new answer name - NAMED_POP_UP_LIST($name, @list); + return NAMED_POP_UP_LIST(NEW_ANS_NAME(), @list); } =head2 answer_matrix @@ -1007,55 +901,42 @@ =head2 answer_matrix =cut sub answer_matrix { - my $m = shift; - my $n = shift; - my $width = shift; - my @options = @_; - my @array = (); + my ($m, $n, $width, @options) = @_; + my @array; for (my $i = 0; $i < $m; $i += 1) { - my @row_array = (); + my @row_array; for (my $i = 0; $i < $n; $i += 1) { push @row_array, ans_rule($width); } - my $r_row_array = \@row_array; - push @array, $r_row_array; + push @array, \@row_array; } # display_matrix hasn't been loaded into the cache safe compartment # so we need to refer to the subroutine in this way to make # sure that main is defined correctly. my $ra_local_display_matrix = PG_restricted_eval(q!\&main::display_matrix!); - &$ra_local_display_matrix(\@array, @options); + return &$ra_local_display_matrix(\@array, @options); } sub NAMED_ANS_ARRAY_EXTENSION { - my $name = shift; - my $col = shift; - my %options = @_; - $col = 20 unless $col; - my $answer_value = ''; + my ($name, $col, %options) = @_; + $col ||= 20; - $answer_value = ${$inputs_ref}{$name} if defined(${$inputs_ref}{$name}); - if ($answer_value =~ /\0/) { - my @answers = split("\0", $answer_value); - $answer_value = shift(@answers); - $answer_value = '' unless defined($answer_value); - } elsif (ref($answer_value) eq 'ARRAY') { - my @answers = @{$answer_value}; + my $answer_value = $inputs_ref->{$name} // ''; + $answer_value = [ split("\0", $answer_value) ] if $answer_value =~ /\0/; + + if (ref($answer_value) eq 'ARRAY') { + my @answers = @$answer_value; $answer_value = shift(@answers); $answer_value = '' unless defined($answer_value); } - my $label; - if (defined($options{aria_label})) { - $label = $options{aria_label}; - } else { - $label = generate_aria_label($name); - } + my $label = $options{aria_label} // generate_aria_label($name); - # warn "ans_label $options{ans_label} $name $answer_value"; - my $answer_group_name; # the name of the answer evaluator controlling this collection of responses. - # catch deprecated use of ans_label to pass answer_group_name + # the name of the answer evaluator controlling this collection of responses. + my $answer_group_name; + + # catch deprecated use of ans_label to pass answer_group_name if (defined($options{ans_label})) { WARN_MESSAGE( "Error in NAMED_ANS_ARRAY_EXTENSION: the answer group name should be passed in ", @@ -1068,76 +949,79 @@ sub NAMED_ANS_ARRAY_EXTENSION { if (defined($options{answer_group_name})) { $answer_group_name = $options{answer_group_name}; } + if ($answer_group_name) { INSERT_RESPONSE($options{answer_group_name}, $name, $answer_value); } else { WARN_MESSAGE("Error: answer_group_name must be defined for $name"); } - $answer_value = encode_pg_and_html($answer_value); - 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}\n\n\\end{rawhtml}\n!, - HTML => qq!!, + return MODES( + TeX => "{\\answerRule[$name]{$tcol}}", + HTML => tag( + 'input', + type => 'text', + size => $col, + name => $name, + id => $name, + class => 'codeshard', + aria_label => $label, + autocomplete => 'off', + autocapitalize => 'off', + spellcheck => 'false', + value => $answer_value + ), PTX => qq!!, ); } sub ans_array { - my $m = shift; - my $n = shift; - my $col = shift; - $col = 20 unless $col; - my $ans_label = NEW_ANS_NAME(); - my $num = ans_rule_count(); - my @options = @_; - my @array = (); - my $answer_value = ""; - my @response_list = (); - my $name; + my ($m, $n, $col, @options) = @_; + $col ||= 20; + + my $ans_label = NEW_ANS_NAME(); + my $num = ans_rule_count(); + my @array; $main::vecnum = -1; CLEAR_RESPONSES($ans_label); for (my $i = 0; $i < $n; $i += 1) { - $name = NEW_ANS_ARRAY_NAME_EXTENSION($num, 0, $i); - $array[0][$i] = NAMED_ANS_ARRAY_EXTENSION($name, $col, ans_label => $ans_label); + $array[0][$i] = + NAMED_ANS_ARRAY_EXTENSION(NEW_ANS_ARRAY_NAME_EXTENSION($num, 0, $i), $col, ans_label => $ans_label); } for (my $j = 1; $j < $m; $j += 1) { for (my $i = 0; $i < $n; $i += 1) { - $name = NEW_ANS_ARRAY_NAME_EXTENSION($num, $j, $i); - $array[$j][$i] = NAMED_ANS_ARRAY_EXTENSION($name, $col, ans_label => $ans_label); + $array[$j][$i] = + NAMED_ANS_ARRAY_EXTENSION(NEW_ANS_ARRAY_NAME_EXTENSION($num, $j, $i), $col, ans_label => $ans_label); } } my $ra_local_display_matrix = PG_restricted_eval(q!\&main::display_matrix!); - &$ra_local_display_matrix(\@array, @options); + return &$ra_local_display_matrix(\@array, @options); } sub ans_array_extension { - my $m = shift; - my $n = shift; - my $col = shift; - $col = 20 unless $col; - my $num = ans_rule_count(); #hack -- ans_rule_count is updated after being used - my @options = @_; - my @response_list = (); - my $name; - my @array = (); + my ($m, $n, $col, @options) = @_; + $col ||= 20; + + my $num = ans_rule_count(); # hack -- ans_rule_count is updated after being used + my @array; my $ans_label = $main::PG->new_label($num); for (my $j = 0; $j < $m; $j += 1) { for (my $i = 0; $i < $n; $i += 1) { - $name = NEW_ANS_ARRAY_NAME_EXTENSION($num, $j, $i); - $array[$j][$i] = NAMED_ANS_ARRAY_EXTENSION($name, $col, answer_group_name => $ans_label, @options); + $array[$j][$i] = NAMED_ANS_ARRAY_EXTENSION( + NEW_ANS_ARRAY_NAME_EXTENSION($num, $j, $i), $col, + answer_group_name => $ans_label, + @options + ); } } my $ra_local_display_matrix = PG_restricted_eval(q!\&main::display_matrix!); - &$ra_local_display_matrix(\@array, @options); + return &$ra_local_display_matrix(\@array, @options); } # end answer blank macros @@ -2624,13 +2508,10 @@ sub knowlLink { } sub iframe { - my $url = shift; - my %options = @_; # keys: height, width, id, name - my $formatted_options = join(" ", map {qq!$_ = "$options{$_}"!} (keys %options)); - return "$BBOLD\[ broken link: $url \] $EBOLD" unless defined($url); + my ($url, %attributes) = @_; MODES( TeX => "\\framebox{" . protect_underbar($url) . "}\n", - HTML => qq!\n \n!, + HTML => tag('iframe', src => $url, %attributes), PTX => '', ); } diff --git a/macros/core/PGessaymacros.pl b/macros/core/PGessaymacros.pl index 453e1ce8af..4df0fb3387 100644 --- a/macros/core/PGessaymacros.pl +++ b/macros/core/PGessaymacros.pl @@ -13,9 +13,6 @@ # Artistic License for more details. ################################################################################ -# FIXME TODO: -# Document and maybe split out: filters, graders, utilities - =head1 NAME PGessaymacros.pl - Macros for building answer evaluators. @@ -26,20 +23,23 @@ =head2 SYNPOSIS essay_cmp() -Answer Boxes +Answer Boxes: essay_box() -To use essay answers just put an C into your problem file wherever you want the -input box to go and then use C for the corresponding checker. You will then need -grade the problem manually. The grader can be found in the "Detail Set List". +To use essay answers call C in your problem file wherever you want +the input box to go, and then use C for the corresponding checker. +You will then need to grade the problem manually. explanation_box() -Like an C, except can be turned off at a configuration level. Intended for two-part -questions where the first answer is automatically assessible, and the second part is an explanation -or "showing your work". An instructor may want to turn these off to use the problem but without the -manual grading component. These necessarily supply their own C. +Like an C, except that it can be turned off at a configuration +level. This is intended for two-part questions where the first answer is +automatically assessible, and the second part is an explanation or "show your +work" type answer. An instructor may want to turn these off to use the problem +but without the manual grading component. These necessarily supply their own +C. + =cut sub _PGessaymacros_init { @@ -47,114 +47,118 @@ sub _PGessaymacros_init { } sub essay_cmp { - - my $self = shift; - my $ans = new AnswerEvaluator; + my (%options) = @_; + my $ans = AnswerEvaluator->new; $ans->ans_hash( - type => "essay", - correct_ans => "Undefined", - correct_value => $self, - @_, + type => 'essay', + correct_ans => 'Undefined', + correct_value => '', + scaffold_force => 1, + feedback_options => sub { + my ($ansHash, $options) = @_; + $options->{resultTitle} = maketext('Ungraded'); + $options->{resultClass} = ''; + $options->{insertMethod} = 'append_content'; + $options->{btnClass} = 'btn-info'; + $options->{btnAddClass} = ''; + $options->{wrapPreviewInTex} = 0; + $options->{showEntered} = 0; # Suppress output of the feedback entered answer. + $options->{showCorrect} = 0; # Suppress output of the feedback correct answer. + }, + %options, ); $ans->install_evaluator(sub { - my $student = shift; - my %response_options = @_; + my $ans_hash = shift; - $student->{original_student_ans} = - (defined $student->{original_student_ans}) ? $student->{original_student_ans} : ''; + $ans_hash->{original_student_ans} //= ''; + $ans_hash->{_filter_name} = 'Essay Check'; + $ans_hash->{score} = 0; + $ans_hash->{ans_message} = maketext('This answer will be graded at a later time.'); + $ans_hash->{preview_text_string} = ''; - my $answer_value = $student->{original_student_ans}; - - # always returns false but stuff should check for the essay flag and avoid the red highlighting - loadMacros("contextTypeset.pl"); + loadMacros('contextTypeset.pl'); my $oldContext = Context(); - Context("Typeset"); - $answer_value = EV3P({ processCommands => 0, processVariables => 0 }, text2PG($answer_value)); - + Context('Typeset'); + $ans_hash->{preview_latex_string} = + EV3P({ processCommands => 0, processVariables => 0 }, text2PG($ans_hash->{original_student_ans})); Context($oldContext); - my $ans_hash = new AnswerHash( - 'score' => "0", - 'correct_ans' => "Undefined", - # 'student_ans'=>$student->{student_ans}, - 'student_ans' => '', #supresses output to original answer field - 'original_student_ans' => $student->{original_student_ans}, - 'type' => 'essay', - 'ans_message' => 'This answer will be graded at a later time.', - 'preview_text_string' => '', - 'preview_latex_string' => $answer_value, - ); return $ans_hash; }); - $ans->install_pre_filter('erase') if $self->{ans_name}; - return $ans; } sub NAMED_ESSAY_BOX { my ($name, $row, $col) = @_; - $row = 8 unless defined($row); - $col = 75 unless defined($col); + $row //= 8; + $col //= 75; my $height = .07 * $row; - my $answer_value = ''; - $answer_value = $inputs_ref->{$name} if defined($inputs_ref->{$name}); - $name = RECORD_ANS_NAME($name, $answer_value); + my $answer_value = $inputs_ref->{$name} // ''; + $name = RECORD_ANS_NAME($name, $answer_value); - my $label = generate_aria_label($name); - # $answer_value =~ tr/$@//d; #`## make sure student answers can not be interpolated by e.g. EV3 - - #### Answer Value needs to have special characters replaced by the html codes - $answer_value = encode_pg_and_html($answer_value); - - # Get rid of tabs since they mess up the past answer db + # Get rid of tabs since they mess up the past answer db. + # FIXME: This fails because this only modifies the value for the next submission. + # It doesn't change the value in the already submitted form. $answer_value =~ s/\t/\ \ \ \ \ /; - #INSERT_RESPONSE($name,$name,$answer_value); # no longer needed? - my $out = MODES( - TeX => qq!\\vskip $height in \\hrulefill\\quad !, - Latex2HTML => - qq!\\begin{rawhtml}\\end{rawhtml}!, - HTML => qq! - - - !, + return MODES( + TeX => qq!\\vskip $height in \\hrulefill\\quad !, + HTML => tag( + 'textarea', + name => $name, + id => $name, + aria_label => generate_aria_label($name), + rows => $row, + cols => $col, + class => 'latexentryfield', + title => 'Enclose math expressions with backticks or use LaTeX.', + # Answer Value needs to have special characters replaced by the html codes + encode_pg_and_html($answer_value) + ) + . tag( + 'div', + class => 'latexentry-button-container d-flex gap-1 mt-1', + id => "$name-latexentry-button-container", + data_feedback_insert_element => $name, + tag( + 'button', + class => 'latexentry-preview btn btn-secondary btn-sm', + type => 'button', + maketext('Preview') + ) + ) + . tag('input', type => 'hidden', name => "previous_$name", value => $answer_value), PTX => '', ); - - $out; } sub essay_help { - - my $out = MODES( - TeX => '', - Latex2HTML => '', - 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. -

    - !, + return MODES( + TeX => '', + HTML => tag( + 'p', + maketext( + '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 => '', ); - - $out; } sub essay_box { - my $row = shift; - my $col = shift; - $row = 8 unless $row; - $col = 75 unless $col; + my ($row, $col) = @_; + $row ||= 8; + $col ||= 75; my $name = NEW_ANS_NAME(); - NAMED_ESSAY_BOX($name, $row, $col); + return NAMED_ESSAY_BOX($name, $row, $col); } @@ -167,20 +171,16 @@ sub essay_box { # help: boolean for whether to display the essay help message; default is true sub explanation_box { my %options = @_; - my $row = 8; - my $col = 75; - $row = $options{height} if defined $options{height}; - $col = $options{width} if defined $options{width}; - $row = $options{row} if defined $options{row}; - $col = $options{col} if defined $options{col}; - my $message = 'Explain.'; - if (defined $options{message}) { $message = $options{message} } - my $help = 1; - $help = $options{help} if defined $options{help}; - if ($envir{waiveExplanations}) { } - else { + + if ($envir{waiveExplanations}) { + return ''; + } else { ANS(essay_cmp()); - return $message . $PAR . essay_box($row, $col) . ($help ? essay_help() : ''); + return + ($options{message} // 'Explain.') + . $PAR + . essay_box($options{row} // $options{height} // 8, $options{col} // $options{width} // 75) + . (($options{help} // 1) ? essay_help() : ''); } } 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..73d8d709aa 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 - ) . < $ans_name, + class => 'graphtool-outer-container', + 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 + ) + . <
    .\n} . qq{
    \n} + . qq{} . qq{} . qq{} + . qq{} . qq{\n} . qq{
    \n}, 'body_text has correct content' @@ -102,7 +104,7 @@ is( { file => 'js/MathQuill/mqeditor.css', external => undef } ], extra_js_files => [ - { file => 'js/InputColor/color.js', external => 0, attributes => { defer => undef } }, + { file => 'js/Feedback/feedback.js', external => 0, attributes => { defer => undef } }, { file => 'js/Base64/Base64.js', external => 0, attributes => { defer => undef } }, { file => 'js/Knowls/knowl.js', external => 0, attributes => { defer => undef } }, { file => 'js/ImageView/imageview.js', external => 0, attributes => { defer => undef } }, From a4478ab5be7d7eabb0e2af8416fa3f8266a76dbd Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 27 Sep 2023 20:55:43 -0500 Subject: [PATCH 06/12] Remove the input background icon and instead use those icons for the button icon. An info icon is used for preview, and an exclamation mark in a triangle for partially correct answers. --- htdocs/js/MathQuill/mqeditor.scss | 2 +- htdocs/js/Problem/problem.scss | 47 ++++++++++++++++--------------- macros/PG.pl | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/htdocs/js/MathQuill/mqeditor.scss b/htdocs/js/MathQuill/mqeditor.scss index 81f3636a6e..e452eba03b 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; + padding: 4px; 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 7733bb3a36..719973e379 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -94,28 +94,6 @@ } } - input[type='text'], - span.mq-editable-field { - background-size: 20px auto; - background-position: right 4px center; - background-repeat: no-repeat; - - &.correct { - padding-right: 29px; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); - } - - &.incorrect { - padding-right: 29px; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23AE5757'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23AE5757' stroke='none'/%3e%3c/svg%3e"); - } - - &.partially-correct { - padding-right: 29px; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='%23FFC107' stroke='black'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='black' stroke='none'/%3e%3c/svg%3e"); - } - } - input[type='radio'] { margin-right: 0.25rem; } @@ -157,6 +135,31 @@ } } + .ww-feedback-btn { + width: 25px; + line-height: 1; + --bs-btn-padding-x: 2px; + --bs-btn-padding-y: 2px; + + i { + &::before { + content: url("data:image/svg+xml,"); + } + + &.correct::before { + content: url("data:image/svg+xml,"); + } + + &.incorrect::before { + content: url("data:image/svg+xml,"); + } + + &.partially-correct::before { + content: url("data:image/svg+xml,"); + } + } + } + .radio-buttons-container, .checkboxes-container, .applet-container, diff --git a/macros/PG.pl b/macros/PG.pl index 0348357011..9a0973c9f0 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -1320,7 +1320,7 @@ sub ENDDOCUMENT { } )->to_string, }, - sub { Mojo::DOM->new_tag('i', class => 'fa-solid fa-caret-down') } + sub { Mojo::DOM->new_tag('i', class => $options{resultClass}) } )->to_string; if ($options{insertElement} && $options{insertElement}->can($options{insertMethod})) { From 0eba130ff88c50e844a20d4550738e283ea5afcb Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 28 Sep 2023 10:50:43 -0500 Subject: [PATCH 07/12] Use a more reliable method for determining the number of unanswered questions. --- macros/PG.pl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/macros/PG.pl b/macros/PG.pl index 9a0973c9f0..c1482a02e7 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -1103,7 +1103,6 @@ sub ENDDOCUMENT { my $isEssay = ($ansHash->{type} // '') eq 'essay'; ++$numCorrect if $answerScore >= 1; ++$numEssay if $isEssay; - ++$numBlank unless $isEssay || (($ansHash->{student_ans} // '') =~ /\S/ || $answerScore >= 1); my %options = ( resultTitle => maketext('Preview'), @@ -1165,6 +1164,11 @@ sub ENDDOCUMENT { $ansHash->{feedback_options}->($ansHash, \%options, $problemContents) if ref($ansHash->{feedback_options}) eq 'CODE'; + # Update the count of the number of unanswered questions. This should be after the custom + # feedback_options call as that method can change the answerGiven option. (The draggableProof.pl macro + # does this.) + ++$numBlank unless $isEssay || $options{answerGiven} || $answerScore >= 1; + # Don't show the results popover if there is nothing to show. next unless @{ $options{feedbackElements} } From 78ef57e98d39fd071e55b782dff995267b07b796 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 28 Sep 2023 15:52:45 -0500 Subject: [PATCH 08/12] Tweak the correct, incorrect, and partially-correct styles to make them a bit darker. Also, make them only apply when the element is not focused so that focus styling is not overridden.. Opacity is simply set to 1. The inset box shadow also has an additial 1 pixel blur radius. --- htdocs/js/Problem/problem.scss | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 719973e379..2ae241e00e 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -72,25 +72,22 @@ input[type='checkbox'], span[id^='mq-answer'], .graphtool-container { - &.correct { - border-color: rgba(81, 153, 81, 0.8); /* green */ + &.correct:not(:focus):not(.mq-focused) { + border-color: rgba(81, 153, 81, 1); /* green */ outline: 0; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px 2px rgba(81, 153, 81, 0.6); - color: inherit; + box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.25), 0 0 8px 2px rgba(81, 153, 81, 1); } - &.incorrect { - border-color: rgba(191, 84, 84, 0.8); /* red */ + &.incorrect:not(:focus):not(.mq-focused) { + border-color: rgba(191, 84, 84, 1); /* 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; + box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.25), 0 0 8px 2px rgba(191, 84, 84, 1); } - &.partially-correct { - border-color: rgba(255, 193, 7, 0.8); /* yellow */ + &.partially-correct:not(:focus):not(.mq-focused) { + border-color: rgba(255, 193, 7, 1); /* yellow */ outline: 0; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px 2px rgba(255, 193, 7, 0.6); - color: inherit; + box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.25), 0 0 8px 2px rgba(255, 193, 7, 1); } } From 02e14c672bb04e2ab7dbfc3a9f4370b981f811aa Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 2 Oct 2023 18:16:06 -0500 Subject: [PATCH 09/12] Add a red dot to the top right corner of the feedback button if a message is present. --- htdocs/js/Problem/problem.scss | 13 +++++++++++++ macros/PG.pl | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 2ae241e00e..8bb2c4b943 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -135,6 +135,7 @@ .ww-feedback-btn { width: 25px; line-height: 1; + position: relative; --bs-btn-padding-x: 2px; --bs-btn-padding-y: 2px; @@ -155,6 +156,18 @@ content: url("data:image/svg+xml,"); } } + + &.with-message::before { + content: ' '; + position: absolute; + transform: translate(-50%, -50%); + top: 0; + left: 100%; + border: 1px solid black; + border-radius: 50%; + background-color: #dc3545; + padding: 0.25rem; + } } .radio-buttons-container, diff --git a/macros/PG.pl b/macros/PG.pl index c1482a02e7..0ba6ed240d 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -1266,8 +1266,9 @@ sub ENDDOCUMENT { ) . Mojo::DOM->new_tag( 'button', - type => 'button', - class => "ww-feedback-btn btn btn-sm $options{btnClass} $options{btnAddClass}", + type => 'button', + class => "ww-feedback-btn btn btn-sm $options{btnClass} $options{btnAddClass}" + . ($rh_envir->{showMessages} && $ansHash->{ans_message} ? ' with-message' : ''), 'aria-label' => $options{resultTitle}, data => { $showResults && $options{resultTitle} ? (bs_title => $options{resultTitle}) : (), From 5cc73cac4f4abff469a12d5ab9b80136177e4692 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 5 Oct 2023 06:45:25 -0500 Subject: [PATCH 10/12] Update localizatio po and pot files again. --- lib/WeBWorK/PG/Localize/cs-CZ.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/de.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/el.po | 126 ++++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/en.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/es.po | 126 ++++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/fr-CA.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/fr.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/he-IL.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/hu.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/ko.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/pg.pot | 106 +++++++++++++++++++++++--- lib/WeBWorK/PG/Localize/ru-RU.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/tr.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/zh-CN.po | 125 +++++++++++++++++++++++++----- lib/WeBWorK/PG/Localize/zh-HK.po | 125 +++++++++++++++++++++++++----- 15 files changed, 1609 insertions(+), 249 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/cs-CZ.po b/lib/WeBWorK/PG/Localize/cs-CZ.po index 7219134a23..6cebc8f7f9 100644 --- a/lib/WeBWorK/PG/Localize/cs-CZ.po +++ b/lib/WeBWorK/PG/Localize/cs-CZ.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:00-0500\n" +"PO-Revision-Date: 2023-10-05 06:42-0500\n" "Last-Translator: Robert Mařík , 2022-2023\n" "Language-Team: Czech (Czech Republic) (http://app.transifex.com/webwork/" "webwork2/language/cs_CZ/)\n" @@ -19,7 +19,43 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " "<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% správně" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "Nebylo odpovězeno na %quant(%1,otázku,otázky)." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "Správná odpověď" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -42,12 +78,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "Nápověda:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -55,21 +91,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "Zpráva" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "Poznámka:" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "Řešení:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -77,11 +130,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -89,10 +172,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "Zatím nehodnoceno" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Za částečné zodpovězení této úlohy můžete obdržet částečné hodnocení." @@ -105,7 +196,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -113,12 +204,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -139,15 +230,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/de.po b/lib/WeBWorK/PG/Localize/de.po index cf575cd3a6..50191e5f9f 100644 --- a/lib/WeBWorK/PG/Localize/de.po +++ b/lib/WeBWorK/PG/Localize/de.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:42-0500\n" "Last-Translator: Fabian Gabel, 2022\n" "Language-Team: German (http://app.transifex.com/webwork/webwork2/language/" "de/)\n" @@ -21,7 +21,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% richtig" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "%quant(%1,Frage wurde,Fragen wurden) nicht beantwortet." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Richtig" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "Lösung" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -44,12 +80,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "Hinweis: " -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "Hinweis: " @@ -57,21 +93,38 @@ msgstr "Hinweis: " msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "Falsch" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "Fehlermeldung" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "Note:" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "Lösung:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "Lösung: " @@ -79,11 +132,41 @@ msgstr "Lösung: " msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "Dies is eine neu randomisierte Version der Aufgabe." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -91,10 +174,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "Diese Aufgabe besteht aus mehreren Teilen." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Für diese Aufgabe können Sie Teilpunkte erhalten." @@ -109,7 +200,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "Antworten können im nächsten Teil nicht mehr geändert werden!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -117,12 +208,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "Antwort" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "Spalte" @@ -143,15 +234,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "Aufgabe" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/el.po b/lib/WeBWorK/PG/Localize/el.po index 29a810ac2e..b6973a6208 100644 --- a/lib/WeBWorK/PG/Localize/el.po +++ b/lib/WeBWorK/PG/Localize/el.po @@ -34,7 +34,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:43-0500\n" "Last-Translator: Kyriaki Kourouni, 2022-2023\n" "Language-Team: Greek (http://app.transifex.com/webwork/webwork2/language/" "el/)\n" @@ -44,7 +44,44 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% σωστό" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" +"%quant(%1,των ερωτήσεων απομένει, των ερωτήσεων απομένουν) αναπάντητες." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Διόρθωση" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "Διόρθωση Απάντησης" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "Λάθος" @@ -67,12 +104,12 @@ msgstr "Μετάβαση στο επόμενο" msgid "Hardcopy will always print the original version of the problem." msgstr "Στην έντυπη μορφή εμφανίζεται πάντα η αρχική μορφή του προβλήματος." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "Υπόδειξη:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "Υπόδειξη:" @@ -80,21 +117,38 @@ msgstr "Υπόδειξη:" msgid "If you come back to it later, it may revert to its original version." msgstr "Αν επιστρέψετε αργότερα, ενδέχεται να επανέλθει στην αρχική του μορφή." +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "Λάθος" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "Μήνυμα" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "Σημείωση:" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "Ορισμός τυχαίου αριθμού:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "Απάντηση:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "Λύση:" @@ -102,11 +156,41 @@ msgstr "Λύση:" msgid "Submit your answers again to go on to the next part." msgstr "Υποβάλετε τις απαντήσεις σας ξανά για να συνεχίσετε." +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "Αυτή είναι μια νέα (επανατυχαιοποιημένη) έκδοση του προβλήματος." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" "Αυτό το πρόβλημα περιέχει ένα βίντεο που πρέπει να προβληθεί στο διαδίκτυο." @@ -115,10 +199,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "Αυτό το πρόβλημα έχει περισσότερα από ένα μέρη." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "Σωστό" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "Χωρίς βαθμό" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Μπορείτε να κερδίσετε μερική πίστωση σε αυτό το πρόβλημα." @@ -132,7 +224,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "Μη δεκτές αλλαγές σε απαντήσεις αν συνεχίσετε στο επόμενο μέρος!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "Ο περιηγητής σας δεν υποστηρίζει την ετικέτα βίντεο." @@ -140,12 +232,12 @@ msgstr "Ο περιηγητής σας δεν υποστηρίζει την ετ msgid "Your score for this attempt is for this part only;" msgstr "Το σκορ σας σε αυτήν την προσπάθεια είναι μόνο για αυτό το μέρος˙" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "απάντηση" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "στήλη" @@ -166,15 +258,15 @@ msgstr "εάν" msgid "otherwise" msgstr "αλλιώς" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "μέρος" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "πρόβλημα" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "σειρά" diff --git a/lib/WeBWorK/PG/Localize/en.po b/lib/WeBWorK/PG/Localize/en.po index b16815e40e..f5acf329f1 100644 --- a/lib/WeBWorK/PG/Localize/en.po +++ b/lib/WeBWorK/PG/Localize/en.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:43-0500\n" "Last-Translator: Jason Aubrey \n" "Language-Team: English (United States) (http://www.transifex.com/webwork/" "webwork2/language/en_US/)\n" @@ -17,7 +17,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -40,12 +76,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -53,21 +89,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -75,11 +128,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -87,10 +170,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "" @@ -103,7 +194,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -111,12 +202,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -138,15 +229,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/es.po b/lib/WeBWorK/PG/Localize/es.po index 14cb89f12a..c1767029ca 100644 --- a/lib/WeBWorK/PG/Localize/es.po +++ b/lib/WeBWorK/PG/Localize/es.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:43-0500\n" "Last-Translator: Enrique Acosta , 2020\n" "Language-Team: Spanish (http://app.transifex.com/webwork/webwork2/language/" "es/)\n" @@ -20,7 +20,44 @@ msgstr "" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? " "1 : 2;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" +"%quant(%1,de las preguntas sigue,de las preguntas siguen) sin respuesta." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Correcto" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -43,12 +80,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "Pista:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -56,21 +93,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "incorrecto" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "Nota:" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "Solución:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -78,11 +132,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -90,10 +174,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Puedes obtener una fracción del puntaje total en este problema." @@ -106,7 +198,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -114,12 +206,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -140,15 +232,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "Problema" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/fr-CA.po b/lib/WeBWorK/PG/Localize/fr-CA.po index 5288f308b5..81c54deb2b 100644 --- a/lib/WeBWorK/PG/Localize/fr-CA.po +++ b/lib/WeBWorK/PG/Localize/fr-CA.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:43-0500\n" "Last-Translator: Jonathan Desaulniers , 2018-2019,2021-2022\n" "Language-Team: French (Canada) (http://app.transifex.com/webwork/webwork2/" @@ -22,7 +22,43 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % " "1000000 == 0 ? 1 : 2;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% correct" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "%quant(%1, question reste, questions restent) sans réponses." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Réussi" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "Bonne réponse" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "Faux" @@ -46,12 +82,12 @@ msgid "Hardcopy will always print the original version of the problem." msgstr "" "Le document sera toujours imprimer avec la version originale du problème." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "Indice :" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "Indice:" @@ -61,21 +97,38 @@ msgstr "" "Si vous revenez plus tard, le problème pourrait être revenu à sa version " "originale." +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "Erroné" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "Message" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "Note:" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "Définir la source aléatoire à:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "Solution :" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "Solution:" @@ -83,11 +136,41 @@ msgstr "Solution:" msgid "Submit your answers again to go on to the next part." msgstr "Soumettre votre réponse à nouveau pour passer à la partie suivante." +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "Voici une nouvelle version (à nouveau randomisée) du problème." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "Ce problème contient une vidéo qui doit être consultée en ligne." @@ -95,10 +178,18 @@ msgstr "Ce problème contient une vidéo qui doit être consultée en ligne." msgid "This problem has more than one part." msgstr "Ce problème comporte plusieurs parties." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "Vrai" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "Non classé" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Vous pouvez obtenir une partie des points pour ce problème." @@ -113,7 +204,7 @@ msgid "You may not change your answers when going on to the next part!" msgstr "" "Vous ne pouvez pas modifier vos réponses en passant à la partie suivante!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "Votre navigateur ne supporte pas l'environnement vidéo." @@ -122,12 +213,12 @@ msgid "Your score for this attempt is for this part only;" msgstr "" "Votre résultat pour cette tentative correspond à cette partie seulement;" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "réponse" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "colonne" @@ -148,15 +239,15 @@ msgstr "Si" msgid "otherwise" msgstr "autrement" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "partie" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "problème" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "ligne" diff --git a/lib/WeBWorK/PG/Localize/fr.po b/lib/WeBWorK/PG/Localize/fr.po index 28fcc99dd5..6ad9848616 100644 --- a/lib/WeBWorK/PG/Localize/fr.po +++ b/lib/WeBWorK/PG/Localize/fr.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:43-0500\n" "Last-Translator: Stéphanie Lanthier , 2011\n" "Language-Team: French (http://app.transifex.com/webwork/webwork2/language/" "fr/)\n" @@ -23,7 +23,43 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % " "1000000 == 0 ? 1 : 2;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% correct" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "%quant(%1, des questions restent, des questions reste) sans réponse." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Correct" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -46,12 +82,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -59,21 +95,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -81,11 +134,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -93,10 +176,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "Mise à jour réussie" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Vous pouvez obtenir une partie des points pour ce problème." @@ -109,7 +200,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -117,12 +208,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -143,15 +234,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/he-IL.po b/lib/WeBWorK/PG/Localize/he-IL.po index 8260036fd3..6353af588d 100644 --- a/lib/WeBWorK/PG/Localize/he-IL.po +++ b/lib/WeBWorK/PG/Localize/he-IL.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:42-0500\n" "Last-Translator: Nathan Wallach , 2022-2023\n" "Language-Team: Hebrew (Israel) (http://app.transifex.com/webwork/webwork2/" "language/he_IL/)\n" @@ -22,7 +22,43 @@ msgstr "" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % " "1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% נכון" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "%quant(%1, מהשאלות לא נענתה, מהשאלות לא נענו)." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "נכון" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "תשובה נכונה" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr " לא נכון" @@ -45,12 +81,12 @@ msgstr "המשך לחלק הבא" msgid "Hardcopy will always print the original version of the problem." msgstr "בקבצי הדפסה תמיד יודפס הגרסה המקורית של השאלה." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "רמז:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "רמז: " @@ -58,21 +94,38 @@ msgstr "רמז: " msgid "If you come back to it later, it may revert to its original version." msgstr "אם תחזור לשאלה בהמשל, ייתכן שהשאלה תחזור לגרסה המקורית." +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "לא נכון" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "הודעות" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "הערה:" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "החלף את זרע ההגרלות אל:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "פתרון:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "פתרון: " @@ -80,11 +133,41 @@ msgstr "פתרון: " msgid "Submit your answers again to go on to the next part." msgstr "שלח את התשובות שוב כדי להתקדם לחלק הבא." +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "הנה גרסה חדשה של השאלה." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "השאלה מכילה וידיאו שיש לראות באופן מקוון." @@ -92,10 +175,18 @@ msgstr "השאלה מכילה וידיאו שיש לראות באופן מקוו msgid "This problem has more than one part." msgstr "לשאלה זו יש יותר מחלק אחד." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "נכון" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "לא נוקדו" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "ניתן לקבל ניקוד חלקי בשאלה זו" @@ -108,7 +199,7 @@ msgstr "אתה יכול לבקש גרסה שונה של שאלה זו לאחר msgid "You may not change your answers when going on to the next part!" msgstr "לא ניתן לשנות תשובות כאשר אתה מתקדם לחלק הבא!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "הדפדפן שלך לא תומך ב-video tag." @@ -116,12 +207,12 @@ msgstr "הדפדפן שלך לא תומך ב-video tag." msgid "Your score for this attempt is for this part only;" msgstr "הציון שקבלת בהגשה זו הוא רק לחקל זה;" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "תשובה" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "עמודה" @@ -142,15 +233,15 @@ msgstr "if" msgid "otherwise" msgstr "אחרת" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "חלק" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "שאלה" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "שורה" diff --git a/lib/WeBWorK/PG/Localize/hu.po b/lib/WeBWorK/PG/Localize/hu.po index acee55a45d..e2eedf2d86 100644 --- a/lib/WeBWorK/PG/Localize/hu.po +++ b/lib/WeBWorK/PG/Localize/hu.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:01-0500\n" +"PO-Revision-Date: 2023-10-05 06:44-0500\n" "Last-Translator: pcsiba , 2014,2016-2017\n" "Language-Team: Hungarian (http://app.transifex.com/webwork/webwork2/language/" "hu/)\n" @@ -18,7 +18,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% helyes" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "%quant(%1,kérdés maradt,kérdés maradt) megválaszolatlan." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Helyes" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "Helyes válasz" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -41,12 +77,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -54,21 +90,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -76,11 +129,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -88,10 +171,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "Igaz" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Ezért a feladatért részpontokat kaphat. " @@ -104,7 +195,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -112,12 +203,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "válasz" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "oszlop" @@ -138,15 +229,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "feladat" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "sor" diff --git a/lib/WeBWorK/PG/Localize/ko.po b/lib/WeBWorK/PG/Localize/ko.po index 4e691ebb7d..78ffde31c8 100644 --- a/lib/WeBWorK/PG/Localize/ko.po +++ b/lib/WeBWorK/PG/Localize/ko.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:02-0500\n" +"PO-Revision-Date: 2023-10-05 06:44-0500\n" "Last-Translator: Ji-Young Ham , 2022\n" "Language-Team: Korean (http://app.transifex.com/webwork/webwork2/language/" "ko/)\n" @@ -18,7 +18,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "정답" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "정답" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -41,12 +77,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -54,21 +90,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "오답" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -76,11 +129,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "다음 단계로 이동하려면 답변을 다시 제출하십시오." +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -88,10 +171,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "채점되지 않은" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "이 문제의 부분 점수를 받았습니다." @@ -104,7 +195,7 @@ msgstr "이 문제의 새 버전은 마감일 이후에 받을 수 있습니다. msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -112,12 +203,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -138,15 +229,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/pg.pot b/lib/WeBWorK/PG/Localize/pg.pot index 421a4d7df1..887760f0a4 100644 --- a/lib/WeBWorK/PG/Localize/pg.pot +++ b/lib/WeBWorK/PG/Localize/pg.pot @@ -14,7 +14,43 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -34,7 +70,7 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1516 /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1402 /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint:" msgstr "" @@ -42,15 +78,31 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 msgid "Solution:" msgstr "" @@ -58,11 +110,35 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3179 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3062 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -70,10 +146,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "" @@ -86,7 +170,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3172 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3055 msgid "Your browser does not support the video tag." msgstr "" @@ -94,11 +178,11 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -117,15 +201,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/ru-RU.po b/lib/WeBWorK/PG/Localize/ru-RU.po index 3a6ced3c50..0495e38bba 100644 --- a/lib/WeBWorK/PG/Localize/ru-RU.po +++ b/lib/WeBWorK/PG/Localize/ru-RU.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:02-0500\n" +"PO-Revision-Date: 2023-10-05 06:44-0500\n" "Last-Translator: FULL NAME \n" "Language-Team: Russian (Russia) (http://app.transifex.com/webwork/webwork2/" "language/ru_RU/)\n" @@ -19,7 +19,43 @@ msgstr "" "n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || " "(n%100>=11 && n%100<=14)? 2 : 3);\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Верно" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "Правильные ответы" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -42,12 +78,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -55,21 +91,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -77,11 +130,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -89,10 +172,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Балл за эту задачу может дробиться. " @@ -105,7 +196,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -113,12 +204,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -139,15 +230,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/tr.po b/lib/WeBWorK/PG/Localize/tr.po index 446bdec32b..a5fa6ad025 100644 --- a/lib/WeBWorK/PG/Localize/tr.po +++ b/lib/WeBWorK/PG/Localize/tr.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:02-0500\n" +"PO-Revision-Date: 2023-10-05 06:44-0500\n" "Last-Translator: FULL NAME \n" "Language-Team: Turkish (http://app.transifex.com/webwork/webwork2/language/" "tr/)\n" @@ -17,7 +17,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% doğru" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "Soruların %1 tanesi yanıtsız bırakıldı." + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "Doğru yanıt" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -40,12 +76,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -53,21 +89,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -75,11 +128,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -87,10 +170,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "Bu sorudan kısmi puan alabilirsiniz." @@ -103,7 +194,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -111,12 +202,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -137,15 +228,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/zh-CN.po b/lib/WeBWorK/PG/Localize/zh-CN.po index 54a71c45c9..e738394966 100644 --- a/lib/WeBWorK/PG/Localize/zh-CN.po +++ b/lib/WeBWorK/PG/Localize/zh-CN.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:02-0500\n" +"PO-Revision-Date: 2023-10-05 06:45-0500\n" "Last-Translator: Liping Chen , 2013\n" "Language-Team: Chinese (China) (http://www.transifex.com/webwork/webwork2/" "language/zh_CN/)\n" @@ -20,7 +20,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% 正确" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "%quant(%1,个问题,个问题) 未完成。" + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "正确" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -43,12 +79,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -56,21 +92,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -78,11 +131,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -90,10 +173,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "这一题你可以得到部分成绩" @@ -106,7 +197,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -114,12 +205,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -140,15 +231,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" diff --git a/lib/WeBWorK/PG/Localize/zh-HK.po b/lib/WeBWorK/PG/Localize/zh-HK.po index 7a4d67a16e..a9a1d45e72 100644 --- a/lib/WeBWorK/PG/Localize/zh-HK.po +++ b/lib/WeBWorK/PG/Localize/zh-HK.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: webwork2\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-09-16 14:03-0500\n" +"PO-Revision-Date: 2023-10-05 06:45-0500\n" "Last-Translator: Liping Chen , 2013\n" "Language-Team: Chinese (Hong Kong) (http://app.transifex.com/webwork/" "webwork2/language/zh_HK/)\n" @@ -18,7 +18,43 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:285 +#. (@answerNames - $numBlank - $numCorrect - $numEssay) +#: /opt/webwork/pg/macros/PG.pl:1412 +msgid "%1 of the answers %plural(%1,is,are) NOT correct." +msgstr "" + +#. ($numEssay) +#: /opt/webwork/pg/macros/PG.pl:1435 +msgid "%1 of the answers will be graded later." +msgstr "" + +#. (round($answerScore * 100) +#: /opt/webwork/pg/macros/PG.pl:1157 +msgid "%1% correct" +msgstr "%1% 正确" + +#. ($numBlank) +#: /opt/webwork/pg/macros/PG.pl:1425 +msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1399 +msgid "All of the answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1390 +msgid "All of the gradeable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1149 +msgid "Correct" +msgstr "正确" + +#: /opt/webwork/pg/macros/PG.pl:1308 +msgid "Correct Answer" +msgstr "" + +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 msgid "False" msgstr "" @@ -41,12 +77,12 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1518 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1519 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1517 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 msgid "Hint: " msgstr "" @@ -54,21 +90,38 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1153 +msgid "Incorrect" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1319 +msgid "Message" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 msgid "Note:" msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1108 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 +msgid "Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1295 +msgid "Preview of Your Answer" +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1509 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1510 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1508 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 msgid "Solution: " msgstr "" @@ -76,11 +129,41 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" +#: /opt/webwork/pg/macros/PG.pl:1378 +msgid "The answer is NOT correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1351 +msgid "The answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1360 +msgid "The answer will be graded later." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1369 +msgid "The question has not been answered." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 +msgid "This answer will be graded at a later time." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3181 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +msgid "" +"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." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -88,10 +171,18 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:284 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 msgid "True" msgstr "" +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +msgid "Ungraded" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1290 +msgid "You Entered" +msgstr "" + #: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 msgid "You can earn partial credit on this problem." msgstr "这一题你可以得到部分成绩" @@ -104,7 +195,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3174 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 msgid "Your browser does not support the video tag." msgstr "" @@ -112,12 +203,12 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:610 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:621 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 msgid "answer" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "column" msgstr "" @@ -138,15 +229,15 @@ msgstr "" msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:627 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 msgid "part" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:616 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 msgid "problem" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:633 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 msgid "row" msgstr "" From 4839f01d2c1a33194e7d93bcc943295474fcb6c6 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 4 Oct 2023 16:34:56 -0500 Subject: [PATCH 11/12] Switch to using bootstraps success, danger, and warning colors. Dispense with the ResultsWithErrors, ResultsWithoutErrors, and ResultsAlert classes, and use bootstrap alerts instead. --- htdocs/js/Problem/problem.scss | 35 +++---------------- .../js/RadioMultiAnswer/RadioMultiAnswer.scss | 5 +++ macros/PG.pl | 18 +++++----- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 8bb2c4b943..bba7d0de77 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -209,33 +209,6 @@ /* Feedback */ -.ResultsWithoutError { - color: #0f5132; /* Dark Green */ - background-color: #8f8; /* Light Green */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; -} - -.ResultsWithError { - color: #400; /* Dark Red */ - background-color: #d69191; /* Light Red */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; -} - -.ResultsAlert { - color: #0e038c; /* Dark Blue */ - background-color: #fbd2b8; /* Light Orange */ - padding: 0.25rem; - border: 1px solid transparent; - border-radius: 0.25rem; - box-shadow: 3px 3px 3px darkgray; -} - .ww-feedback-popover { --bs-popover-body-padding-x: 0; --bs-popover-body-padding-y: 0; @@ -248,19 +221,21 @@ &.correct { .popover-header { - --bs-popover-header-bg: #8f8; + --bs-popover-header-bg: var(--bs-success); + --bs-popover-header-color: white; } } &.incorrect { .popover-header { - --bs-popover-header-bg: #d69191; + --bs-popover-header-bg: var(--bs-danger); + --bs-popover-header-color: white; } } &.partially-correct { .popover-header { - --bs-popover-header-bg: #ffc107; + --bs-popover-header-bg: var(--bs-warning); } } diff --git a/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss b/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss index 7242d95fe0..9694f6547f 100644 --- a/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss +++ b/htdocs/js/RadioMultiAnswer/RadioMultiAnswer.scss @@ -49,6 +49,11 @@ .rma-state-disabled { opacity: 0.35; } + + .ww-feedback-btn { + top: 50%; + transform: translateY(-50%); + } } .ww-feedback-btn { diff --git a/macros/PG.pl b/macros/PG.pl index 0ba6ed240d..5c4b8b1531 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -1347,7 +1347,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsWithoutError mb-2', + class => 'alert alert-success mb-2 p-1', maketext('The answer is correct.') ) ); @@ -1356,7 +1356,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsAlert mb-2', + class => 'alert alert-info mb-2 p-1', maketext('The answer will be graded later.') ) ); @@ -1365,7 +1365,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsAlert mb-2', + class => 'alert alert-warning mb-2 p-1', maketext('The question has not been answered.') ) ); @@ -1374,7 +1374,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsWithError mb-2', + class => 'alert alert-danger mb-2 p-1', maketext('The answer is NOT correct.') ) ); @@ -1386,7 +1386,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsWithoutError mb-2', + class => 'alert alert-success mb-2 p-1', maketext('All of the gradeable answers are correct.') ) ); @@ -1395,7 +1395,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsWithoutError mb-2', + class => 'alert alert-success mb-2 p-1', maketext('All of the answers are correct.') ) ); @@ -1405,7 +1405,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsWithError mb-2', + class => 'alert alert-danger mb-2 p-1', maketext( '[_1] of the answers [plural,_1,is,are] NOT correct.', @answerNames - $numBlank - $numCorrect - $numEssay @@ -1418,7 +1418,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsAlert mb-2', + class => 'alert alert-warning mb-2 p-1', maketext( '[quant,_1,of the questions remains,of the questions remain] unanswered.', $numBlank @@ -1431,7 +1431,7 @@ sub ENDDOCUMENT { @summary, Mojo::DOM->new_tag( 'div', - class => 'ResultsAlert mb-2', + class => 'alert alert-info mb-2 p-1', maketext('[_1] of the answers will be graded later.', $numEssay) ) ); From 8b76324ae6a16fae1ee8f3bb3fcadbec09e3c0ea Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 24 Oct 2023 17:19:08 -0500 Subject: [PATCH 12/12] Tweaks to style and popover DOM placement. Reduce the padding on the card headers in the results popover. Make the popover container the parent element of the feedback button. In most cases this will result in the popover being in the DOM immediately after the feedback button, and thus also immediately after the button in the tab order. Make clicking on the popover header close it. Note that this does not have interactive semantics other than a pointer cursor when the mouse is over it, and is not focusable. That is probably okay since this would be an annoying tab stop for keyboard users, and keyboard users can already open and close the popover with the button. --- htdocs/js/Feedback/feedback.js | 8 ++++++-- htdocs/js/Problem/problem.scss | 1 + macros/PG.pl | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/htdocs/js/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js index ee39da494c..b7271cee39 100644 --- a/htdocs/js/Feedback/feedback.js +++ b/htdocs/js/Feedback/feedback.js @@ -3,7 +3,7 @@ if (feedbackBtn.dataset.popoverInitialized) return; feedbackBtn.dataset.popoverInitialized = 'true'; - new bootstrap.Popover(feedbackBtn, { sanitize: false }); + new bootstrap.Popover(feedbackBtn, { sanitize: false, container: feedbackBtn.parentElement }); // Render MathJax previews. if (window.MathJax) { @@ -12,15 +12,19 @@ }); } - // Execute javascript in the answer preview. feedbackBtn.addEventListener('shown.bs.popover', () => { const bsPopover = bootstrap.Popover.getInstance(feedbackBtn); + + // Execute javascript in the answer preview. bsPopover.tip?.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); }); + + // Make a click on the popover header close the popover. + bsPopover.tip?.querySelector('.popover-header')?.addEventListener('click', () => bsPopover?.hide()); }); }; diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index bba7d0de77..b2ee8ea366 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -223,6 +223,7 @@ .popover-header { --bs-popover-header-bg: var(--bs-success); --bs-popover-header-color: white; + cursor: pointer; } } diff --git a/macros/PG.pl b/macros/PG.pl index 5c4b8b1531..2770286d50 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -1239,7 +1239,7 @@ sub ENDDOCUMENT { return '' unless defined $line && $line =~ /\S/; return Mojo::DOM->new_tag( 'div', - class => 'card-header text-center', + class => 'card-header text-center p-1', 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 {$line}); }