diff --git a/bin/check_modules.pl b/bin/check_modules.pl index 3b73cee2bf..264b4af686 100755 --- a/bin/check_modules.pl +++ b/bin/check_modules.pl @@ -88,6 +88,7 @@ =head1 DESCRIPTION Net::OAuth Opcode Pandoc + Perl::Critic Perl::Tidy PHP::Serialization Pod::Simple::Search diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js index 286840cc16..9ef2cd8427 100644 --- a/htdocs/js/PGProblemEditor/pgproblemeditor.js +++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js @@ -161,6 +161,15 @@ ?.addEventListener('change', () => (deleteBackupCheck.checked = true)); } + const renderArea = document.getElementById('pgedit-render-area'); + + const scrollToRenderArea = () => { + // Scroll to the top of the render window if the current scroll position is below that. + const renderAreaRect = renderArea.getBoundingClientRect(); + const topBarHeight = document.querySelector('.webwork-logo')?.getBoundingClientRect().height ?? 0; + if (renderAreaRect.top < topBarHeight) window.scrollBy(0, renderAreaRect.top - topBarHeight); + }; + // Send a request to the server to perltidy the current PG code in the CodeMirror editor. const tidyPGCode = () => { const request_object = { courseID: document.getElementsByName('courseID')[0]?.value }; @@ -235,15 +244,43 @@ .catch((err) => showMessage(`Error: ${err?.message ?? err}`)); }; + // Send a request to the server to run the PG critic in the CodeMirror editor. + const runPGCritic = () => { + const request_object = { courseID: document.getElementsByName('courseID')[0]?.value }; + + const user = document.getElementsByName('user')[0]; + if (user) request_object.user = user.value; + const sessionKey = document.getElementsByName('key')[0]; + if (sessionKey) request_object.key = sessionKey.value; + + request_object.rpc_command = 'runPGCritic'; + request_object.pgCode = + webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? ''; + + fetch(webserviceURL, { method: 'post', mode: 'same-origin', body: new URLSearchParams(request_object) }) + .then((response) => response.json()) + .then((data) => { + if (data.error) throw new Error(data.error); + if (!data.result_data) throw new Error('An invalid response was received.'); + renderArea.innerHTML = data.result_data.html; + scrollToRenderArea(); + }) + .catch((err) => showMessage(`Error: ${err?.message ?? err}`)); + }; + document.getElementById('take_action')?.addEventListener('click', async (e) => { - if (document.getElementById('current_action')?.value === 'format_code') { + if (document.getElementById('current_action')?.value === 'code_maintenance') { e.preventDefault(); - if (document.querySelector('input[name="action.format_code"]:checked').value == 'tidyPGCode') { + if (document.querySelector('input[name="action.code_maintenance"]:checked').value === 'tidyPGCode') { tidyPGCode(); } else if ( - document.querySelector('input[name="action.format_code"]:checked').value == 'convertCodeToPGML' + document.querySelector('input[name="action.code_maintenance"]:checked').value === 'convertCodeToPGML' ) { convertCodeToPGML(); + } else if ( + document.querySelector('input[name="action.code_maintenance"]:checked').value === 'runPGCritic' + ) { + runPGCritic(); } return; } @@ -306,7 +343,6 @@ }); const renderURL = `${webworkConfig?.webwork_url ?? '/webwork2'}/render_rpc`; - const renderArea = document.getElementById('pgedit-render-area'); const fileType = document.getElementsByName('file_type')[0]?.value; // This is either the div containing the CodeMirror editor or the problemContents textarea in the case that @@ -390,11 +426,7 @@ } adjustIFrameHeight(); - - // Scroll to the top of the render window if the current scroll position is below that. - const renderAreaRect = renderArea.getBoundingClientRect(); - const topBarHeight = document.querySelector('.webwork-logo')?.getBoundingClientRect().height ?? 0; - if (renderAreaRect.top < topBarHeight) window.scrollBy(0, renderAreaRect.top - topBarHeight); + scrollToRenderArea(); }); const render = () => diff --git a/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm b/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm index c7736812de..c668a903e1 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm @@ -90,7 +90,7 @@ the submit button pressed (the action). Requested actions and aliases View/Reload action = view Generate Hardcopy: action = hardcopy - Format Code: action = format_code + Code Maintenance: action = code_maintenance Save: action = save Save as: action = save_as Append: action = add_problem @@ -118,15 +118,15 @@ use SampleProblemParser qw(getSampleProblemCode generateMetadata); use constant DEFAULT_SEED => 123456; # Editor tabs -use constant ACTION_FORMS => [qw(view hardcopy format_code save save_as add_problem revert)]; +use constant ACTION_FORMS => [qw(view hardcopy code_maintenance save save_as add_problem revert)]; use constant ACTION_FORM_TITLES => { - view => x('View/Reload'), - hardcopy => x('Generate Hardcopy'), - format_code => x('Format Code'), - save => x('Save'), - save_as => x('Save As'), - add_problem => x('Append'), - revert => x('Revert'), + view => x('View/Reload'), + hardcopy => x('Generate Hardcopy'), + code_maintenance => x('Code Maintenance'), + save => x('Save'), + save_as => x('Save As'), + add_problem => x('Append'), + revert => x('Revert'), }; my $BLANKPROBLEM = 'newProblem.pg'; @@ -847,9 +847,9 @@ sub view_handler ($c) { return; } -# The format_code action is handled by javascript. This is provided just in case +# The code_maintenance action is handled by javascript. This is provided just in case # something goes wrong and the handler is called. -sub format_code_handler { } +sub code_maintenance_handler { } sub hardcopy_handler ($c) { # Redirect to problem editor page. diff --git a/lib/WebworkWebservice.pm b/lib/WebworkWebservice.pm index d8f9a28a5e..3393cc3b14 100644 --- a/lib/WebworkWebservice.pm +++ b/lib/WebworkWebservice.pm @@ -255,6 +255,7 @@ sub command_permission { putPastAnswer => 'problem_grader', tidyPGCode => 'access_instructor_tools', convertCodeToPGML => 'access_instructor_tools', + runPGCritic => 'access_instructor_tools', # WebworkWebservice::RenderProblem renderProblem => 'proctor_quiz_login', diff --git a/lib/WebworkWebservice/ProblemActions.pm b/lib/WebworkWebservice/ProblemActions.pm index 3252bcdaa8..3305176361 100644 --- a/lib/WebworkWebservice/ProblemActions.pm +++ b/lib/WebworkWebservice/ProblemActions.pm @@ -8,6 +8,7 @@ use Data::Structure::Util qw(unbless); use WeBWorK::PG::Tidy qw(pgtidy); use WeBWorK::PG::ConvertToPGML qw(convertToPGML); +use WeBWorK::PG::Critic qw(critiquePGCode); sub getUserProblem { my ($invocant, $self, $params) = @_; @@ -165,4 +166,18 @@ sub convertCodeToPGML { } +sub runPGCritic { + my ($invocant, $self, $params) = @_; + + return { + ra_out => { + html => $self->c->render_to_string( + template => 'ContentGenerator/Instructor/PGProblemEditor/pg_critic', + violations => [ critiquePGCode($params->{pgCode}) ] + ) + }, + text => 'The script pg-critic has been run successfully.' + }; +} + 1; diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep new file mode 100644 index 0000000000..f05df5d277 --- /dev/null +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/code_maintenance_form.html.ep @@ -0,0 +1,47 @@ +% last unless $c->{is_pg}; +
- <%= maketext('You can also click "Edit Selected Theme" to edit a hardcopy theme. The new theme will be saved to ' - . 'the templates/hardcopyThemes folder.') =%> + <%= maketext('You can also click "Edit Selected Theme" to edit a hardcopy theme. The new theme will be saved ' + . 'to the templates/hardcopyThemes folder.') =%>
-