10 | %= form_for 'render-api/tags' => ( method => 'POST', id => 'problem-tags' ) => begin
11 | %= hidden_field 'file' => '', id => 'tag-filename'
12 |
13 |
14 | %= label_for 'Status' => 'Review Status: '
15 | %= select_field 'Status' => [['not reviewed' => ''], ['Accepted' => 'A'], ['Missing resource' => 'N'], ['Hold for further review' => 'F'], ['Rejected' => 'R']]
16 |
17 |
18 | %= label_for 'Description' => 'Description: '
19 | %= text_area 'Description'
20 |
21 |
22 | %= label_for 'Author' => 'Author: '
23 | %= text_field 'Author'
24 |
25 |
26 | %= label_for 'Institution' => 'Institution: '
27 | %= text_field 'Institution'
28 |
29 |
30 | %= label_for 'Date' => 'Date: '
31 | %= text_field 'Date'
32 |
33 |
34 | %= label_for 'DBsubject' => 'DBsubject: '
35 | %= select_field 'DBsubject' => [''], id => 'db-subject', onchange => 'updateDBchapter()'
36 |
37 |
38 | %= label_for 'DBchapter' => 'DBchapter: '
39 | %= select_field 'DBchapter' => [''], id => 'db-chapter', onchange => 'updateDBsection()'
40 |
41 |
42 | %= label_for 'DBsection' => 'DBsection: '
43 | %= select_field 'DBsection' => [''], id => 'db-section'
44 |
45 |
46 | %= label_for 'Level' => 'Level: '
47 | %= select_field 'Level' => [1,2,3,4,5,6]
48 |
49 |
50 |
51 | %= label_for 'Language' => 'Language: '
52 | %= text_field 'Language'
53 |
54 |
55 | %= label_for 'Keywords' => 'Keywords: '
56 | %= text_field 'Keywords'
57 |
58 |
59 | %= label_for 'Resources' => 'Resources: '
60 | %= text_field 'Resources'
61 |
62 |
63 | %= label_for 'MO' => 'MathObjects: '
64 | %= check_box 'MO', id => 'MO', value => 1
65 |
66 |
67 | %= label_for 'Static' => 'Static: '
68 | %= check_box 'Static', id => 'Static', value => 1
69 |
70 |
71 |
72 |
73 | %= label_for 'hintExists' => 'Hint provided: '
74 | %= check_box 'hintExists', id => 'hintExists', value => 1
75 |
76 |
77 | %= label_for 'solutionExists' => 'Solution provided: '
78 | %= check_box 'solutionExists', id => 'solutionExists', value => 1
79 |
80 |
81 |
82 | %= submit_button 'Save Tags'
83 |
84 | %= end
85 |
86 |
87 | %= javascript begin
88 | taxo = <%== $taxo %>;
89 | %= end
90 | %= javascript 'js/tags.js'
91 |
--------------------------------------------------------------------------------
/lib/WeBWorK/Utils.pm:
--------------------------------------------------------------------------------
1 | package WeBWorK::Utils;
2 | use base qw(Exporter);
3 |
4 | use strict;
5 | use warnings;
6 |
7 | use JSON;
8 |
9 | our @EXPORT_OK = qw(
10 | wwRound
11 | getAssetURL
12 | );
13 |
14 | # usage wwRound($places,$float)
15 | # return $float rounded up to number of decimal places given by $places
16 | sub wwRound(@) {
17 | my $places = shift;
18 | my $float = shift;
19 | my $factor = 10**$places;
20 | return int($float * $factor + 0.5) / $factor;
21 | }
22 |
23 | my $staticWWAssets;
24 | my $staticPGAssets;
25 | my $thirdPartyWWDependencies;
26 | my $thirdPartyPGDependencies;
27 |
28 | sub readJSON {
29 | my $fileName = shift;
30 |
31 | return unless -r $fileName;
32 |
33 | open(my $fh, "<:encoding(UTF-8)", $fileName) or die "FATAL: Unable to open '$fileName'!";
34 | local $/;
35 | my $data = <$fh>;
36 | close $fh;
37 |
38 | return JSON->new->decode($data);
39 | }
40 |
41 | sub getThirdPartyAssetURL {
42 | my ($file, $dependencies, $baseURL, $useCDN) = @_;
43 |
44 | for (keys %$dependencies) {
45 | if ($file =~ /^node_modules\/$_\/(.*)$/) {
46 | if ($useCDN && $1 !~ /mathquill/) {
47 | return
48 | "https://cdn.jsdelivr.net/npm/$_\@"
49 | . substr($dependencies->{$_}, 1) . '/'
50 | . ($1 =~ s/(?:\.min)?\.(js|css)$/.min.$1/gr);
51 | } else {
52 | return Mojo::URL->new("${baseURL}$file")->query(version => $dependencies->{$_} =~ s/#/@/gr);
53 | }
54 | }
55 | }
56 | return;
57 | }
58 |
59 | # Get the url for static assets.
60 | sub getAssetURL {
61 | my ($language, $file) = @_;
62 |
63 | # Load the static files list generated by `npm install` the first time this method is called.
64 | unless ($staticWWAssets) {
65 | my $staticAssetsList = "$ENV{RENDER_ROOT}/public/static-assets.json";
66 | $staticWWAssets = readJSON($staticAssetsList);
67 | unless ($staticWWAssets) {
68 | warn "ERROR: '$staticAssetsList' not found or not readable!\n"
69 | . "You may need to run 'npm install' from '$ENV{RENDER_ROOT}/public'.";
70 | $staticWWAssets = {};
71 | }
72 | }
73 |
74 | unless ($staticPGAssets) {
75 | my $staticAssetsList = "$ENV{PG_ROOT}/htdocs/static-assets.json";
76 | $staticPGAssets = readJSON($staticAssetsList);
77 | unless ($staticPGAssets) {
78 | warn "ERROR: '$staticAssetsList' not found or not readable!\n"
79 | . "You may need to run 'npm install' from '$ENV{PG_ROOT}/htdocs'.";
80 | $staticPGAssets = {};
81 | }
82 | }
83 |
84 | unless ($thirdPartyWWDependencies) {
85 | my $packageJSON = "$ENV{RENDER_ROOT}/public/package.json";
86 | my $data = readJSON($packageJSON);
87 | warn "ERROR: '$packageJSON' not found or not readable!\n" unless $data && defined $data->{dependencies};
88 | $thirdPartyWWDependencies = $data->{dependencies} // {};
89 | }
90 |
91 | unless ($thirdPartyPGDependencies) {
92 | my $packageJSON = "$ENV{PG_ROOT}/htdocs/package.json";
93 | my $data = readJSON($packageJSON);
94 | warn "ERROR: '$packageJSON' not found or not readable!\n" unless $data && defined $data->{dependencies};
95 | $thirdPartyPGDependencies = $data->{dependencies} // {};
96 | }
97 |
98 | # Check to see if this is a third party asset file in node_modules (either in webwork2/htdocs or pg/htdocs).
99 | # If so, then either serve it from a CDN if requested, or serve it directly with the library version
100 | # appended as a URL parameter.
101 | if ($file =~ /^node_modules/) {
102 | my $wwFile = getThirdPartyAssetURL(
103 | $file, $thirdPartyWWDependencies,
104 | '',
105 | 0
106 | );
107 | return $wwFile if $wwFile;
108 |
109 | my $pgFile =
110 | getThirdPartyAssetURL($file, $thirdPartyPGDependencies, 'pg_files/', 1);
111 | return $pgFile if $pgFile;
112 | }
113 |
114 | # If a right-to-left language is enabled (Hebrew or Arabic) and this is a css file that is not a third party asset,
115 | # then determine the rtl varaint file name. This will be looked for first in the asset lists.
116 | my $rtlfile =
117 | ($language =~ /^(he|ar)/ && $file !~ /node_modules/ && $file =~ /\.css$/)
118 | ? $file =~ s/\.css$/.rtl.css/r
119 | : undef;
120 |
121 | # First check to see if this is a file in the webwork htdocs location with a rtl variant.
122 | return "$staticWWAssets->{$rtlfile}"
123 | if defined $rtlfile && defined $staticWWAssets->{$rtlfile};
124 |
125 | # Next check to see if this is a file in the webwork htdocs location.
126 | return "$staticWWAssets->{$file}" if defined $staticWWAssets->{$file};
127 |
128 | # Now check to see if this is a file in the pg htdocs location with a rtl variant.
129 | return "pg_files/$staticPGAssets->{$rtlfile}" if defined $rtlfile && defined $staticPGAssets->{$rtlfile};
130 |
131 | # Next check to see if this is a file in the pg htdocs location.
132 | return "pg_files/$staticPGAssets->{$file}" if defined $staticPGAssets->{$file};
133 |
134 | # If the file was not found in the lists, then just use the given file and assume its path is relative to the
135 | # render app public folder.
136 | return "$file";
137 | }
138 |
139 | 1;
140 |
--------------------------------------------------------------------------------
/public/css/crt-display.css:
--------------------------------------------------------------------------------
1 | /* http://aleclownes.com/2017/02/01/crt-display.html */
2 | @keyframes flicker {
3 | 0% {
4 | opacity: 0.27861;
5 | }
6 | 5% {
7 | opacity: 0.34769;
8 | }
9 | 10% {
10 | opacity: 0.23604;
11 | }
12 | 15% {
13 | opacity: 0.90626;
14 | }
15 | 20% {
16 | opacity: 0.18128;
17 | }
18 | 25% {
19 | opacity: 0.83891;
20 | }
21 | 30% {
22 | opacity: 0.65583;
23 | }
24 | 35% {
25 | opacity: 0.67807;
26 | }
27 | 40% {
28 | opacity: 0.26559;
29 | }
30 | 45% {
31 | opacity: 0.84693;
32 | }
33 | 50% {
34 | opacity: 0.96019;
35 | }
36 | 55% {
37 | opacity: 0.08594;
38 | }
39 | 60% {
40 | opacity: 0.20313;
41 | }
42 | 65% {
43 | opacity: 0.71988;
44 | }
45 | 70% {
46 | opacity: 0.53455;
47 | }
48 | 75% {
49 | opacity: 0.37288;
50 | }
51 | 80% {
52 | opacity: 0.71428;
53 | }
54 | 85% {
55 | opacity: 0.70419;
56 | }
57 | 90% {
58 | opacity: 0.7003;
59 | }
60 | 95% {
61 | opacity: 0.36108;
62 | }
63 | 100% {
64 | opacity: 0.24387;
65 | }
66 | }
67 | @keyframes textShadow {
68 | 0% {
69 | text-shadow: 0.4389924193300864px 0 1px rgba(0,30,255,0.5), -0.4389924193300864px 0 1px rgba(255,0,80,0.3), 0 0 3px;
70 | }
71 | 5% {
72 | text-shadow: 2.7928974010788217px 0 1px rgba(0,30,255,0.5), -2.7928974010788217px 0 1px rgba(255,0,80,0.3), 0 0 3px;
73 | }
74 | 10% {
75 | text-shadow: 0.02956275843481219px 0 1px rgba(0,30,255,0.5), -0.02956275843481219px 0 1px rgba(255,0,80,0.3), 0 0 3px;
76 | }
77 | 15% {
78 | text-shadow: 0.40218538552878136px 0 1px rgba(0,30,255,0.5), -0.40218538552878136px 0 1px rgba(255,0,80,0.3), 0 0 3px;
79 | }
80 | 20% {
81 | text-shadow: 3.4794037899852017px 0 1px rgba(0,30,255,0.5), -3.4794037899852017px 0 1px rgba(255,0,80,0.3), 0 0 3px;
82 | }
83 | 25% {
84 | text-shadow: 1.6125630401149584px 0 1px rgba(0,30,255,0.5), -1.6125630401149584px 0 1px rgba(255,0,80,0.3), 0 0 3px;
85 | }
86 | 30% {
87 | text-shadow: 0.7015590085143956px 0 1px rgba(0,30,255,0.5), -0.7015590085143956px 0 1px rgba(255,0,80,0.3), 0 0 3px;
88 | }
89 | 35% {
90 | text-shadow: 3.896914047650351px 0 1px rgba(0,30,255,0.5), -3.896914047650351px 0 1px rgba(255,0,80,0.3), 0 0 3px;
91 | }
92 | 40% {
93 | text-shadow: 3.870905614848819px 0 1px rgba(0,30,255,0.5), -3.870905614848819px 0 1px rgba(255,0,80,0.3), 0 0 3px;
94 | }
95 | 45% {
96 | text-shadow: 2.231056963361899px 0 1px rgba(0,30,255,0.5), -2.231056963361899px 0 1px rgba(255,0,80,0.3), 0 0 3px;
97 | }
98 | 50% {
99 | text-shadow: 0.08084290417898504px 0 1px rgba(0,30,255,0.5), -0.08084290417898504px 0 1px rgba(255,0,80,0.3), 0 0 3px;
100 | }
101 | 55% {
102 | text-shadow: 2.3758461067427543px 0 1px rgba(0,30,255,0.5), -2.3758461067427543px 0 1px rgba(255,0,80,0.3), 0 0 3px;
103 | }
104 | 60% {
105 | text-shadow: 2.202193051050636px 0 1px rgba(0,30,255,0.5), -2.202193051050636px 0 1px rgba(255,0,80,0.3), 0 0 3px;
106 | }
107 | 65% {
108 | text-shadow: 2.8638780614874975px 0 1px rgba(0,30,255,0.5), -2.8638780614874975px 0 1px rgba(255,0,80,0.3), 0 0 3px;
109 | }
110 | 70% {
111 | text-shadow: 0.48874025155497314px 0 1px rgba(0,30,255,0.5), -0.48874025155497314px 0 1px rgba(255,0,80,0.3), 0 0 3px;
112 | }
113 | 75% {
114 | text-shadow: 1.8948491305757957px 0 1px rgba(0,30,255,0.5), -1.8948491305757957px 0 1px rgba(255,0,80,0.3), 0 0 3px;
115 | }
116 | 80% {
117 | text-shadow: 0.0833037308038857px 0 1px rgba(0,30,255,0.5), -0.0833037308038857px 0 1px rgba(255,0,80,0.3), 0 0 3px;
118 | }
119 | 85% {
120 | text-shadow: 0.09769827255241735px 0 1px rgba(0,30,255,0.5), -0.09769827255241735px 0 1px rgba(255,0,80,0.3), 0 0 3px;
121 | }
122 | 90% {
123 | text-shadow: 3.443339761481782px 0 1px rgba(0,30,255,0.5), -3.443339761481782px 0 1px rgba(255,0,80,0.3), 0 0 3px;
124 | }
125 | 95% {
126 | text-shadow: 2.1841838852799786px 0 1px rgba(0,30,255,0.5), -2.1841838852799786px 0 1px rgba(255,0,80,0.3), 0 0 3px;
127 | }
128 | 100% {
129 | text-shadow: 2.6208764473832513px 0 1px rgba(0,30,255,0.5), -2.6208764473832513px 0 1px rgba(255,0,80,0.3), 0 0 3px;
130 | }
131 | }
132 | .crt::after {
133 | content: " ";
134 | display: block;
135 | position: absolute;
136 | top: 0;
137 | left: 0;
138 | bottom: 0;
139 | right: 0;
140 | background: rgba(18, 16, 16, 0.1);
141 | opacity: 0;
142 | z-index: 2;
143 | pointer-events: none;
144 | animation: flicker 0.15s infinite;
145 | }
146 | .crt::before {
147 | content: " ";
148 | display: block;
149 | position: absolute;
150 | top: 0;
151 | left: 0;
152 | bottom: 0;
153 | right: 0;
154 | background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
155 | z-index: 2;
156 | background-size: 100% 2px, 3px 100%;
157 | pointer-events: none;
158 | }
159 | .crt {
160 | animation: textShadow 1.6s infinite;
161 | }
162 |
--------------------------------------------------------------------------------
/templates/RPCRenderFormats/default.html.ep:
--------------------------------------------------------------------------------
1 | % use WeBWorK::Utils qw(getAssetURL wwRound);
2 | %
3 |
4 | >
5 |
6 |
32 |
33 |
34 | %== $resultSummary
35 | <%= form_for $FORM_ACTION_URL, id => 'problemMainForm', class => 'problem-main-form',
36 | name => 'problemMainForm', method => 'POST', begin %>
37 |
>
38 | %== $problemText
39 |
40 | % if ($showScoreSummary) {
41 |
<%= $lh->maketext('You received a score of [_1] for this attempt.',
42 | wwRound(0, $rh_result->{problem_result}{score} * 100) . '%') %>
43 | % if ($rh_result->{problem_result}{msg}) {
44 |
<%= $rh_result->{problem_result}{msg} %>
45 | % }
46 | <%= hidden_field 'problem-result-score' => $rh_result->{problem_result}{score},
47 | id => 'problem-result-score' %>
48 | % }
49 | %= hidden_field sessionJWT => $rh_result->{sessionJWT}
50 | % if ($rh_result->{JWTanswerURLstatus}) {
51 | %= hidden_field JWTanswerURLstatus => $rh_result->{JWTanswerURLstatus}
52 | % }
53 | % if ($formatName eq 'debug' && $rh_result->{inputs_ref}{clientDebug}) {
54 | %= hidden_field clientDebug => $rh_result->{inputs_ref}{clientDebug}
55 | % }
56 | % if ($formatName ne 'static') {
57 |
58 | % # Submit buttons (preview and submit are shown by default)
59 | % if ($showPreviewButton ne '0') {
60 | <%= submit_button $lh->maketext('Preview My Answers'),
61 | name => 'previewAnswers', id => 'previewAnswers_id', class => 'btn btn-primary mb-1' %>
62 | % }
63 | % if ($showCheckAnswersButton ne '0') {
64 | <%= submit_button $lh->maketext('Submit Answers'),
65 | name => 'submitAnswers', class => 'btn btn-primary mb-1' %>
66 | % }
67 | % if ($showCorrectAnswersButton ne '0') {
68 | <%= submit_button $lh->maketext('Show Correct Answers'),
69 | name => 'showCorrectAnswers', class => 'btn btn-primary mb-1' %>
70 | % }
71 |
72 | % }
73 | % end
74 |
75 |
76 | % # PG warning messages (this includes translator warnings but not translator errors).
77 | % if ($rh_result->{pg_warnings}) {
78 |
79 |
<%= $lh->maketext('Warning messages') %>
80 |
81 | % for (split("\n", $rh_result->{pg_warnings})) {
82 | <%== $_ %>
83 | % }
84 |
85 |
86 | % }
87 | % # PG warning messages generated with WARN_message.
88 | % if (ref $rh_result->{warning_messages} eq 'ARRAY' && @{ $rh_result->{warning_messages} }) {
89 |
90 |
<%= $lh->maketext('PG warning messages') %>
91 |
92 | % for (@{ $rh_result->{warning_messages} }) {
93 | <%== $_ %>
94 | % }
95 |
96 |
97 | % }
98 | % # Translator errors.
99 | % if ($rh_result->{flags}{error_flag}) {
100 |
101 |
Translator errors
102 | <%== $rh_result->{errors} %>
103 |
104 | % }
105 | % # Additional information output only for the debug format.
106 | % if ($formatName eq 'debug') {
107 | % # PG debug messages generated with DEBUG_message.
108 | % if (@{ $rh_result->{debug_messages} }) {
109 |
110 |
PG debug messages
111 |
112 | % for (@{ $rh_result->{debug_messages} }) {
113 | <%== $_ %>
114 | % }
115 |
116 |
117 | % }
118 | % # Internal debug messages generated within PGcore.
119 | % if (ref $rh_result->{internal_debug_messages} eq 'ARRAY' && @{ $rh_result->{internal_debug_messages} }) {
120 |
121 |
Internal errors
122 |
123 | % for (@{ $rh_result->{internal_debug_messages} }) {
124 | <%== $_ %>
125 | % }
126 |
127 |
128 | % }
129 | % if ($rh_result->{inputs_ref}{clientDebug}) {
130 |
Webwork client data
131 | %== $pretty_print->($rh_result)
132 | % }
133 | % }
134 |
135 | % # Show the footer unless it is explicity disabled.
136 | % if ($showFooter ne '0') {
137 |
142 | % }
143 |
144 |
145 |
--------------------------------------------------------------------------------
/public/js/apps/Problem/problem.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | const frame = window.frameElement.id || window.frameElement.dataset.id || 'no-id';
3 | // Activate the popovers in the results table.
4 | document.querySelectorAll('.attemptResults .answer-preview[data-bs-toggle="popover"]')
5 | .forEach((preview) => {
6 | if (preview.dataset.bsContent)
7 | new bootstrap.Popover(preview);
8 | });
9 |
10 | // if there is a JWTanswerURLstatus element, report it to parent
11 | const status = document.getElementById('JWTanswerURLstatus')?.value;
12 | if (status) {
13 | console.log("problem status updated:", JSON.parse(value));
14 | window.parent.postMessage(value, '*');
15 | }
16 |
17 | // fetch the problem-result-score and postMessage to parent
18 | const score = document.getElementById('problem-result-score')?.value;
19 | if (score) {
20 | window.parent.postMessage(JSON.stringify({
21 | type: 'webwork.interaction.attempt',
22 | status: score,
23 | frame: frame,
24 | }), '*');
25 | }
26 |
27 | // set up listeners on knowl hints and solutions
28 | document.querySelectorAll('.knowl[data-type="hint"]').forEach((hint) => {
29 | hint.addEventListener('click', (event) => {
30 | window.parent.postMessage(JSON.stringify({
31 | type: 'webwork.interaction.hint',
32 | status: hint.classList[1],
33 | id: hint.dataset.bsTarget,
34 | frame: frame,
35 | }), '*');
36 | });
37 | });
38 |
39 | document.querySelectorAll('.knowl[data-type="solution"]').forEach((solution) => {
40 | solution.addEventListener('click', (event) => {
41 | window.parent.postMessage(JSON.stringify({
42 | type: 'webwork.interaction.solution',
43 | status: solution.classList[1],
44 | id: solution.dataset.bsTarget,
45 | frame: frame,
46 | }), '*');
47 | });
48 | });
49 |
50 | // set up listeners on the form for focus in/out, because they will bubble up to form
51 | // and because we don't want to juggle mathquill elements
52 | const form = document.getElementById('problemMainForm');
53 | let messageQueue = [];
54 | let messageTimer = null;
55 |
56 | function processMessageQueue() {
57 | // Process the original messages in the queue
58 | for (let message = messageQueue.pop(); message; message = messageQueue.pop()) {
59 | window.parent.postMessage(JSON.stringify(message), '*');
60 | }
61 |
62 | // Clear the message queue and timer
63 | messageQueue = [];
64 | clearTimeout(messageTimer);
65 | messageTimer = null;
66 | }
67 |
68 | // interrupt the blur/focus/blur/focus sequence caused by the toolbar
69 | function checkForButtonClick() {
70 | // using unshift so most recent is at the front
71 | if (messageQueue[0].type !== 'webwork.interaction.focus') return;
72 |
73 | // toolbar interaction focus/blur happens in between matching ids
74 | const id = messageQueue[0].id;
75 | if (messageQueue[3].id !== id) return;
76 |
77 | // toolbar interaction is focus/blur with same id, ends with answer id
78 | if (!messageQueue[1].id.endsWith(id)
79 | || !messageQueue[2].id.endsWith(id)
80 | || messageQueue[1].id !== messageQueue[2].id) return;
81 |
82 | // if we get here, we have a toolbar interaction
83 | const button = messageQueue[1].id.replace(`-${id}`, '');
84 | messageQueue.splice(0, 4, {
85 | type: 'webwork.interaction.toolbar',
86 | id: button,
87 | });
88 | }
89 |
90 | function scheduleMessage(message) {
91 | messageQueue.unshift(message);
92 |
93 | if (messageQueue.length >= 4) {
94 | checkForButtonClick();
95 | }
96 |
97 | if (messageTimer) clearTimeout(messageTimer);
98 | messageTimer = setTimeout(processMessageQueue, 350);
99 | }
100 |
101 | form.addEventListener('focusin', (event) => {
102 | const id = event.composedPath().reduce((s, el) => s ? s : el.id, '');
103 | if (id !== 'problem_body') {
104 | scheduleMessage({
105 | type: 'webwork.interaction.focus',
106 | id: id.replace('mq-answer-', ''),
107 | frame: frame,
108 | });
109 | }
110 | });
111 |
112 | form.addEventListener('focusout', (event) => {
113 | const id = event.composedPath().reduce((s, el) => s ? s : el.id, '');
114 | if (id !== 'problem_body') {
115 | scheduleMessage({
116 | type: 'webwork.interaction.blur',
117 | id: id.replace('mq-answer-', ''),
118 | frame: frame,
119 | });
120 | }
121 | });
122 |
123 | const modal = document.getElementById('creditModal');
124 | if (modal) {
125 | const bsModal = new bootstrap.Modal(modal);
126 | bsModal.show();
127 | const creditForm = document.getElementById('creditForm');
128 | creditForm.addEventListener('submit', (event) => {
129 | event.preventDefault();
130 | const formData = new FormData();
131 |
132 | // get the sessionJWT from the document and add it to the form data
133 | const sessionJWT = document.getElementsByName('sessionJWT').item(0).value;
134 | formData.append('sessionJWT', sessionJWT);
135 | // get the email from the form and add it to the form data
136 | const email = document.getElementById('creditModalEmail').value;
137 | formData.append('email', email);
138 | const url = creditForm.action;
139 | const options = {
140 | method: 'POST',
141 | body: formData,
142 | };
143 | fetch(url, options)
144 | .then((response) => {
145 | if (!response.ok) {
146 | console.error(response.statusText);
147 | }
148 | bsModal.hide();
149 | })
150 | .catch((error) => {
151 | console.error('Error:', error);
152 | bsModal.hide();
153 | });
154 | });
155 |
156 | // we also need to trigger the submit when the user clicks the button
157 | // or when they hit enter in the input field
158 | const creditButton = document.getElementById('creditModalSubmitBtn');
159 | creditButton.addEventListener('click', (event) => {
160 | creditForm.dispatchEvent(new Event('submit'));
161 | });
162 | const creditInput = document.getElementById('creditModalEmail');
163 | creditInput.addEventListener('keyup', (event) => {
164 | if (event.key === 'Enter') {
165 | creditForm.dispatchEvent(new Event('submit'));
166 | }
167 | });
168 | }
169 | })();
170 |
--------------------------------------------------------------------------------
/lib/WeBWorK/Localize/en.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | #
5 | # Translators:
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: webwork2\n"
9 | "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
10 | "PO-Revision-Date: 2021-03-09 17:00-0600\n"
11 | "Last-Translator: \n"
12 | "Language: en_US\n"
13 | "MIME-Version: 1.0\n"
14 | "Content-Type: text/plain; charset=UTF-8\n"
15 | "Content-Transfer-Encoding: 8bit\n"
16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
17 |
18 | #. (wwRound(0, $answerScore*100)
19 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:255
20 | msgid "%1% correct"
21 | msgstr ""
22 |
23 | #. ($numBlanks)
24 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:395
25 | msgid "%quant(%1,of the questions remains,of the questions remain) unanswered."
26 | msgstr ""
27 |
28 | #: PG/macros/PGbasicmacros.pl:1277 PG/macros/PGbasicmacros.pl:1286
29 | msgid ""
30 | "(Instructor hint preview: show the student hint after the following number "
31 | "of attempts:"
32 | msgstr ""
33 |
34 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:386
35 | msgid "All of the answers above are correct."
36 | msgstr ""
37 |
38 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:384
39 | msgid "All of the gradeable answers above are correct."
40 | msgstr ""
41 |
42 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:289
43 | msgid "Answer Preview"
44 | msgstr ""
45 |
46 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:391
47 | msgid "At least one of the answers above is NOT correct."
48 | msgstr ""
49 |
50 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:291
51 | msgid "Correct Answer"
52 | msgstr ""
53 |
54 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:288
55 | msgid "Entered"
56 | msgstr ""
57 |
58 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:238
59 | msgid "FeedbackMessage"
60 | msgstr ""
61 |
62 | #: PG/macros/problemRandomize.pl:185 PG/macros/problemRandomize.pl:186
63 | msgid "Get a new version of this problem"
64 | msgstr ""
65 |
66 | #: PG/macros/compoundProblem.pl:470
67 | msgid "Go back to Part 1"
68 | msgstr ""
69 |
70 | #: PG/macros/compoundProblem.pl:291 PG/macros/compoundProblem.pl:480
71 | #: PG/macros/compoundProblem.pl:491
72 | msgid "Go on to next part"
73 | msgstr ""
74 |
75 | #: PG/macros/problemRandomize.pl:409
76 | msgid "Hardcopy will always print the original version of the problem."
77 | msgstr ""
78 |
79 | #: PG/macros/PGbasicmacros.pl:1558
80 | msgid "Hint:"
81 | msgstr ""
82 |
83 | #: PG/macros/PGbasicmacros.pl:1558
84 | msgid "Hint: "
85 | msgstr ""
86 |
87 | #: PG/macros/problemRandomize.pl:408
88 | msgid "If you come back to it later, it may revert to its original version."
89 | msgstr ""
90 |
91 | #: PG/macros/PGbasicmacros.pl:1226
92 | msgid "Instructor solution preview: show the student solution after due date."
93 | msgstr ""
94 |
95 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:292
96 | msgid "Message"
97 | msgstr ""
98 |
99 | #: PG/macros/problemRandomize.pl:382 PG/macros/problemRandomize.pl:407
100 | msgid "Note:"
101 | msgstr ""
102 |
103 | #: RenderApp/Controller/FormatRenderedProblem.pm:276
104 | msgid "Preview My Answers"
105 | msgstr ""
106 |
107 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:290
108 | msgid "Result"
109 | msgstr ""
110 |
111 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:302
112 | msgid "Results for this submission"
113 | msgstr ""
114 |
115 | #: PG/macros/problemRandomize.pl:187
116 | msgid "Set random seed to:"
117 | msgstr ""
118 |
119 | #: RenderApp/Controller/FormatRenderedProblem.pm:277
120 | msgid "Show correct answers"
121 | msgstr ""
122 |
123 | #: PG/macros/PGbasicmacros.pl:1554 PG/macros/PGbasicmacros.pl:1555
124 | msgid "Solution:"
125 | msgstr ""
126 |
127 | #: PG/macros/PGbasicmacros.pl:1553
128 | msgid "Solution: "
129 | msgstr ""
130 |
131 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:377
132 | msgid "Some answers will be graded later."
133 | msgstr ""
134 |
135 | #: RenderApp/Controller/FormatRenderedProblem.pm:278
136 | msgid "Submit Answers"
137 | msgstr ""
138 |
139 | #: PG/macros/compoundProblem.pl:502
140 | msgid "Submit your answers again to go on to the next part."
141 | msgstr ""
142 |
143 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:379
144 | msgid "The answer above is NOT correct."
145 | msgstr ""
146 |
147 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:375
148 | msgid "The answer above is correct."
149 | msgstr ""
150 |
151 | #: PG/macros/problemRandomize.pl:407
152 | msgid "This is a new (re-randomized) version of the problem."
153 | msgstr ""
154 |
155 | #: PG/macros/PGbasicmacros.pl:3084
156 | msgid "This problem contains a video which must be viewed online."
157 | msgstr ""
158 |
159 | #: PG/macros/compoundProblem.pl:602
160 | msgid "This problem has more than one part."
161 | msgstr ""
162 |
163 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:248
164 | msgid "Ungraded"
165 | msgstr ""
166 |
167 | #: PG/macros/PGanswermacros.pl:1693
168 | msgid "You can earn partial credit on this problem."
169 | msgstr ""
170 |
171 | #: PG/macros/problemRandomize.pl:383
172 | msgid "You can get a new version of this problem after the due date."
173 | msgstr ""
174 |
175 | #: PG/macros/compoundProblem.pl:610
176 | msgid "You may not change your answers when going on to the next part!"
177 | msgstr ""
178 |
179 | #: PG/macros/PGbasicmacros.pl:3079
180 | msgid "Your browser does not support the video tag."
181 | msgstr ""
182 |
183 | #: PG/macros/compoundProblem.pl:603
184 | msgid "Your score for this attempt is for this part only;"
185 | msgstr ""
186 |
187 | #. (wwRound(0, $problemResult->{score} * 100)
188 | #: RenderApp/Controller/FormatRenderedProblem.pm:215
189 | msgid "Your score on this attempt is %1"
190 | msgstr ""
191 |
192 | #: RenderApp/Controller/FormatRenderedProblem.pm:217
193 | msgid "Your score was not recorded."
194 | msgstr ""
195 |
196 | #: PG/macros/PGbasicmacros.pl:645 PG/macros/PGbasicmacros.pl:656
197 | msgid "answer"
198 | msgstr ""
199 |
200 | #: PG/macros/PGbasicmacros.pl:669
201 | msgid "column"
202 | msgstr ""
203 |
204 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:244
205 | msgid "correct"
206 | msgstr ""
207 |
208 | #. ('j','k','_0')
209 | #. ('j','k')
210 | #: PG/lib/Parser/List/Vector.pm:35 PG/lib/Value/Vector.pm:278
211 | #: PG/macros/contextLimitedVector.pl:94
212 | msgid "i"
213 | msgstr ""
214 |
215 | #: PG/macros/contextPiecewiseFunction.pl:774
216 | msgid "if"
217 | msgstr ""
218 |
219 | #: WeBWorK/lib/WeBWorK/Utils/AttemptsTable.pm:253
220 | msgid "incorrect"
221 | msgstr ""
222 |
223 | #: PG/macros/contextPiecewiseFunction.pl:777
224 | msgid "otherwise"
225 | msgstr ""
226 |
227 | #: PG/macros/PGbasicmacros.pl:662
228 | msgid "part"
229 | msgstr ""
230 |
231 | #: PG/macros/PGbasicmacros.pl:651
232 | msgid "problem"
233 | msgstr ""
234 |
235 | #: PG/macros/PGbasicmacros.pl:668
236 | msgid "row"
237 | msgstr ""
238 |
239 | #: PG/macros/compoundProblem.pl:470 PG/macros/compoundProblem.pl:480
240 | msgid "when you submit your answers"
241 | msgstr ""
242 |
243 | #: PG/macros/compoundProblem.pl:604
244 | msgid "your overall score is for all the parts combined."
245 | msgstr ""
246 |
--------------------------------------------------------------------------------
/lib/WeBWorK/Localize/standalone.pot:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR