From 2307ec322e223006edb76cc81c76531baa4f6a76 Mon Sep 17 00:00:00 2001 From: Glenn Rice <grice1@missouriwestern.edu> Date: Fri, 12 Apr 2024 10:35:11 -0500 Subject: [PATCH 1/2] Make the PGLM lab problem open via the `render_rpc` endpoint. Instead of opening the problem using the `editMode` hack of the usual problem page, render the problem directly in its own page via the `render_rpc` endpoint. This means the style hacks in the PGML-lab problem are also not needed since the elements that were being hidden are not present in the page to begin with. The PGMLLab link in a course's templates directory is not needed as the problem source is read directly from the assets directory, and submitted to the `render_rpc` endpoint via the `uriEncodedProblemSource` parameter. --- assets/pg/PGMLLab/PGML-lab.pg | 6 ---- courses.dist/modelCourse/templates/PGMLLab | 1 - .../images}/webwork_logo.png | Bin htdocs/js/PGProblemEditor/pgproblemeditor.js | 34 +++++++++++++++++- lib/FormatRenderedProblem.pm | 1 + lib/WeBWorK/ContentGenerator/RenderViaRPC.pm | 3 +- lib/WebworkWebservice.pm | 3 +- lib/WebworkWebservice/RenderProblem.pm | 8 ++--- .../Instructor/PGProblemEditor.html.ep | 30 +++++++--------- templates/RPCRenderFormats/default.html.ep | 3 +- 10 files changed, 55 insertions(+), 34 deletions(-) delete mode 120000 courses.dist/modelCourse/templates/PGMLLab rename {assets/pg/PGMLLab => htdocs/images}/webwork_logo.png (100%) diff --git a/assets/pg/PGMLLab/PGML-lab.pg b/assets/pg/PGMLLab/PGML-lab.pg index e163e5a2b1..9d7bcc0772 100644 --- a/assets/pg/PGMLLab/PGML-lab.pg +++ b/assets/pg/PGMLLab/PGML-lab.pg @@ -2,12 +2,6 @@ DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); -# Hide things that are appropriate for regular problem use -HEADER_TEXT(tag( - 'style', - '#page-title, #message, #message_bottom, #custom_edit_message, #breadcrumb-navigation, #score_summary, #problemFooter {display: none}' -)); - sub EscapeHTML { my $s = shift; $s =~ s/&/~~&/g; diff --git a/courses.dist/modelCourse/templates/PGMLLab b/courses.dist/modelCourse/templates/PGMLLab deleted file mode 120000 index bc34f0c25f..0000000000 --- a/courses.dist/modelCourse/templates/PGMLLab +++ /dev/null @@ -1 +0,0 @@ -../../../webwork2/assets/pg/PGMLLab \ No newline at end of file diff --git a/assets/pg/PGMLLab/webwork_logo.png b/htdocs/images/webwork_logo.png similarity index 100% rename from assets/pg/PGMLLab/webwork_logo.png rename to htdocs/images/webwork_logo.png diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js index 86daeb8cb6..5f1771916f 100644 --- a/htdocs/js/PGProblemEditor/pgproblemeditor.js +++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js @@ -536,7 +536,6 @@ /text\/html/.test(response.headers.get('content-type')) ) { throw await response.text(); - return; } const data = await response.blob(); @@ -564,4 +563,37 @@ rendering = false; } }; + + const pgmlLabButton = document.getElementById('pgml-lab'); + pgmlLabButton?.addEventListener('click', () => { + const form = document.createElement('form'); + form.style.display = 'none'; + form.target = 'PGML'; + form.action = renderURL; + form.method = 'post'; + + const inputs = [ + ['courseID', document.getElementsByName('courseID')[0]?.value], + ['displayMode', document.getElementById('action_view_displayMode_id')?.value ?? 'MathJax'], + ['fileName', 'PGMLLab/PGML-lab.pg'], + ['uriEncodedProblemSource', pgmlLabButton.dataset.source] + ]; + + const user = document.getElementsByName('user')[0]; + if (user) inputs.push(['user', user.value]); + const sessionKey = document.getElementsByName('key')[0]; + if (sessionKey) inputs.push(['key', sessionKey.value]); + + for (const [name, value] of inputs) { + const input = document.createElement('input'); + input.name = name; + input.value = value; + input.type = 'hidden'; + form.append(input); + } + + document.body.append(form); + form.submit(); + form.remove(); + }); })(); diff --git a/lib/FormatRenderedProblem.pm b/lib/FormatRenderedProblem.pm index 387b6aef04..8ebc3051e1 100644 --- a/lib/FormatRenderedProblem.pm +++ b/lib/FormatRenderedProblem.pm @@ -266,6 +266,7 @@ sub formatRenderedProblem { LTIGradeMessage => $LTIGradeMessage, sourceFilePath => $ws->{inputs_ref}{sourceFilePath} // '', problemSource => $ws->{inputs_ref}{problemSource} // '', + rawProblemSource => $ws->{inputs_ref}{rawProblemSource} // '', uriEncodedProblemSource => $ws->{inputs_ref}{uriEncodedProblemSource} // '', fileName => $ws->{inputs_ref}{fileName} // '', formLanguage => $formLanguage, diff --git a/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm b/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm index 262186eeca..8157353273 100644 --- a/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm +++ b/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm @@ -66,7 +66,8 @@ async sub pre_header_initialize ($c) { return; } - $c->param('displayMode', 'tex') if ($c->param('outputformat') eq 'pdf' || $c->param('outputformat') eq 'tex'); + $c->param('displayMode', 'tex') + if $c->param('outputformat') && ($c->param('outputformat') eq 'pdf' || $c->param('outputformat') eq 'tex'); # Call the WebworkWebservice to render the problem and store the result in $c->return_object. my $rpc_service = WebworkWebservice->new($c); diff --git a/lib/WebworkWebservice.pm b/lib/WebworkWebservice.pm index a9356aa183..14c7e4b959 100644 --- a/lib/WebworkWebservice.pm +++ b/lib/WebworkWebservice.pm @@ -174,7 +174,8 @@ otherwise. sub formatRenderedProblem { my $self = shift; return HardcopyRenderedProblem::hardcopyRenderedProblem($self) - if $self->{inputs_ref}{outputformat} eq 'tex' || $self->{inputs_ref}{outputformat} eq 'pdf'; + if $self->{inputs_ref}{outputformat} + && ($self->{inputs_ref}{outputformat} eq 'tex' || $self->{inputs_ref}{outputformat} eq 'pdf'); return FormatRenderedProblem::formatRenderedProblem($self); } diff --git a/lib/WebworkWebservice/RenderProblem.pm b/lib/WebworkWebservice/RenderProblem.pm index 98367ccdd1..8ccf5f21ff 100644 --- a/lib/WebworkWebservice/RenderProblem.pm +++ b/lib/WebworkWebservice/RenderProblem.pm @@ -24,10 +24,10 @@ use Mojo::Util qw(url_unescape); use WeBWorK::Debug; use WeBWorK::CourseEnvironment; -use WeBWorK::PG::IO; use WeBWorK::DB; use WeBWorK::DB::Utils qw(global2user fake_set fake_problem); use WeBWorK::Utils qw(decode_utf8_base64); +use WeBWorK::Utils::Files qw(readFile); use WeBWorK::Utils::Rendering qw(renderPG); our $UNIT_TESTS_ON = 0; @@ -190,7 +190,7 @@ async sub renderProblem { if ($rh->{problemSource}) { $r_problem_source = \(decode_utf8_base64($rh->{problemSource}) =~ tr/\r/\n/r); $problemRecord->source_file(defined $rh->{fileName} ? $rh->{fileName} : $rh->{sourceFilePath}); - } elsif (defined $rh->{rawProblemSource}) { + } elsif ($rh->{rawProblemSource}) { $r_problem_source = \$rh->{rawProblemSource}; $problemRecord->source_file(defined $rh->{fileName} ? $rh->{fileName} : $rh->{sourceFilePath}); } elsif ($rh->{uriEncodedProblemSource}) { @@ -198,9 +198,7 @@ async sub renderProblem { $problemRecord->source_file(defined $rh->{fileName} ? $rh->{fileName} : $rh->{sourceFilePath}); } elsif (defined $rh->{sourceFilePath} && $rh->{sourceFilePath} =~ /\S/) { $problemRecord->source_file($rh->{sourceFilePath}); - $r_problem_source = - \(WeBWorK::PG::IO::read_whole_file($ce->{courseDirs}{templates} . '/' . $rh->{sourceFilePath})); - $problemRecord->source_file('RenderProblemFooBar') unless defined($problemRecord->source_file); + $r_problem_source = \(readFile($ce->{courseDirs}{templates} . '/' . $rh->{sourceFilePath})); } if ($UNIT_TESTS_ON) { diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep index 524ffff757..6c11e49345 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor.html.ep @@ -1,6 +1,8 @@ % use Mojo::URL; +% use Mojo::Util qw(url_escape); % % use WeBWorK::Utils qw(not_blank x getAssetURL); +% use WeBWorK::Utils::Files qw(readFile); % use WeBWorK::Utils::Sets qw(format_set_name_display); % use WeBWorK::HTML::CodeMirrorEditor % qw(generate_codemirror_html generate_codemirror_controls_html output_codemirror_static_files); @@ -114,24 +116,16 @@ class => 'reference-link btn btn-sm btn-info', data => { bs_toggle => 'tooltip', bs_placement => 'top' } =%> % # PGML lab problem rendered as an unattached problem in a new window. - % if (-e "$ce->{courseDirs}{templates}/PGMLLab/PGML-lab.pg") { - <%= link_to maketext('PGML') => $c->systemLink( - url_for('problem_detail', setID => 'Undefined_Set', problemID => 1), - params => { - displayMode => $ce->{pg}{options}{displayMode}, - problemSeed => 1234, - editMode => 'temporaryFile', - sourceFilePath => 'PGMLLab/PGML-lab.pg' - } - ), - target => 'PGML', - title => maketext( - 'PG markdown syntax used to format WeBWorK questions. ' - . 'This interactive lab can help you to learn the techniques.' - ), - class => 'reference-link btn btn-sm btn-info', - data => { bs_toggle => 'tooltip', bs_placement => 'top' } =%> - % } + % my $pgml_lab_source = readFile("$ce->{webworkDirs}{assets}/pg/PGMLLab/PGML-lab.pg"); + <%= tag 'button', type => 'button', + title => maketext( + 'PG markdown syntax used to format WeBWorK questions. ' + . 'This interactive lab can help you to learn the techniques.' + ), + id => 'pgml-lab', + class => 'reference-link btn btn-sm btn-info', + data => { source => url_escape($pgml_lab_source), bs_toggle => 'tooltip', bs_placement => 'top' }, + 'PGML' =%> % # http://webwork.maa.org/wiki/Category:Authors <%= link_to maketext('Author Info') => $ce->{webworkURLs}{AuthorHelpURL}, target => 'author_info', diff --git a/templates/RPCRenderFormats/default.html.ep b/templates/RPCRenderFormats/default.html.ep index 638f3565e1..83a0ca949e 100644 --- a/templates/RPCRenderFormats/default.html.ep +++ b/templates/RPCRenderFormats/default.html.ep @@ -70,11 +70,12 @@ % %= hidden_field sourceFilePath => $sourceFilePath %= hidden_field problemSource => $problemSource + %= hidden_field rawProblemSource => $rawProblemSource %= hidden_field uriEncodedProblemSource => $uriEncodedProblemSource %= hidden_field problemSeed => $problemSeed %= hidden_field problemUUID => $problemUUID %= hidden_field psvn => $psvn - %= hidden_field pathToProblemFile => $fileName + %= hidden_field fileName => $fileName %= hidden_field courseID => $courseID %= hidden_field user => $user %= hidden_field passwd => $passwd From 7dd127d5efd231bdc87823c2291fb22981b629c2 Mon Sep 17 00:00:00 2001 From: Glenn Rice <grice1@missouriwestern.edu> Date: Fri, 12 Apr 2024 11:53:16 -0500 Subject: [PATCH 2/2] Add examples of using PGML tags to the PGML lab problem. The examples are in the "Substitutions" drop down menu of "Examples". In particular there is an exmaple demonstrating how to use PGML tags for placement of the feedback button with MultiAnswer problems that use `singleResult => 1`. --- assets/pg/PGMLLab/PGML-lab.pg | 61 ++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/assets/pg/PGMLLab/PGML-lab.pg b/assets/pg/PGMLLab/PGML-lab.pg index 9d7bcc0772..e04e0f6494 100644 --- a/assets/pg/PGMLLab/PGML-lab.pg +++ b/assets/pg/PGMLLab/PGML-lab.pg @@ -461,6 +461,64 @@ TEXT(tag( ENDPGML ] ], + [ + 'Tags' => [ + <<~ 'END_PG', + loadMacros('parserMultiAnswer.pl', 'parserPopUp.pl'); + $ma = MultiAnswer(DropDown([ [ 'minimum', 'maximum' ] ], 1), 4)->with( + singleResult => 1, + checker => sub { + my ($cor, $stu) = @_; + return $cor->[0] == $stu->[0] && $cor->[1] == $stu->[1] + ? 1 + : 0; + } + ); + END_PG + <<~'ENDPGML' + A tag is a "div" by default. HTML attributes can be set via an array in the + first option. The only other allowed tag type is a span. To switch to a + span add 'span' to the beginning of the attribute array. The second tex + option and the third ptx option are used to set the output for the TeX and + PTX display modes. The format of the tex option is an array containing two + strings. The first string is the TeX code to insert before the content, + and the second the TeX code to insert after the content. The format of the + ptx option is similar to the format for the first html. It is an array with + the tag name (required), optionally followed by an array of attributes, and + optionally a separator. + + [<Dangerous content.>]{ + [ 'span', class => 'p-1 alert alert-danger', role => 'alert' ] + }{ + [ '{\color{red}', '}' ] + }{ + ['alert'] + } + + Tags may contain other PGML content and answers. Note that a span tag may + not contain new lines, tables, or anything else that would be invalid in a + span. Basically only text elements are valid. However, div tags may + contain pretty much anything. + + [< + This is an equation [`x + 3 = 5`]. + + The solution to the above equation is [_]{2}. + >]{ [ style => 'border: 1px solid black; padding: 1rem;' ] } + + One useful application is when using the parserMultiAnswer.pl macro with + singleResult answers. Wrap the answers in in a div tag with the + "ww-feedback-container" class to tell PG where to place the feedback + button. The feedback button will be placed at the end of the containing div + tag. + + [< + The [_]{$ma} value of [`f(x)`] is [_]{$ma} for the function + [`f(x) = 4 - x^2`]. + >]{ [ class => 'ww-feedback-container' ] } + ENDPGML + ] + ], ), Examples( 'Inline formatting', @@ -975,7 +1033,8 @@ TEXT(tag( '[@ perl-command @]* (no escaping)', '[@ perl-command @]** (parse results)', '[% comment %]', - '[<url>] (not implemented)', + '[<tag>]{html}{tex}{ptx}', + '[[url]] (not implemented)', '[!image!]{source}{width}{height}', ), Menu(