-
-
-
-EOF
-$ans =3;
-
-ANS(num_cmp($ans,reltol=>1,format =>"%0.14g") ); #We are allowing 1 percent error for the answer.
-
-
-ENDDOCUMENT();
diff --git a/courses.dist/modelCourse/templates/setDemo/limits.pg b/courses.dist/modelCourse/templates/setDemo/limits.pg
deleted file mode 100644
index 4bb29e5280..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/limits.pg
+++ /dev/null
@@ -1,89 +0,0 @@
-DOCUMENT(); # This should be the first executable line in the problem.
-
-loadMacros(
- "PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
- "PGgraphmacros.pl",
- "PGauxiliaryFunctions.pl"
-);
-
-$showPartialCorrectAnswers = 1;
-
-$a=random(-3,3,1);
-$b=random(-2,3,1);
-$c=random(-3,2,1);
-$m1=random(-1,1,0.5);
-$m2=($b - $a)/2;
-$m3=($c - $b - 1)/2;
-$m4=random(-1,1,0.5);
-@slice = NchooseK(3,3);
-
-@colors = ("blue", "red", "green");
-@sc = @colors[@slice]; #scrambled colors
-@sa = ('A','B','C')[@slice];
-
-$f1 = FEQ("${m1}(x+1) + $a for x in [-2,-1) using color:$sc[0] and weight:2");
-$f2 = FEQ("${m2}(x-1) + $b for x in (-1,1) using color=$sc[0] and weight:2");
-$f3 = FEQ("${m3}(x-3) + $c for x in [1,3) using color=$sc[0] and weight=2");
-$f4 = FEQ("1+$a for x in [-1,-1] using color=$sc[0] and weight=2");
-$f5 = FEQ("${m4}(x-3) + $c for x in (3,4] using color=$sc[0] and weight=2");
-
-$graph = init_graph(-3,-6,5,6,'axes'=>[0,0],'grid'=>[8,12]);
-
-($f1Ref,$f2Ref,$f3Ref,$f4Ref,$f5Ref) = plot_functions($graph,$f1,$f2,$f3,$f4,$f5);
-
-BEGIN_TEXT
-Let F be the function below.$PAR
-If you are having a hard time seeing the picture clearly, click on the picture. It will expand to a larger picture on its own page so that you can inspect it more clearly.$PAR
-END_TEXT
-
-TEXT(image( insertGraph($graph), height=>200, width=>200 ));
-
-BEGIN_TEXT
-$BR
-$BR
-Evaluate each of the following expressions. $PAR
-Note: Enter 'DNE' if the limit does not exist or is not defined. $PAR
-
-a) \( \lim_{x \to -1^-} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-b) \( \lim_{x \to -1^+} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-c) \( \lim_{x \to -1} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-d) \( F(-1) \) = \{ans_rule(4)\}
-$PAR
-
-e) \( \lim_{x \to 1^-} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-f) \( \lim_{x \to 1^+} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-g) \( \lim_{x \to 1} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-h) \( \lim_{x \to 3} F(x) \) = \{ans_rule(4)\}
-$PAR
-
-i) \( F(3) \) = \{ans_rule(4)\}
-$PAR
-
-END_TEXT
-
-$ap1 = 1 + $a;
-$bp1 = 1 + $b;
-
-# limits at -1
-ANS(num_cmp( [ $a, $a, $a, $ap1] , strings => ['DNE'] )) ;
-# limits at 1
-ANS(num_cmp( [ $b, $bp1,'DNE'] , strings => ['DNE'] )) ;
-# limits at 3
-ANS(num_cmp( [ $c, 'DNE' ] , strings => ['DNE'] )) ;
-
-
-ENDDOCUMENT(); # This should be the last executable line in the problem.
diff --git a/courses.dist/modelCourse/templates/setDemo/liteApplet1.pg b/courses.dist/modelCourse/templates/setDemo/liteApplet1.pg
deleted file mode 100644
index a00e0178be..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/liteApplet1.pg
+++ /dev/null
@@ -1,67 +0,0 @@
-DOCUMENT();
-
-loadMacros("PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
-);
-
-$showPartialCorrectAnswers = 1;
-
-# The link to the java applet is hard wired to use the java applet
-# served from the University of Rochester WeBWorK machine.
-# It is possible to set this up so that the java applet is served
-# from any machine
-# For details use the Feedback button to contact the authors of WeBWorK
-
-BEGIN_TEXT
-This is a lite applet designed by Frank Wattenberg.
-$BR
-\{htmlLink( '/webwork2_course_files/demoCourse/live_map_instructions.html ',
-'Instructions for using the map',' target="intro" ' )\}
-$HR
-END_TEXT
-$appletText =
-appletLink(
-q! archive="/courses/system_html/applets/Image_and_Cursor_All/Image_and_Cursor.jar"
-code="Image_and_Cursor" width = 500 height = 458
-!,
-q!Your browser does not support Java, so nothing is displayed.
-
-
-
-
-
-
-
-!
-);
-sub dist {
- my $ra_pt1 = shift;
- my $ra_pt2 =shift;
- my $conversion = 300 /(145 - 72); # number of km per pixel
- return $conversion* sqrt( ($ra_pt1->[0] - $ra_pt2->[0])**2 + ($ra_pt1->[1] - $ra_pt2->[1])**2);
-}
-
-$kandahar = [132,101];
-$kabul = [209,185];
-$mazur_e_sharif = [170, 243];
-$shindand = [46, 155];
-
-$questions = EV3(
-"$PAR How far is it from Kandahar to Kabul? " , ans_rule(30),
-" $PAR How far is it from Kabul to Mazar-e-Sharif? ", ans_rule(30),
-" $PAR How far is it from Kandahar to Shindand? " , ans_rule(30),
-);
-#TEXT(
-#begintable(2),
-#row( $appletText, $questions),
-#endtable()
-#);
-TEXT($appletText, $questions);
-ANS(num_cmp(dist($kandahar,$kabul), reltol => 3, units=>'km'));
-ANS(num_cmp(dist($kabul, $mazur_e_sharif), reltol => 3, units=>'km'));
-ANS(num_cmp(dist($kandahar,$shindand), reltol => 3, units=>'km'));
-
-
-
-ENDDOCUMENT();
diff --git a/courses.dist/modelCourse/templates/setDemo/liteApplet2.pg b/courses.dist/modelCourse/templates/setDemo/liteApplet2.pg
deleted file mode 100644
index 00b754242d..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/liteApplet2.pg
+++ /dev/null
@@ -1,72 +0,0 @@
-DOCUMENT();
-
-loadMacros("PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
-);
-
-$showPartialCorrectAnswers = 1;
-
-BEGIN_TEXT
-This is a lite applet designed by Frank Wattenberg.
-$BR
-\{htmlLink( '/webwork2_course_files/demoCourse/live_map_instructions.html ',
-'Instructions for using the map',' target="intro" ' )\}
-$HR
-END_TEXT
-TEXT(
-appletLink(
-q! archive="/courses/system_html/applets/Image_and_Cursor_All/Image_and_Cursor.jar"
-code="Image_and_Cursor" width = 500 height = 458
-!,
-q!Your browser does not support Java, so nothing is displayed.
-
-
-
-
-
-
-
-!
-),
-);
-sub dist {
- my $ra_pt1 = shift;
- my $ra_pt2 =shift;
- $conversion = 300 /(145 - 72); # number of km per pixel
- return $conversion * sqrt( ($ra_pt1->[0] - $ra_pt2->[0])**2 + ($ra_pt1->[1] - $ra_pt2->[1])**2);
-}
-@cities = (
- { name => 'Kandahar', location => [132,101] },
- { name => 'Kabul', location => [209,185] },
- { name => 'Mazur e Sharif', location => [170, 243] },
- { name => 'Shindand', location => [46, 155] },
- { name => 'Zaranj', location => [39, 93] }
-);
-@index = NchooseK(scalar(@cities), 3 );
-sub cityName {
- my $index = shift ;
- $cities[$index -1]->{name};
-}
-sub cityLoc {
- my $index = shift;
- $cities[$index-1]->{location};
-}
-
-$conversion = 300 /(145 - 72); # number of km per pixel
-BEGIN_TEXT
-$PAR
-How far is it from \{cityName($index[1])\} to \{cityName($index[2])\}? \{ans_rule(30)\}
-$PAR
-How far is it from \{cityName($index[1])\} to \{cityName($index[3])\}? \{ans_rule(30)\}
-$PAR
-How far is it from \{cityName($index[2])\} to \{cityName($index[3])\}? \{ans_rule(30)\}
-END_TEXT
-
-ANS(num_cmp(dist(cityLoc($index[1]),cityLoc($index[2])), reltol=>3, units=>'km'));
-ANS(num_cmp(dist(cityLoc($index[2]), cityLoc($index[2])), reltol=>3, units=>'km'));
-ANS(num_cmp(dist(cityLoc($index[2]),cityLoc($index[2])), reltol=>3, units=>'km'));
-
-
-
-ENDDOCUMENT();
diff --git a/courses.dist/modelCourse/templates/setDemo/nsc2s10p2.pg b/courses.dist/modelCourse/templates/setDemo/nsc2s10p2.pg
deleted file mode 100644
index 632cad14d7..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/nsc2s10p2.pg
+++ /dev/null
@@ -1,66 +0,0 @@
-#DESCRIPTION
-# Identify the graphs of the function and the derivative
-#ENDDESCRIPTION
-
-#KEYWORDS('derivatives', 'graphs')
-DOCUMENT(); # This should be the first executable line in the problem.
-
-loadMacros(
- "PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
- "PGauxiliaryFunctions.pl",
- "PGgraphmacros.pl"
-);
-
-$showPartialCorrectAnswers = 0;
-
-$a=random(0, 6.3, .1);
-$b=random(1.1, 1.5, .1);
-
-$dom = 4;
-@slice = NchooseK(3,3);
-
-@colors = ("blue", "red", "black");
-@sc = @colors[@slice]; #scrambled colors
-@sa = ('A','B','C')[@slice];
-
-
-$f = "sin($a+$b*cos(x)) for x in <-$dom,$dom> using color:$sc[0] and weight:2";
-$fp = "cos($a+$b*cos(x))*(-$b)*sin(x) for x in <-$dom,$dom> using color=$sc[1] and weight:2";
-$fpp = " -sin($a+$b*cos(x))*$b*$b*sin(x)*sin(x) + cos($a+$b*cos(x))*(-$b)*cos(x) for x in <-$dom,$dom> using color=$sc[2] and weight=2";
-
-$graph = init_graph(-4,-4,4,4,'axes'=>[0,0],'grid'=>[8,8]);
-
-($fRef,$fpRef,$fppRef) = plot_functions( $graph,
- $f,$fp,$fpp
- );
-
-# create labels
-
-$label_point=-0.75;
-$label_f = new Label ( $label_point,&{$fRef->rule}($label_point),$sa[0],"$sc[0]",'left') ;
- # NOTE: $fRef->rule is a reference to the subroutine which calculates the
- # function. It was defined in the output of plot_functions. It is used here
- # to calculate the y value of the label corresponding to the function,
- # and below to find the y values for the labels corresponding to the
- # first and second derivatives.
-
-$label_fp = new Label ( $label_point,&{$fpRef->rule}($label_point),$sa[1],"$sc[1]",'left') ;
-$label_fpp = new Label ( $label_point,&{$fppRef->rule}($label_point),$sa[2],"$sc[2]",'left');
-
-# insert the labels into the graph
-
-$graph->lb($label_f,$label_fp,$label_fpp);
-
-BEGIN_TEXT
-\{ image(insertGraph($graph))\}$BR
-Identify the graphs A (blue), B( red) and C (green) as the graphs of a function and its
-derivatives:$PAR
-\{ans_rule(4)\} is the graph of the function $PAR
-\{ans_rule(4)\} is the graph of the function's first derivative $PAR
-\{ans_rule(4)\} is the graph of the function's second derivative $PAR
-END_TEXT
-ANS(std_str_cmp_list( @sa ) );
-
-ENDDOCUMENT(); # This should be the last executable line in the problem.
diff --git a/courses.dist/modelCourse/templates/setDemo/paperHeaderFile1.pg b/courses.dist/modelCourse/templates/setDemo/paperHeaderFile1.pg
deleted file mode 100644
index 1b9210ae6e..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/paperHeaderFile1.pg
+++ /dev/null
@@ -1,27 +0,0 @@
-##Problem set header for set 0, Spring 1999
-
-&DOCUMENT;
-
-loadMacros(
-"PG.pl",
-"PGbasicmacros.pl",
-"PGchoicemacros.pl",
-"PGanswermacros.pl"
-);
-
-TEXT(EV2(<' ) \}
-END_OF_TEXT
-
-##
-
-TEXT("$BR$BR",ans_rule(30),"$BR");
-ANS( CAPA_ans( $v0 , 'format' => "%0.2e" , 'sig' => '3 PLUS 13', 'reltol' => 1 , 'wgt' => $prob_val , 'tries' => $prob_try , 'unit' => 'm/s' ) );
-ENDDOCUMENT();
-#####################
-
-###Error: $smallg not defined in this file
-###Error: $degrad not defined in this file
-###Error: $degrad not defined in this file
-###Error: $degrad not defined in this file
-###Error: $deg_u not defined in this file
-###Error: $m_u not defined in this file
-###Error: $m_u not defined in this file
-###Error: $prob_val not defined in this file
-###Error: $prob_try not defined in this file
-
-#####################
-
-
-#################################################
-## Processing time = 0 secs ( 0.56 usr 0.00 sys = 0.56 cpu)
-#################################################
diff --git a/courses.dist/modelCourse/templates/setDemo/prob5.pg b/courses.dist/modelCourse/templates/setDemo/prob5.pg
deleted file mode 100644
index 670194a3fe..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/prob5.pg
+++ /dev/null
@@ -1,136 +0,0 @@
-&DOCUMENT();
-loadMacros("PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
-);
-#########################################################
-# allow the student to change the seed for this problem.
-
-$newProblemSeed = ( defined( ${$inputs_ref}{'newProblemSeed'} ) )? ${$inputs_ref}{'newProblemSeed'} : $problemSeed;
-$PG_random_generator->srand($newProblemSeed);
-BEGIN_TEXT
-To see a different version of the problem change
-the problem seed and press the 'Submit Answer' button below.$PAR Problem Seed:
-\{ MODES(
-TeX => qq! Change the problem seed to change the problem:$problemSeed!,
-Latex2HTML => qq! Change the problem seed to change the problem:
- \begin{rawhtml}
-
- \end{rawhtml}!,
-HTML => qq! !
-)
-\}
-END_TEXT
-#########################################################
-# define function to be evaluated
-$a= random(1,3,1);
-$b= random(-4,4,.1);
-$c = random(-4,4,1);
-$x0=random(-2,2,1);
-$function = FEQ(" ${a}x^2+${b}x +$c ");
-sub fp { # define a subroutine to calculate the derivative
- my $x = shift;
- 2*$a*$x+$b;
-}
-$ans = fp($x0);
-HEADER_TEXT(<
-
-
-
-
-
-
-EOF
-
-TEXT(MODES(
-TeX => '',
-Latex2HTML => "\begin{rawhtml} This problem requires that Java
- Script be enabled \end{rawhtml}",
-HTML => " This problem requires that Java Script be
- enabled "
-));
-
-BEGIN_TEXT
-$PAR
-This problem illustrates how you can embed JavaScript code in a WeBWorK example
-to create an interactive homework problem that could never be provided by a text book.
-$PAR
-WeBWorK can use existing $BBOLD JavaScript$EBOLD and $BBOLD Java $EBOLD code to augment its capabilities.
-$HR
-$PAR
-By typing any value x into the left hand window and pressing the --f--\(>\) button
-you can determine the value of f(x).
-$PAR
-Using this 'oracle' function, calculate the derivative of \( f \) at x=$x0.
-$PAR
-\(f'($x0) =\) \{ans_rule(20) \} You can use a
-\{htmlLink(alias('calc.html'), "calculator" ,q!TARGET = "calculator"!) \}
-
-$PAR
-END_TEXT
-
-$javaScript =<
-
-
-
-ENDOFSCRIPT
-
-TEXT(M3(
- " \fbox{ The java Script calculator was displayed here
- }",
- "\begin{rawhtml} $javaScript \end{rawhtml}",
- $javaScript
- ));
-
-
-
-ANS(std_num_cmp($ans,1,"%0.14g") ); #We are allowing 1 percent error for the answer.
-
-
-&ENDDOCUMENT;
-
diff --git a/courses.dist/modelCourse/templates/setDemo/prob6b.pg b/courses.dist/modelCourse/templates/setDemo/prob6b.pg
deleted file mode 100644
index 60cc2f3e5f..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/prob6b.pg
+++ /dev/null
@@ -1,86 +0,0 @@
-DOCUMENT();
-
-loadMacros("PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
-);
-#<<<#########################################################
-# allow the student to change the seed for this problem.
-
-$newProblemSeed = ( defined( ${$inputs_ref}{'newProblemSeed'} ) )?
-${$inputs_ref}{'newProblemSeed'} : $problemSeed;
-$PG_random_generator->srand($newProblemSeed);
-BEGIN_TEXT
-To see a different version of the problem change
-the problem seed and press the 'Submit Answer' button below.$PAR Problem Seed:
-\{ MODES(
-TeX => qq! Change the problem seed to change the problem:$problemSeed!,
-Latex2HTML => qq! Change the problem seed to change the problem:
- \begin{rawhtml}
-
- \end{rawhtml}!,
-HTML => qq! !
-)
-\}
-END_TEXT
-#########################################################>>>
-$p = random(2,9,1); # multiplier
-$p2 = ( $p % 2 == 0) ? 2*$p : $p;
-
-# The link to the java applet is hard wired to use the java applet
-# served from the University of Rochester WeBWorK machine.
-# It is possible to set this up so that the java applet is served
-# from any machine
-# For details use the Feedback button to contact the authors of WeBWorK
-
-BEGIN_TEXT
-This problem requires a browser capable of running Java.
-
-$PAR
-This problem illustrates how you can Java applets in a WeBWorK example.
-$PAR
-This polar coordinate grapher was constructed at the Mathematics Department
-of The Johns Hopkins University and the applet is being served from their computer.
-$PAR
-WeBWorK can use existing $BBOLD JavaScript$EBOLD and $BBOLD Java $EBOLD code to
-augment its capabilities.
-$HR
-END_TEXT
-TEXT(MODES(
-TeX => "\fbox{The Johns Hopkins University Mathematics Department's
- polar graph plotting applet goes here}",
-HTML => qq{
-
-
-
-
-
-
-},
-Latex2HTML => qq!\begin{rawhtml}
-
-
-
-
-
- \end{rawhtml}
-!
-));
-
-BEGIN_TEXT
-$PAR
-For what value of \( k \) does the graph of \( r = \cos(kt) \) look
-like a rose with $p2 petals?
-$BR
-\(k = \) \{ ans_rule(20) \} ;
-
-$PAR
-
-END_TEXT
-
-ANS(num_cmp($p) );
-
-ENDDOCUMENT();
-
diff --git a/courses.dist/modelCourse/templates/setDemo/s2_2_1.pg b/courses.dist/modelCourse/templates/setDemo/s2_2_1.pg
deleted file mode 100644
index b01f44e3e6..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/s2_2_1.pg
+++ /dev/null
@@ -1,53 +0,0 @@
-##DESCRIPTION
-##KEYWORDS('derivatives')
-## Find a derivative of a polynomial, evaluate it at a point
-##ENDDESCRIPTION
-
-DOCUMENT(); # This should be the first executable line in the problem.
-loadMacros(
- "PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
- "PGauxiliaryFunctions.pl"
-);
-
-$showPartialCorrectAnswers = 1;
-
-$a1 = random(2,7,1);
-$b1 = random(2,12,1);
-$c1 = random(1,40,1);
-$x1 = random(1,5,1);
-$deriv1 = 2*$a1*$x1-$b1;
-$funct1 = "2*$a1*x-$b1";
-
-BEGIN_TEXT
-If \( f(x) = $a1 x^2 - $b1 x -$c1 \), find \( f'( x ) \).
-$BR $BR \{ans_rule(48) \}
-$BR $BR
-END_TEXT
-
-$ans = $funct1;
-&ANS(fun_cmp($ans));
-
-$a1_2 = 2*$a1;
-
-BEGIN_TEXT
-Find \( f'( $x1 ) \).
-$BR $BR \{ans_rule(48) \}
-$BR $BR
-END_TEXT
-
-$ans = $deriv1;
-&ANS(num_cmp($ans));
-
-&SOLUTION(EV3(<<'EOT'));
-$SOL $BR
-In general the derivative of \( x^n \) is \( nx^{n-1} \). Using this (and the
-basic sum and constant multiple rules) we find the derivative of $BR
-\(${a1}x^2 - ${b1}x -$c1 \quad \) is $BR \( ${a1_2}x - $b1 \).$BR $BR
-
-To find the derivative at \($x1\) we just have to evaluate \( f'( x ) \) at
-\( x = $x1) \), i.e. \( ${a1_2}\cdot$x1 - $b1 \) or $ans.
-EOT
-
-ENDDOCUMENT(); # This should be the last executable line in the problem.
diff --git a/courses.dist/modelCourse/templates/setDemo/sample_myown_ans.pg b/courses.dist/modelCourse/templates/setDemo/sample_myown_ans.pg
deleted file mode 100644
index 3b7da75f8f..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/sample_myown_ans.pg
+++ /dev/null
@@ -1,85 +0,0 @@
-DOCUMENT();
-
-loadMacros(
- "PGbasicmacros.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl"
-);
-
-$showPartialCorrectAnswers = 1;
-
-BEGIN_TEXT
- This problem demonstrates how you can write your own procedure to check answers.
- The procedure is embedded right in the problem. If you wanted to use it for several
- problems, you could put it in a file similar to "PGanswermacros.pl" and load it into
- the problem.
-
- This problem asks you to enter a palindrome, a word, number, or phrase that is the same
- when read backwards or forward. For example, madam or Hannah. For us a standard
- palindrome will ignore spaces and case, but a strict palindrome will not. So e.g. Hannah
- is a standard but not a strict palindrome. We will write a test for both types. $BR $BR
- Enter a standard palindrome such as "Hannah", "1234321", or "Mom". $BR
- This uses std${US}palindrome${US}test $BR
- \{ans_rule(60) \}
-END_TEXT
-
-
-$std_palindrome_test = sub {
- my $in = shift @_;
- my $normalizedCorrectAnswer = "There are many correct answers, e.g. Hannah";
- $in =~ s|~~s+||g; # remove all spaces
- ## use ~~ inplace of perl's backslash in problems
- $in = uc $in; # Make letters uppercase
- ## use ~~ inplace of perl's backslash in problems
- my $reverse = reverse $in;
- my $correctQ = ($in eq $reverse) ? 1: 0;
- my $ansMsg = '';
- unless ($in =~ m|~~S|) {
- $correctQ = 0;
- $ansMsg = 'An empty string is not accepted as a palindrome';
- }
- my $rh_answer = new AnswerHash( score => $correctQ,
- correct_ans => $normalizedCorrectAnswer,
- student_ans => $in,
- ans_message => $ansMsg,
- type => 'custom'
- );
- $rh_answer;
-
-};
-
-ANS($std_palindrome_test);
-
-
-BEGIN_TEXT;
-$PAR
-Now enter a strict palindrome such as "1234321", or "mom". $BR
-This uses strict${US}palindrome${US}test $BR
-\{ans_rule(60) \}
-END_TEXT
-
-$strict_palindrome_test = sub {
- my $in = shift @_;
- my $normalizedCorrectAnswer = "There are many correct answers, e.g. HannaH";
- $in =~ s/~~s*$//; # remove trailing whitespace ## use ~~ inplace of perl's backslash in problems
- $in =~ s/^~~s*//; # remove initial spaces ## use ~~ inplace of perl's backslash in problems
- my $reverse = reverse $in;
- my $correctQ = ($in eq $reverse) ? 1: 0;
- my $ansMsg = '';
- unless ($in =~ m|~~S|) {
- $correctQ = 0;
- $ansMsg = 'An empty string is not accepted as a palindrome';
- }
-
- my $rh_answer = {score => $correctQ,
- correct_ans => $normalizedCorrectAnswer,
- student_ans => $in,
- ans_message => $ansMsg,
- type => 'custom'
- };
- $rh_answer;
-};
-
-ANS($strict_palindrome_test);
-
-ENDDOCUMENT();
diff --git a/courses.dist/modelCourse/templates/setDemo/sample_units_ans.pg b/courses.dist/modelCourse/templates/setDemo/sample_units_ans.pg
deleted file mode 100644
index a72a70a5a9..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/sample_units_ans.pg
+++ /dev/null
@@ -1,38 +0,0 @@
-DOCUMENT();
-loadMacros( "PGbasicmacros.pl",
- "PGauxiliaryFunctions.pl",
- "PGchoicemacros.pl",
- "PGanswermacros.pl",
-);
-
-$showPartialCorrectAnswers = 1;
-$showHint =0;
-
-$fx = random( 2.1, 6.0 , 0.1) ;
-$fy = random( 3.1, 8.0 , 0.1) ;
-$ansxy = sqrt($fx * $fx + $fy * $fy);
-$anscm = $ansxy*100;
-
-BEGIN_TEXT
-This problem demonstrates how WeBWorK handles
-numerical answers involving units. WeBWorK can handle all units that
-are used in elementary physics courses.
-See \{ htmlLink("http://webwork.maa.org/wiki/Units","answers with units") \}
-for more details. $PAR
-
-Two perpendicular sides of a triangle are $fx m and
-$fy m long respectively.
-What is the length of the third side of the triangle? $BR$BR
-You can answer this in terms of m's, cm's, km's, in's, ft, etc. but you must enter the units. $BR$BR
-Click "Hint" below if you don't remember the Pythagorean theorem.
-$BR$BR
-\{ans_rule(40) \}
-END_TEXT
-
-HINT(EV3(<<'EOT'));
-Remembering the Pythagorean theorem \( A^2 +B^2 = C^2 \), you can enter
-sqrt(${fx}${CARET}2 + ${fy}${CARET}2) m or \{spf($ansxy, "%0.2f" )\} m or \{spf($anscm, "%0.2f" )\} cm or ...
-EOT
-
-ANS(num_cmp("$ansxy", units => 'm'));
-ENDDOCUMENT()
diff --git a/courses.dist/modelCourse/templates/setDemo/screenHeaderFile1.pg b/courses.dist/modelCourse/templates/setDemo/screenHeaderFile1.pg
deleted file mode 100644
index ea6c0bef30..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/screenHeaderFile1.pg
+++ /dev/null
@@ -1,77 +0,0 @@
-##Screen set header for set 0, Fall 1998
-
-&DOCUMENT;
-
-loadMacros(
-"PG.pl",
-"PGbasicmacros.pl",
-"PGchoicemacros.pl",
-"PGanswermacros.pl"
-);
-
-
-
-BEGIN_TEXT
-This is a demonstration set designed to illustrate the range of types of questions which can be asked using WeBWorK rather than to illustrate a typical calculus problem set.
-
-$PAR
-$BBOLD 1. Simple numerical problem. $EBOLD A simple problem requiring a numerical answer. It illustrates how one can allow WeBWorK to calculate answers from formulas (e.g. an answer such as sqrt(3^2 +4^2) can be entered instead of the answer 5.). It also shows
-an example of feedback on the correctness of each answer, rather than grading the entire problem.
-$PAR
-$BBOLD 2. Graphs and limits. $EBOLD The graph in this example is constructed on the fly. From the graph a student is supposed to determine the values and limits of the function at various points. The immediate feedback on this problem is particularly useful, since students often make unconcious mistakes.
-$PAR
-$BBOLD 3. Derivatives. $EBOLD An example of checking answers which are formulas, rather than numbers.
-$PAR
-$BBOLD 4. Anti-derivatives. $EBOLD This example will accept any anti-derivative, adjusting for the fact that the answer is only defined up to a constant.
-$PAR
-$BBOLD 5. Answers with units. $EBOLD Try entering the answer to this question in meters (m) and also centimeters (cm).
-$PAR
-$BBOLD 6. A physics example. $EBOLD Includes a static picture.
-$PAR
-$BBOLD 7. More graphics. $EBOLD An example of on-the-fly graphics. Select the graph of f, it's derivative and it's second derivatives.
-$PAR
-$BBOLD 8. JavaScript example. $EBOLD I'm particularly fond of this example. The computer provides an "oracle" function: give it a number \(x\) and it will provide you with the value \(f(x)\) of the function at \(x\). Using this, calculate the value of the derivative of \(f\) at 2. (i.e. \(f'(2)\) ). Students are forced to use the Newton quotient, since there are no formulas to work with. I don't think this problem could be asked as written homework.
-$PAR
-$BBOLD 9. Java example. $EBOLD This gives an example of incorporating a java applet which can be used experimentally to determine answers for WeBWorK questions. This example is of historical interest since it comes from the first site after Rochester, Johns Hopkins University, to use WeBWorK. It currently gives an example of what happens when a WeBWorK problem called an applet residing on a server that no longer exists.
-$PAR
-$BBOLD 10. Palindrome. $EBOLD To answer this problem enter any palindrome. This problem illustrates the power of the "answer-evaluator" model. For each problem the problem designer writes a function which accepts a student's answer and produces a 0 or 1 (for incorrect or correct). Usually this is done by comparing with an answer given by the problem designer, but in this case the function checks if the answer is the same forward and backward.
-$PAR
-END_TEXT
-
-
-
-BEGIN_TEXT
-$HR
-
-Use this box to give information about this problem
-set. Typical information might include some of these facts:
-$PAR
-WeBWorK assignment number $setNumber closes on : $formattedDueDate.
-
-
-$PAR
-The primary purpose of WeBWorK is to let you know if you are getting the right answer or to alert
-you if you get the wrong answer. Usually you can attempt a problem as many times as you want before
-the close date. However, if you are having trouble figuring out your error, you should
-consult the book, or ask a fellow student, one of the TA's or
-your professor for help. Don't spend a lot of time guessing -- it's not very efficient or effective.
-$PAR
-
-You can use the Feedback button on each problem
-page to send email to the professors.
-$PAR
-Give 4 or 5 significant digits for (floating point) numerical answers.
-For most problems when entering numerical answers, you can if you wish
-enter elementary expressions such as 2^3 instead of 8, sin(3*pi/2) instead
-of -1, e^(ln(2)) instead of 2,
-(2+tan(3))*(4-sin(5))^6-7/8 instead of 27620.3413, etc.
-$PAR
- Here's the
-\{ htmlLink(qq!http://webwork.maa.org/wiki/Available_Functions!,"list of the functions") \}
- which WeBWorK understands.
-
-Along with the \{htmlLink(qq!http://webwork.maa.org/wiki/Units!, "list of units")\} which WeBWorK understands. This can be useful in
-physics problems.
-END_TEXT
-
-ENDDOCUMENT();
diff --git a/courses.dist/modelCourse/templates/setDemo/srw1_9_4.pg b/courses.dist/modelCourse/templates/setDemo/srw1_9_4.pg
deleted file mode 100644
index c4c663dcda..0000000000
--- a/courses.dist/modelCourse/templates/setDemo/srw1_9_4.pg
+++ /dev/null
@@ -1,110 +0,0 @@
-##DESCRIPTION
-## find distance between two points, find coordinates of the midpoint of
-## a line segment connecting them
-##ENDDESCRIPTION
-
-##KEYWORDS('algebra', 'coordinate geometry', 'distance', 'midpoint')
-
-DOCUMENT(); # This should be the first executable line in the problem.
-
-loadMacros(
-"PG.pl",
-"PGbasicmacros.pl",
-"PGchoicemacros.pl",
-"PGanswermacros.pl",
-"PGauxiliaryFunctions.pl"
-);
-
-$showPartialCorrectAnswers = 1;
-
-#install_problem_grader(~~&std_problem_grader); ##uncomment to use std grader
-#install_problem_grader(~~&custom_problem_grader); ##uncomment to use custom grader
-
-
-$x1 = random(1,5,1);
-$y1 = random(-5,-1,1);
-$x2 = random(-10,-3,1);
-$y2 = random(-9,-2,1);
-$len1 = sqrt(($x1-$x2)**2 + ($y1-$y2)**2);
-$midx = ($x1+$x2)/2;
-$midy = ($y1+$y2)/2;
-
-BEGIN_TEXT
-Consider the two points \( ($x1 ,$y1 )\) and \( ($x2 ,$y2 )\).
-The distance between them is:$BR
-\{ans_rule(30) \}
-$BR
-END_TEXT
-
-$ans = $len1;
-&ANS(std_num_cmp($ans));
-
-BEGIN_TEXT
-The x co-ordinate of the midpoint of the line
-segment that joins them is:\{ans_rule(20) \}
-$BR
-END_TEXT
-$ans = $midx;
-&ANS(std_num_cmp($ans));
-
-BEGIN_TEXT
-The y co-ordinate of the midpoint of the line segment that joins them is:
-\{ans_rule(20) \}
-$BR
-END_TEXT
-$ans = $midy;
-&ANS(std_num_cmp($ans));
-
-
-sub custom_problem_grader {
- my $rh_evaluated_answers = shift;
- my $rh_problem_state = shift;
- my %form_options = @_;
- my %evaluated_answers = %{$rh_evaluated_answers};
- # The hash $rh_evaluated_answers typically contains:
- # 'answer1' => 34, 'answer2'=> 'Mozart', etc.
-
- # By default the old problem state is simply passed back out again.
- my %problem_state = %$rh_problem_state;
-
-
- # %form_options might include
- # The user login name
- # The permission level of the user
- # The studentLogin name for this psvn.
- # Whether the form is asking for a refresh or is submitting a new answer.
-
- # initial setup of the answer
- my $total=0;
- my %problem_result = ( score => 0,
- errors => '',
- type => 'custom_problem_grader',
- msg => 'Part 1 is worth 50% and parts 2 and 3 are worth 25% each.',
- );
-
- # Return unless answers have been submitted
- unless ($form_options{answers_submitted} == 1) {
- return(~~%problem_result,~~%problem_state);
- }
- # Answers have been submitted -- process them.
-
- $total += .5*($evaluated_answers{'AnSwEr1'}->{score});
- $total += .25*($evaluated_answers{'AnSwEr2'}->{score});
- $total += .25*($evaluated_answers{'AnSwEr3'}->{score});
-
-
- $problem_result{score} = $total;
- # increase recorded score if the current score is greater.
- $problem_state{recorded_score} = $problem_result{score} if $problem_result{score} > $problem_state{recorded_score};
-
-
- $problem_state{num_of_correct_ans}++ if $total == 1;
- $problem_state{num_of_incorrect_ans}++ if $total < 1 ;
- (~~%problem_result, ~~%problem_state);
-
-}
-
-
-ENDDOCUMENT(); # This should be the last executable line in the problem.
-
-
diff --git a/docker-config/docker-compose.dist.yml b/docker-config/docker-compose.dist.yml
index fa6a02b003..5ef4bb91b8 100644
--- a/docker-config/docker-compose.dist.yml
+++ b/docker-config/docker-compose.dist.yml
@@ -96,7 +96,7 @@ services:
# If you would like a 1 stage build process comment out the next line, and just run "docker-compose build".
dockerfile: DockerfileStage2
# For the 2 stage build process run
- # docker build --tag webwork-base:forWW220 -f DockerfileStage1 .
+ # docker build --tag webwork-base:forWW221 -f DockerfileStage1 .
# and then
# docker-compose build
# When rebuilding to get updated images add the "--no-cache" option to both commands.
diff --git a/docker-config/idp/README.md b/docker-config/idp/README.md
index 586b9d9953..90da62cd62 100644
--- a/docker-config/idp/README.md
+++ b/docker-config/idp/README.md
@@ -27,7 +27,7 @@ the port as needed.
A docker service that implements a SAML2 identity provider is provided in the
`docker-compose.yml.dist` file. To start this identity provider along with the
rest of webwork2, add the `--profile saml2dev` argument to docker compose as in
-the following exmaple.
+the following example.
```bash
docker compose --profile saml2dev up
@@ -165,7 +165,7 @@ identity provider is accessible to webwork2.
The user was verified by the identity provider but did not have a corresponding
user account in the Webwork course. The Webwork user account needs to be created
-separately as the Saml2 autentication module does not do user provisioning.
+separately as the Saml2 authentication module does not do user provisioning.
### The WeBWorK service provider does not appear in the service provider Federation tab
diff --git a/htdocs/generate-assets.js b/htdocs/generate-assets.js
index 81ee6c7b10..f264601394 100755
--- a/htdocs/generate-assets.js
+++ b/htdocs/generate-assets.js
@@ -2,7 +2,8 @@
/* eslint-env node */
-const yargs = require('yargs');
+const yargs = require('yargs/yargs');
+const { hideBin } = require('yargs/helpers');
const chokidar = require('chokidar');
const path = require('path');
const { minify } = require('terser');
@@ -14,7 +15,7 @@ const postcss = require('postcss');
const rtlcss = require('rtlcss');
const cssMinify = require('cssnano');
-const argv = yargs
+const argv = yargs(hideBin(process.argv))
.usage('$0 Options')
.version(false)
.alias('help', 'h')
@@ -110,7 +111,11 @@ const processFile = async (file, _details) => {
// This works for both sass/scss files and css files.
let result;
try {
- result = sass.compile(filePath, { sourceMap: argv.enableSourcemaps });
+ result = sass.compile(filePath, {
+ sourceMap: argv.enableSourcemaps,
+ // Silence warnings about bootstrap usage of deprecated sass methods.
+ silenceDeprecations: ['import', 'global-builtin', 'color-functions']
+ });
} catch (e) {
console.log(`\x1b[31mIn ${file}:`);
console.log(`${e.message}\x1b[0m`);
diff --git a/htdocs/js/ActionTabs/actiontabs.js b/htdocs/js/ActionTabs/actiontabs.js
index 50c43da164..0662a455c7 100644
--- a/htdocs/js/ActionTabs/actiontabs.js
+++ b/htdocs/js/ActionTabs/actiontabs.js
@@ -6,6 +6,7 @@
actionLink.addEventListener('show.bs.tab', () => {
if (takeAction) takeAction.value = actionLink.textContent;
if (currentAction) currentAction.value = actionLink.dataset.action;
+ takeAction.formNoValidate = actionLink.dataset.noValidate ? true : false;
});
});
diff --git a/htdocs/js/DatePicker/datepicker.js b/htdocs/js/DatePicker/datepicker.js
index 193ae9b6a1..2ae1002c15 100644
--- a/htdocs/js/DatePicker/datepicker.js
+++ b/htdocs/js/DatePicker/datepicker.js
@@ -119,13 +119,14 @@
],
onClick: (index, fp) => {
if (index === 0) {
- const today = new Date();
- // If there isn't a selected date, then use 12:00 am on the current date.
- const selectedDate = fp.selectedDates[0] ?? new Date(new Date().toDateString());
- selectedDate.setFullYear(today.getFullYear());
- selectedDate.setMonth(today.getMonth());
- selectedDate.setDate(today.getDate());
- fp.setDate(selectedDate, true);
+ // The initial date represents 12:00 am on the current date.
+ const today = new Date(new Date().toDateString());
+ if (fp.selectedDates[0]) {
+ today.setHours(fp.selectedDates[0].getHours());
+ today.setMinutes(fp.selectedDates[0].getMinutes());
+ today.setSeconds(fp.selectedDates[0].getSeconds());
+ }
+ fp.setDate(today, true);
} else if (index === 1) {
fp.setDate(new Date(), true);
}
diff --git a/htdocs/js/GatewayQuiz/gateway.js b/htdocs/js/GatewayQuiz/gateway.js
index e1241e0a94..ee4024bd7f 100644
--- a/htdocs/js/GatewayQuiz/gateway.js
+++ b/htdocs/js/GatewayQuiz/gateway.js
@@ -188,6 +188,41 @@
if (actuallySubmit) return;
+ const inputs = Array.from(document.querySelectorAll('input, select'));
+
+ // All problem numbers are represented by a probstatus hidden input. Use those to determine the problem
+ // numbers of problems in the test. Note that problem numbering displayed on the page will not match these
+ // numbers in the cases that the test definition has non-consecutive numbering or that problem order is
+ // randomized. But the problem numbering will always match the quiz prefix numbering.
+ const problems = [];
+ for (const input of inputs.filter((i) => /^probstatus\d*/.test(i.name))) {
+ problems[parseInt(input.name.replace('probstatus', ''))] = {};
+ }
+
+ // Determine which questions have been answered. Note that there can be multiple inputs for a
+ // given question (for example for checkbox or radio answers).
+ for (const input of inputs.filter(
+ (i) => /Q\d{4}_/.test(i.name) && !/^MaThQuIlL_/.test(i.name) && !/^previous_/.test(i.name)
+ )) {
+ const answered =
+ input.type === 'radio' || input.type === 'checkbox' ? !!input.checked : /\S/.test(input.value);
+ const match = /Q(\d{4})_/.exec(input.name);
+ const problemNumber = parseInt(match?.[1] ?? '0');
+ if (!(input.name in problems[problemNumber])) problems[problemNumber][input.name] = answered;
+ else if (answered) problems[problemNumber][input.name] = 1;
+ }
+
+ // Determine if there are any unanswered questions in each problem.
+ let numProblemsWithUnanswered = 0;
+ for (const problem of problems) {
+ // Skip problem 0 and any problems that don't exist in the test
+ // due to non-consecutive numbering in the test definition.
+ if (!problem) continue;
+
+ if (!Object.keys(problem).length || !Object.values(problem).every((answered) => answered))
+ ++numProblemsWithUnanswered;
+ }
+
// Prevent the gwquiz form from being submitted until after confirmation.
evt.preventDefault();
@@ -219,8 +254,25 @@
const modalBody = document.createElement('div');
modalBody.classList.add('modal-body');
- const modalBodyContent = document.createElement('div');
+ if (numProblemsWithUnanswered) {
+ const modalSecondaryContent = document.createElement('div');
+ modalSecondaryContent.classList.add('mb-3');
+ modalSecondaryContent.textContent =
+ (numProblemsWithUnanswered > 1
+ ? submitAnswers.dataset.unansweredQuestionsMessage
+ ? submitAnswers.dataset.unansweredQuestionsMessage.replace('%d', numProblemsWithUnanswered)
+ : `There are ${numProblemsWithUnanswered} problems with unanswered questions.`
+ : (submitAnswers.dataset.unansweredQuestionMessage ??
+ 'There is a problem with unanswered questions.')) +
+ ' ' +
+ (submitAnswers.dataset.returnToTestMessage ??
+ 'Are you sure you want to grade the test? ' +
+ 'Select "No" if you would like to return to the test to enter more answers.');
+ modalBody.append(modalSecondaryContent);
+ }
+
+ const modalBodyContent = document.createElement('div');
modalBodyContent.textContent = submitAnswers.dataset.confirmDialogMessage;
modalBody.append(modalBodyContent);
diff --git a/htdocs/js/GatewayQuiz/gateway.scss b/htdocs/js/GatewayQuiz/gateway.scss
index f7d450dff6..bb294426eb 100644
--- a/htdocs/js/GatewayQuiz/gateway.scss
+++ b/htdocs/js/GatewayQuiz/gateway.scss
@@ -1,13 +1,5 @@
/* gateway styles */
-div.gwMessage {
- background-color: #ffeeaa;
- box-shadow: 3px 3px 3px darkgray;
- margin: 0 0 1rem 0;
- padding: 0.25rem;
- border-radius: 3px;
-}
-
#gwTimer {
position: sticky;
width: 15em;
@@ -60,7 +52,12 @@ table.attemptResults {
border: 1px solid #ddd;
border-radius: 3px;
- h2 {
+ [data-bs-theme='dark'] & {
+ border-color: #555;
+ background-color: var(--bs-primary-bg-subtle, 'black');
+ }
+
+ h2.gw-problem-number {
display: inline-block;
font-size: 16px;
margin-right: 5px;
@@ -85,12 +82,17 @@ table.attemptResults {
}
colgroup.page {
- border-left: solid 1pt black;
- border-right: solid 1pt black;
+ border-left: solid 1pt var(--bs-emphasis-color, black);
+ border-right: solid 1pt var(--bs-emphasis-color, black);
}
.page.active {
background-color: #ffeeaa;
+
+ [data-bs-theme='dark'] & {
+ color: white;
+ background-color: #80690a;
+ }
}
}
@@ -98,8 +100,9 @@ div.gwDivider {
margin: 0px 0px 10px 0px;
}
-/* Override the pg style so that the problem-content is not offset in gateway quizzes. */
+/* Override the pg style so that the problem-content is not offset in gateway quizzes and force a light color scheme. */
.problem-content {
+ color-scheme: light;
padding: unset;
background-color: unset;
border: unset;
diff --git a/htdocs/js/MathJaxConfig/bs-color-scheme.js b/htdocs/js/MathJaxConfig/bs-color-scheme.js
new file mode 100644
index 0000000000..589775edfa
--- /dev/null
+++ b/htdocs/js/MathJaxConfig/bs-color-scheme.js
@@ -0,0 +1,87 @@
+if (MathJax.loader) MathJax.loader.checkVersion('[bs-color-scheme]', '4.1.1', 'extension');
+
+const switchToBSStyle = (obj, key = '@media (prefers-color-scheme: dark)') => {
+ obj["[data-bs-theme='dark']"] = obj[key];
+ delete obj[key];
+ obj["[data-bs-theme='light']"] = structuredClone(obj);
+};
+
+for (const [immediate, extension, ready] of [
+ [
+ MathJax._.ui?.dialog,
+ 'core',
+ () => {
+ const { DraggableDialog } = MathJax._.ui.dialog.DraggableDialog;
+ switchToBSStyle(DraggableDialog.styles);
+ }
+ ],
+ [
+ MathJax._.a11y?.explorer,
+ 'a11y/explorer',
+ () => {
+ const Region = MathJax._.a11y.explorer.Region;
+ for (const region of ['LiveRegion', 'HoverRegion', 'ToolTip']) {
+ if (':root' in Region[region].style.styles) {
+ Region[region].style.styles["[data-bs-theme='light']"] = Region[region].style.styles[':root'];
+
+ // The variable --mjx-bg1-color is defined to be 'rgba(var(--mjx-bg-blue), var(--mjx-bg-alpha))'.
+ // I suspect this is a typo as the variable -mjx-bg-alpha is not defined anywhere. In any case this
+ // change is needed to get the correct background color on the focused element in the explorer.
+ Region[region].style.styles["[data-bs-theme='light']"]['--mjx-bg1-color'] =
+ 'rgba(var(--mjx-bg-blue), var(--mjx-bg1-alpha))';
+ }
+ Region[region].style.styles["[data-bs-theme='dark']"] =
+ Region[region].style.styles['@media (prefers-color-scheme: dark)'];
+ if (':root' in Region[region].style.styles["[data-bs-theme='dark']"]) {
+ Object.assign(
+ Region[region].style.styles["[data-bs-theme='dark']"],
+ Region[region].style.styles["[data-bs-theme='dark']"][':root']
+ );
+ delete Region[region].style.styles["[data-bs-theme='dark']"][':root'];
+ }
+ Region[region].style.styles['@media (prefers-color-scheme: dark)'] = {};
+ }
+ Region.LiveRegion.style.styles['@media (prefers-color-scheme: dark)']['mjx-ignore'] = { ignore: 1 };
+ MathJax.startup.extendHandler((handler) => {
+ switchToBSStyle(
+ handler.documentClass.speechStyles,
+ '@media (prefers-color-scheme: dark) /* explorer */'
+ );
+ return handler;
+ });
+ }
+ ],
+ [
+ MathJax._.output?.chtml,
+ 'output/chtml',
+ () => {
+ const { CHTML } = MathJax._.output.chtml_ts;
+ switchToBSStyle(CHTML.commonStyles);
+ const { ChtmlMaction } = MathJax._.output.chtml.Wrappers.maction;
+ switchToBSStyle(ChtmlMaction.styles, '@media (prefers-color-scheme: dark) /* chtml maction */');
+ }
+ ],
+ [
+ MathJax._.output?.svg,
+ 'output/svg',
+ () => {
+ const { SVG } = MathJax._.output.svg_ts;
+ switchToBSStyle(SVG.commonStyles);
+ const { SvgMaction } = MathJax._.output.svg.Wrappers.maction;
+ switchToBSStyle(SvgMaction.styles, '@media (prefers-color-scheme: dark) /* svg maction */');
+ }
+ ]
+]) {
+ if (immediate) {
+ ready();
+ } else {
+ const config = MathJax.config.loader;
+ config[extension] ??= {};
+ config[extension].extraLoads ??= [];
+ const check = config[extension].checkReady;
+ config[extension].checkReady = async () => {
+ if (check) await check();
+ return ready();
+ };
+ }
+}
diff --git a/htdocs/js/MathJaxConfig/mathjax-config.js b/htdocs/js/MathJaxConfig/mathjax-config.js
index 937f33393f..2b42f0928f 100644
--- a/htdocs/js/MathJaxConfig/mathjax-config.js
+++ b/htdocs/js/MathJaxConfig/mathjax-config.js
@@ -1,7 +1,10 @@
if (!window.MathJax) {
window.MathJax = {
- tex: { packages: { '[+]': ['noerrors'] } },
- loader: { load: ['input/asciimath', '[tex]/noerrors'] },
+ tex: { packages: { '[+]': webworkConfig?.showMathJaxErrors ? [] : ['noerrors'] } },
+ loader: {
+ load: ['input/asciimath', '[tex]/noerrors', '[bs-color-scheme]'],
+ paths: { 'bs-color-scheme': webworkConfig?.mathJaxBSColorSchemeUrl ?? './bs-color-scheme.js' }
+ },
startup: {
ready() {
const AM = MathJax.InputJax.AsciiMath.AM;
@@ -98,24 +101,6 @@ if (!window.MathJax) {
AM.symbols.splice(i, 0, { input: trigger, ...newTriggers[trigger].symbols });
}
- // The following is a workaround for a bug in MathJax when the math renderer is changed.
- // Note that this should be removed when we have upgraded to MathJax 4.
- const { STATE } = MathJax._.core.MathItem;
- const { Menu } = MathJax._.ui.menu.Menu;
- const { mathjax } = MathJax._.mathjax;
- Menu.prototype.rerender = function (start = STATE.TYPESET) {
- this.rerenderStart = Math.min(start, this.rerenderStart);
- if (!Menu.loading) {
- if (this.rerenderStart <= STATE.COMPILED) this.document.reset({ inputJax: [] });
- MathJax.startup.promise.then(() => {
- mathjax.handleRetriesFor(() => {
- this.document.rerender(this.rerenderStart);
- this.rerenderStart = STATE.LAST;
- });
- });
- }
- };
-
return MathJax.startup.defaultReady();
}
},
diff --git a/htdocs/js/PGCodeMirror/pgeditor.scss b/htdocs/js/PGCodeMirror/pgeditor.scss
index 3f1cd895c1..384adc6d17 100644
--- a/htdocs/js/PGCodeMirror/pgeditor.scss
+++ b/htdocs/js/PGCodeMirror/pgeditor.scss
@@ -1,5 +1,5 @@
.code-mirror-editor {
- border: 1px solid #ddd;
+ border: 1px solid var(--ww-layout-border-color, #ddd);
min-height: 400px;
overflow: auto;
resize: vertical;
@@ -25,7 +25,7 @@
// This style is used if the CodeMirror editor is disabled in localOverrides.conf.
.text-area-editor {
- border: 1px solid #ddd;
+ border: 1px solid var(--ww-layout-border-color, #ddd);
padding: 2px;
height: 550px;
min-height: 400px;
diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js
index 286840cc16..c1a2aacf3d 100644
--- a/htdocs/js/PGProblemEditor/pgproblemeditor.js
+++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js
@@ -1,5 +1,43 @@
(() => {
+ const renderURL = `${webworkConfig?.webwork_url ?? '/webwork2'}/render_rpc`;
+
+ for (const pgmlLabButton of document.querySelectorAll('.pgml-lab')) {
+ pgmlLabButton.addEventListener('click', (e) => {
+ e.preventDefault();
+ 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();
+ });
+ }
+
const fileChooserForm = document.forms['pg-editor-file-chooser'];
+
if (fileChooserForm) {
const newProblemRadio = document.getElementById('new_problem');
@@ -161,6 +199,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 };
@@ -199,7 +246,7 @@
if (webworkConfig?.pgCodeMirror) webworkConfig.pgCodeMirror.source = data.result_data.tidiedPGCode;
else document.getElementById('problemContents').value = data.result_data.tidiedPGCode;
saveTempFile();
- showMessage('Successfuly perltidied code.', true);
+ showMessage('Successfully perltidied code.', true);
}
})
.catch((err) => showMessage(`Error: ${err?.message ?? err}`));
@@ -235,15 +282,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;
}
@@ -305,8 +380,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
@@ -322,27 +395,124 @@
}
});
- // Synchronize the heights of the render area and the editor area for wide windows.
- if (editorArea && renderArea) {
- const codeMirrorResizeObserver = new ResizeObserver((entries) => {
- if (document.body.clientWidth < 992) return;
-
- for (const entry of entries) {
- if (entry.borderBoxSize) {
- // Note that the blockSize is the height (since width is not resizable).
- const height = Array.isArray(entry.borderBoxSize)
- ? entry.borderBoxSize[0].blockSize
- : entry.borderBoxSize.blockSize;
- if (window.getComputedStyle(renderArea).getPropertyValue('height') !== `${height}px`)
- renderArea.style.height = `${height}px`;
- if (window.getComputedStyle(editorArea).getPropertyValue('height') !== `${height}px`) {
- editorArea.style.height = `${height}px`;
- }
- }
+ const pgEditContainer = document.getElementById('pgedit-container');
+ const codePanel = pgEditContainer?.querySelector('.pgedit-panel.code');
+ const renderPanel = pgEditContainer?.querySelector('.pgedit-panel.render');
+
+ if (pgEditContainer && codePanel && renderPanel) {
+ if (document.body.clientWidth < 992) {
+ const initialCodePanelHeight = localStorage.getItem('WW.pgedit.codePanelHeight');
+ if (initialCodePanelHeight) codePanel.style.height = initialCodePanelHeight;
+ } else {
+ const initialResizeContainerHeight = localStorage.getItem('WW.pgedit.containerHeight');
+ if (initialResizeContainerHeight) pgEditContainer.style.height = initialResizeContainerHeight;
+ const initialCodePanelWidth = localStorage.getItem('WW.pgedit.codePanelWidth');
+ if (initialCodePanelWidth) codePanel.style.width = initialCodePanelWidth;
+ }
+
+ const verticalResizer = pgEditContainer.querySelector('.vertical-resizer');
+
+ verticalResizer?.addEventListener('pointerdown', (e) => {
+ verticalResizer.setPointerCapture(e.pointerId);
+
+ const startY = e.clientY;
+ const startHeight =
+ document.body.clientWidth < 992
+ ? codePanel.getBoundingClientRect().height
+ : pgEditContainer.getBoundingClientRect().height;
+
+ const onPointerMove =
+ document.body.clientWidth < 992
+ ? (moveEvent) => {
+ codePanel.style.height = `${startHeight + (moveEvent.clientY - startY)}px`;
+ localStorage.setItem('WW.pgedit.codePanelHeight', codePanel.style.height);
+ }
+ : (moveEvent) => {
+ pgEditContainer.style.height = `${startHeight + (moveEvent.clientY - startY)}px`;
+ localStorage.setItem('WW.pgedit.containerHeight', pgEditContainer.style.height);
+ };
+ const onPointerUp = () => {
+ verticalResizer.releasePointerCapture(e.pointerId);
+ document.removeEventListener('pointermove', onPointerMove);
+ document.removeEventListener('pointerup', onPointerUp);
+ };
+
+ document.addEventListener('pointermove', onPointerMove);
+ document.addEventListener('pointerup', onPointerUp);
+ });
+
+ const updateHeight = (delta) => {
+ if (document.body.clientWidth < 992) {
+ codePanel.style.height = `${codePanel.getBoundingClientRect().height + delta}px`;
+ localStorage.setItem('WW.pgedit.codePanelHeight', codePanel.style.height);
+ } else {
+ pgEditContainer.style.height = `${pgEditContainer.getBoundingClientRect().height + delta}px`;
+ localStorage.setItem('WW.pgedit.containerHeight', pgEditContainer.style.height);
+ }
+ };
+
+ verticalResizer?.addEventListener('keydown', (e) => {
+ const step = e.ctrlKey ? 1 : e.altKey ? 50 : 20;
+ if (e.key === 'ArrowUp') {
+ updateHeight(-step);
+ e.preventDefault();
+ } else if (e.key === 'ArrowDown') {
+ updateHeight(step);
+ e.preventDefault();
+ }
+ });
+
+ const horizontalResizer = pgEditContainer.querySelector('.horizontal-resizer');
+
+ horizontalResizer?.addEventListener('pointerdown', (e) => {
+ horizontalResizer.setPointerCapture(e.pointerId);
+
+ const startX = e.clientX;
+ const startWidth = codePanel.getBoundingClientRect().width;
+
+ const onPointerMove = (moveEvent) => {
+ codePanel.style.width = `${startWidth + (moveEvent.clientX - startX)}px`;
+ localStorage.setItem('WW.pgedit.codePanelWidth', codePanel.style.width);
+ };
+
+ const onPointerUp = () => {
+ horizontalResizer.releasePointerCapture(e.pointerId);
+ document.removeEventListener('pointermove', onPointerMove);
+ document.removeEventListener('pointerup', onPointerUp);
+ };
+
+ document.addEventListener('pointermove', onPointerMove);
+ document.addEventListener('pointerup', onPointerUp);
+ });
+
+ horizontalResizer?.addEventListener('dblclick', () => {
+ codePanel.style.width = 'calc(50% - 0.5rem + 1px)';
+ localStorage.setItem('WW.pgedit.codePanelWidth', codePanel.style.width);
+ });
+
+ horizontalResizer?.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ codePanel.style.width = 'calc(50% - 0.5rem + 1px)';
+ localStorage.setItem('WW.pgedit.codePanelWidth', codePanel.style.width);
+ e.preventDefault();
+ }
+ });
+
+ const updateWidth = (delta) => {
+ codePanel.style.width = `${codePanel.getBoundingClientRect().width + delta}px`;
+ localStorage.setItem('WW.pgedit.codePanelWidth', codePanel.style.width);
+ };
+
+ horizontalResizer?.addEventListener('keydown', (e) => {
+ const step = e.ctrlKey ? 1 : e.altKey ? 50 : 20;
+ if (e.key === 'ArrowLeft') {
+ updateWidth(-step);
+ e.preventDefault();
+ } else if (e.key === 'ArrowRight') {
+ updateWidth(step);
+ e.preventDefault();
}
});
- codeMirrorResizeObserver.observe(editorArea);
- codeMirrorResizeObserver.observe(renderArea);
}
// Save the initial placeholder content of the render area so that it can be put back when a problem is reloaded.
@@ -350,13 +520,31 @@
const iframe = document.createElement('iframe');
iframe.title = 'Rendered content';
iframe.id = 'pgedit-render-iframe';
+ iframe.style.colorScheme = 'light';
- // Adjust the height of the iframe when the window is resized and when the iframe loads.
+ // Adjust editor dimensions when the window is resized and when the iframe loads.
const adjustIFrameHeight = () => {
if (document.body.clientWidth < 992) {
- if (iframe.contentDocument)
- renderArea.style.height = `${iframe.contentDocument.documentElement.offsetHeight + 2}px`;
- } else renderArea.style.height = `${editorArea.offsetHeight}px`;
+ if (iframe.contentDocument) {
+ pgEditContainer.style.height = '';
+ codePanel.style.width = '100%';
+ codePanel.style.height = localStorage.getItem('WW.pgedit.codePanelHeight') ?? '';
+ renderArea.style.height = `${
+ iframe.contentDocument.documentElement.offsetHeight +
+ 2 +
+ (document.getElementById('author-comment')?.offsetHeight ?? 0)
+ }px`;
+ renderPanel.style.width = '100%';
+ renderPanel.style.height = renderArea.style.height;
+ }
+ } else {
+ pgEditContainer.style.height = localStorage.getItem('WW.pgedit.containerHeight') ?? '';
+ codePanel.style.width = localStorage.getItem('WW.pgedit.codePanelWidth') ?? '';
+ renderPanel.style.width = '';
+ codePanel.style.height = '100%';
+ renderPanel.style.height = '100%';
+ renderArea.style.height = '100%';
+ }
};
window.addEventListener('resize', adjustIFrameHeight);
@@ -382,6 +570,7 @@
requestData.set('send_pg_flags', 1);
requestData.set(button.name, button.value);
requestData.set('set_id', document.getElementsByName('hidden_set_id')[0]?.value ?? 'Unknown Set');
+ requestData.set('showMathJaxErrors', 1);
await renderProblem(requestData);
@@ -390,11 +579,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 = () =>
@@ -473,7 +658,8 @@
displayMode: document.getElementById('action_view_displayMode_id')?.value ?? 'MathJax',
language: document.querySelector('input[name="hidden_language"]')?.value ?? 'en',
send_pg_flags: 1,
- view_problem_debugging_info: 1
+ view_problem_debugging_info: 1,
+ showMathJaxErrors: 1
})
).then(() => resolve());
});
@@ -512,7 +698,8 @@
if (data.pg_flags && data.pg_flags.comment) {
// The problem has a comment, so show it.
const container = document.createElement('div');
- container.classList.add('px-2', 'mb-2');
+ container.id = 'author-comment';
+ container.classList.add('p-2');
container.innerHTML = data.pg_flags.comment;
iframe.after(container);
}
@@ -616,37 +803,4 @@
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/htdocs/js/PGProblemEditor/pgproblemeditor.scss b/htdocs/js/PGProblemEditor/pgproblemeditor.scss
new file mode 100644
index 0000000000..fec708827f
--- /dev/null
+++ b/htdocs/js/PGProblemEditor/pgproblemeditor.scss
@@ -0,0 +1,174 @@
+#pgedit-container {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ height: 600px;
+ min-height: 400px;
+
+ .pgedit-inner-container {
+ display: flex;
+ flex: 1;
+ width: 100%;
+ height: calc(100% - 1rem - 2px);
+
+ .pgedit-panel {
+ overflow: auto;
+ }
+
+ .code {
+ width: calc(50% - 0.5rem);
+ height: 100%;
+ min-width: 400px;
+
+ .code-mirror-editor {
+ min-height: unset;
+ overflow: unset;
+ height: 100%;
+ }
+
+ .text-area-editor {
+ min-height: unset;
+ overflow: unset;
+ height: 100%;
+ resize: unset;
+ }
+ }
+
+ .render {
+ flex: 1;
+ min-width: 300px;
+ height: 100%;
+
+ #pgedit-render-area {
+ border: 1px solid var(--ww-layout-border-color, #ddd);
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ #pgedit-render-iframe {
+ flex-grow: 1;
+ border: none;
+ width: 100%;
+ }
+ }
+
+ #author-comment {
+ border-top: 1px solid var(--ww-layout-border-color, #ddd);
+ }
+ }
+ }
+
+ .pgedit-resizer {
+ background-color: #fff;
+ transition:
+ background 0.2s,
+ box-shadow 0.2s ease-in-out;
+ position: relative;
+ border: 1px solid var(--ww-layout-border-color, #ddd);
+ user-select: none;
+ touch-action: none;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: black;
+
+ &:focus {
+ z-index: 19;
+ box-shadow: 0 0 0.2rem 0.25rem #aaa;
+ outline: none;
+ }
+
+ &:hover {
+ z-index: 19;
+ background-color: #888;
+ color: white;
+ box-shadow: 0 0 0.2rem 0.25rem #888;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ background: transparent;
+ }
+ }
+
+ .vertical-resizer {
+ height: 1rem;
+ width: 100%;
+ cursor: row-resize;
+
+ i {
+ transform: scale(4, 1);
+ }
+
+ &::after {
+ top: -4px;
+ left: 0;
+ right: 0;
+ bottom: -4px;
+ }
+ }
+
+ .horizontal-resizer {
+ height: 100%;
+ width: 1rem;
+ cursor: col-resize;
+
+ i {
+ transform: scale(1, 4);
+ }
+
+ &::after {
+ top: 0;
+ left: -4px;
+ right: -4px;
+ bottom: 0;
+ }
+ }
+
+ @media (prefers-color-scheme: dark) {
+ .pgedit-resizer {
+ background-color: #000;
+ color: white;
+
+ &:focus {
+ box-shadow: 0 0 0.2rem 0.25rem #777;
+ }
+
+ &:hover {
+ background-color: #bbb;
+ color: black;
+ box-shadow: 0 0 0.2rem 0.25rem #999;
+ }
+ }
+ }
+
+ @media (max-width: 991px) {
+ height: unset;
+ min-height: unset;
+
+ .pgedit-inner-container {
+ flex-direction: column;
+ row-gap: 1rem;
+
+ .code {
+ width: 100%;
+ height: 400px;
+ min-height: 200px;
+ min-width: unset;
+ }
+
+ .render {
+ flex: unset;
+ height: 400px;
+ min-height: 200px;
+ width: 100%;
+ min-width: unset;
+ }
+
+ .horizontal-resizer {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/htdocs/js/PODViewer/podviewer.css b/htdocs/js/PODViewer/podviewer.css
deleted file mode 100644
index e4f17811d2..0000000000
--- a/htdocs/js/PODViewer/podviewer.css
+++ /dev/null
@@ -1,66 +0,0 @@
-.main-index-header,
-.pod-header {
- height: 65px;
- top: 0;
- left: 0;
- right: 0;
- z-index: 2;
-}
-
-#sidebar {
- --bs-offcanvas-width: 300px;
- overflow-y: auto;
-}
-
-#sidebar ul.nav ul.nav li {
- border-left: 1px solid #e1e4e8;
- padding-left: 10px;
-}
-
-#sidebar ul.nav ul.nav li:hover {
- border-left: 6px solid #e1e4e8;
- padding-left: 5px;
-}
-
-.main-index-container,
-.pod-page-container {
- margin-top: 65px;
-}
-
-@media only screen and (min-width: 768px) {
- #sidebar {
- height: calc(100vh - 65px);
- width: 300px;
- }
-
- .pod-page-container {
- margin-left: 300px;
- }
-}
-
-#_podtop_ pre {
- border: 1px solid #ccc;
- border-radius: 5px;
- background: #f6f6f6;
- padding: 0.75rem;
-}
-
-#_podtop_,
-#_podtop_ *[id] {
- scroll-margin-top: calc(65px + 1rem);
-}
-
-@media only screen and (max-width: 768px) {
- .pod-header {
- height: 100px;
- }
-
- .pod-page-container {
- margin-top: 100px;
- }
-
- #_podtop_,
- #_podtop_ *[id] {
- scroll-margin-top: calc(100px + 1rem);
- }
-}
diff --git a/htdocs/js/PODViewer/podviewer.js b/htdocs/js/PODViewer/podviewer.js
deleted file mode 100644
index 795093205a..0000000000
--- a/htdocs/js/PODViewer/podviewer.js
+++ /dev/null
@@ -1,8 +0,0 @@
-(() => {
- const offcanvas = bootstrap.Offcanvas.getOrCreateInstance(document.getElementById('sidebar'));
- for (const link of document.querySelectorAll('#sidebar .nav-link')) {
- // The timeout is to workaround an issue in Chrome. If the offcanvas hides before the window scrolls to the
- // fragment in the page, scrolling stops before it gets there.
- link.addEventListener('click', () => setTimeout(() => offcanvas.hide(), 500));
- }
-})();
diff --git a/htdocs/js/ProblemGrader/problemgrader.js b/htdocs/js/ProblemGrader/problemgrader.js
index 1e33408bbf..ada3727ecc 100644
--- a/htdocs/js/ProblemGrader/problemgrader.js
+++ b/htdocs/js/ProblemGrader/problemgrader.js
@@ -1,6 +1,45 @@
'use strict';
(() => {
+ const setPointInputValue = (pointInput, score) =>
+ (pointInput.value = parseFloat(
+ (Math.round((score * pointInput.max) / 100 / pointInput.step) * pointInput.step).toFixed(2)
+ ));
+
+ // Update problem score if point value changes and is a valid value.
+ for (const pointInput of document.querySelectorAll('.problem-points')) {
+ pointInput.addEventListener('input', () => {
+ const userId = pointInput.id.replace(/\.points$/, '');
+ if (pointInput.checkValidity()) {
+ const scoreInput = document.getElementById(`${userId}.score`);
+ if (scoreInput) {
+ scoreInput.classList.remove('is-invalid');
+ scoreInput.value = Math.round((100 * pointInput.value) / pointInput.max);
+ }
+ pointInput.classList.remove('is-invalid');
+ } else {
+ pointInput.classList.add('is-invalid');
+ }
+ });
+ }
+
+ // Update problem points if score changes and is a valid value.
+ for (const scoreInput of document.querySelectorAll('.problem-score')) {
+ scoreInput.addEventListener('input', () => {
+ const userId = scoreInput.id.replace(/\.score$/, '');
+ if (scoreInput.checkValidity()) {
+ const pointInput = document.getElementById(`${userId}.points`);
+ if (pointInput) {
+ pointInput.classList.remove('is-invalid');
+ pointInput.value = setPointInputValue(pointInput, scoreInput.value);
+ }
+ scoreInput.classList.remove('is-invalid');
+ } else {
+ scoreInput.classList.add('is-invalid');
+ }
+ });
+ }
+
const userSelect = document.getElementById('student_selector');
if (!userSelect) return;
@@ -15,7 +54,8 @@
problemSeed: selectedUser.dataset.problemSeed,
set_id: document.getElementsByName('hidden_set_id')[0]?.value,
probNum: document.getElementsByName('hidden_problem_id')[0]?.value,
- processAnswers: 1
+ processAnswers: 1,
+ WWcorrectAns: 1
};
if (selectedUser.dataset.versionId) ro.version_id = selectedUser.dataset.versionId;
diff --git a/htdocs/js/ProblemGrader/singleproblemgrader.js b/htdocs/js/ProblemGrader/singleproblemgrader.js
index 002c5b7f0d..9c9f16fbe9 100644
--- a/htdocs/js/ProblemGrader/singleproblemgrader.js
+++ b/htdocs/js/ProblemGrader/singleproblemgrader.js
@@ -125,6 +125,7 @@
version_id: saveData.versionId,
problem_id: saveData.problemId,
status: parseInt(scoreInput.value) / 100,
+ ...(saveData.saveSubStatus === '1' ? { sub_status: parseInt(scoreInput.value) / 100 } : {}),
mark_graded: true
}),
signal: controller.signal
@@ -216,4 +217,141 @@
}
});
}
+
+ const settingStoreID = `WW.${document.getElementsByName('courseID')[0]?.value ?? 'unknownCourse'}.${
+ document.getElementsByName('user')[0]?.value ?? 'unknownUser'
+ }.problem_grader`;
+ let gradersOpen = localStorage.getItem(`${settingStoreID}.open`) === 'true';
+
+ const graderCollapses = [];
+
+ for (const grader of document.querySelectorAll('.problem-grader')) {
+ const problemId = grader.id.replace('problem-grader-');
+
+ grader.classList.add('accordion');
+
+ const accordionItem = document.createElement('div');
+ accordionItem.classList.add('accordion-item');
+
+ const accordionHeader = document.createElement('h2');
+ accordionHeader.classList.add('accordion-header');
+
+ const accordionButton = document.createElement('button');
+ accordionButton.classList.add('accordion-button');
+ accordionButton.type = 'button';
+ accordionButton.textContent = grader.dataset.graderTitle ?? 'Problem Grader';
+ accordionButton.dataset.bsToggle = 'collapse';
+ accordionButton.dataset.bsTarget = `#problem-grader-collapse-${problemId}`;
+ accordionButton.setAttribute('aria-controls', `#problem-grader-collapse-${problemId}`);
+ accordionButton.setAttribute('aria-expanded', gradersOpen);
+ if (!gradersOpen) accordionButton.classList.add('collapsed');
+
+ accordionHeader.append(accordionButton);
+
+ const accordionCollapse = document.createElement('div');
+ accordionCollapse.classList.add('accordion-collapse', 'collapse');
+ accordionCollapse.id = `problem-grader-collapse-${problemId}`;
+ accordionCollapse.dataset.bsParent = `problem-grader-${problemId}`;
+ if (gradersOpen) accordionCollapse.classList.add('show');
+
+ const accordionBody = grader.querySelector('.problem-grader-table');
+ accordionBody.classList.add('accordion-body');
+ accordionCollapse.append(accordionBody);
+
+ accordionItem.append(accordionHeader, accordionCollapse);
+ grader.append(accordionItem);
+
+ const graderCollapse = new bootstrap.Collapse(accordionCollapse, { toggle: false });
+ graderCollapses.push(graderCollapse);
+
+ grader.classList.remove('d-none');
+
+ // Expand or collapse all problem graders on the page when any one of them is expanded or collapsed.
+ let transitioning = false;
+ accordionCollapse.addEventListener('show.bs.collapse', () => {
+ if (transitioning) return;
+ transitioning = true;
+ for (const grader of graderCollapses) {
+ if (grader !== graderCollapse) grader.show();
+ }
+ transitioning = false;
+ });
+ accordionCollapse.addEventListener('hide.bs.collapse', () => {
+ if (transitioning) return;
+ transitioning = true;
+ for (const grader of graderCollapses) {
+ if (grader !== graderCollapse) grader.hide();
+ }
+ transitioning = false;
+ });
+
+ // Make sure that the "Reveal" button in feedback is not shown if a feedback button is used while the problem
+ // grader is open. However, also make sure that the "Reveal" button is shown for any feedback button that is
+ // not used while the problem grader is open.
+
+ const unrevealedFeedbackBtns = [];
+
+ for (const feedbackBtn of document.querySelectorAll('.ww-feedback-btn')) {
+ const container = document.createElement('div');
+ container.innerHTML = feedbackBtn.dataset.bsContent;
+ const button = container.querySelector('.reveal-correct-btn');
+ if (!button) continue;
+
+ button.nextElementSibling?.classList.remove('d-none');
+ button.remove();
+
+ const fragment = new DocumentFragment();
+ fragment.append(container);
+
+ unrevealedFeedbackBtns.push([feedbackBtn, fragment.firstElementChild.innerHTML]);
+
+ const handler = () => {
+ const index = unrevealedFeedbackBtns.findIndex((data) => data[0] === feedbackBtn);
+ if (index !== -1) {
+ if (gradersOpen) {
+ unrevealedFeedbackBtns.splice(index, 1);
+ feedbackBtn.removeEventListener('shown.bs.popover', handler);
+ } else {
+ bootstrap.Popover.getInstance(feedbackBtn)
+ ?.tip?.querySelector('.reveal-correct-btn')
+ ?.addEventListener(
+ 'click',
+ () => {
+ unrevealedFeedbackBtns.splice(index, 1);
+ feedbackBtn.removeEventListener('shown.bs.popover', handler);
+ },
+ { once: true }
+ );
+ }
+ }
+ };
+
+ feedbackBtn.addEventListener('shown.bs.popover', handler);
+ }
+
+ const removeRevealButtons = () => {
+ for (const data of unrevealedFeedbackBtns) {
+ const feedbackPopover = bootstrap.Popover.getInstance(data[0]);
+ feedbackPopover?.setContent({ '.popover-body': data[1] });
+ }
+ };
+
+ if (gradersOpen) removeRevealButtons();
+
+ // In addition to removing and putting back the feedback "Reveal" buttons as needed,
+ // preserve the collapsed/expanded status of the problem graders in local storage.
+ accordionCollapse.addEventListener('shown.bs.collapse', () => {
+ localStorage.setItem(`${settingStoreID}.open`, 'true');
+ gradersOpen = true;
+ removeRevealButtons();
+ });
+ accordionCollapse.addEventListener('hidden.bs.collapse', () => {
+ gradersOpen = false;
+ localStorage.setItem(`${settingStoreID}.open`, 'false');
+ for (const data of unrevealedFeedbackBtns) {
+ const feedbackPopover = bootstrap.Popover.getInstance(data[0]);
+ feedbackPopover?.setContent({ '.popover-body': data[0].dataset.bsContent });
+ }
+ });
+ }
})();
diff --git a/htdocs/js/ProblemSetDetail/problemsetdetail.js b/htdocs/js/ProblemSetDetail/problemsetdetail.js
index 93661d6739..1b9be297ee 100644
--- a/htdocs/js/ProblemSetDetail/problemsetdetail.js
+++ b/htdocs/js/ProblemSetDetail/problemsetdetail.js
@@ -467,4 +467,26 @@
const input = document.getElementById(btn.dataset.seedInput);
if (input) btn.addEventListener('click', () => (input.value = Math.floor(Math.random() * 10000)));
}
+
+ // Handle mixed select/number input fields.
+ for (const numericSelect of document.querySelectorAll('.mixed-numeric-select')) {
+ const select = numericSelect.querySelector('select');
+ const numberInput = numericSelect.querySelector('input');
+ let currentNumberValue = numberInput.value !== '' ? numberInput.value : numberInput.min;
+
+ const setNumericState = () => {
+ if (select.value === 'numeric') {
+ numberInput.value = currentNumberValue;
+ numberInput.disabled = false;
+ numberInput.required = true;
+ } else {
+ if (numberInput.value !== '') currentNumberValue = numberInput.value;
+ numberInput.value = '';
+ numberInput.disabled = true;
+ numberInput.required = false;
+ }
+ };
+ select.addEventListener('change', setNumericState);
+ setNumericState();
+ }
})();
diff --git a/htdocs/js/ProblemSetList/problemsetlist.js b/htdocs/js/ProblemSetList/problemsetlist.js
index ca9f334853..b86bdebbc7 100644
--- a/htdocs/js/ProblemSetList/problemsetlist.js
+++ b/htdocs/js/ProblemSetList/problemsetlist.js
@@ -7,12 +7,12 @@
for (const id of ids) elements.push(document.getElementById(id));
for (const element of elements) {
if (element?.id.endsWith('_err_msg')) {
- element?.classList.remove('d-none');
- } else {
- element?.classList.add('is-invalid');
+ element.classList.remove('d-none');
+ } else if (element) {
+ element.classList.add('is-invalid');
if (!(element.id in event_listeners)) {
event_listeners[element.id] = hide_errors([], elements);
- element?.addEventListener('change', event_listeners[element.id]);
+ element.addEventListener('change', event_listeners[element.id]);
}
}
}
@@ -23,17 +23,17 @@
for (const id of ids) elements.push(document.getElementById(id));
for (const element of elements) {
if (element?.id.endsWith('_err_msg')) {
- element?.classList.add('d-none');
+ element.classList.add('d-none');
if (element.id === 'select_set_err_msg' && 'set_table_id' in event_listeners) {
document
.getElementById('set_table_id')
?.removeEventListener('change', event_listeners.set_table_id);
delete event_listeners.set_table_id;
}
- } else {
- element?.classList.remove('is-invalid');
+ } else if (element) {
+ element.classList.remove('is-invalid');
if (element.id in event_listeners) {
- element?.removeEventListener('change', event_listeners[element.id]);
+ element.removeEventListener('change', event_listeners[element.id]);
delete event_listeners[element.id];
}
}
@@ -139,16 +139,32 @@
filter_select?.addEventListener('change', filterElementToggle);
// This will make the popup menu alternate between a single selection and a multiple selection menu.
- const importAmtSelect = document.getElementById('import_amt_select');
- if (importAmtSelect) {
- importAmtSelect.addEventListener('change', () => {
- const numSelect = document.problemsetlist['action.import.number'];
- const number = parseInt(numSelect.options[numSelect.selectedIndex].value);
- document.problemsetlist['action.import.source'].size = number;
- document.problemsetlist['action.import.source'].multiple = number > 1 ? true : false;
- document.problemsetlist['action.import.name'].value = number > 1 ? '(taken from filenames)' : '';
- document.problemsetlist['action.import.name'].readOnly = number > 1 ? true : false;
- document.problemsetlist['action.import.name'].disabled = number > 1 ? true : false;
+ const numSelect = document.problemsetlist['action.import.number'];
+ if (numSelect) {
+ numSelect.addEventListener('change', () => {
+ const number = parseInt(numSelect.options[numSelect.selectedIndex]?.value ?? '1');
+ const importSourceSelect = document.problemsetlist['action.import.source'];
+ if (importSourceSelect) {
+ importSourceSelect.size = number;
+ if (number === 1) {
+ if (!importSourceSelect.value) importSourceSelect.options[0].selected = true;
+ importSourceSelect.options[0].textContent =
+ importSourceSelect.dataset.selectSingleText ?? 'Select filename below';
+ importSourceSelect.multiple = false;
+ } else {
+ importSourceSelect.options[0].textContent =
+ importSourceSelect.dataset.selectMultipleText ?? 'Select filename below';
+ importSourceSelect.multiple = true;
+ importSourceSelect.options[0].selected = false;
+ }
+ }
+ const importNameInput = document.problemsetlist['action.import.name'];
+ if (importNameInput) {
+ importNameInput.value =
+ number > 1 ? (importNameInput.dataset.multipleFilesText ?? '(taken from filenames)') : '';
+ importNameInput.readOnly = number > 1 ? true : false;
+ importNameInput.disabled = number > 1 ? true : false;
+ }
});
}
@@ -174,10 +190,15 @@
'zh-HK': 'yyyy/L/d ah:mm'
};
- // Initialize the date/time picker for the import form.
+ // Initialize the date/time picker for the import form and common date editor.
+ const dateInputs = [];
const importDateShift = document.getElementById('import_date_shift');
- if (importDateShift) {
- luxon.Settings.defaultLocale = importDateShift.dataset.locale ?? 'en';
+ if (importDateShift) dateInputs.push(importDateShift);
+ const commonDateInput = document.getElementById('common-date');
+ if (commonDateInput) dateInputs.push(commonDateInput);
+
+ for (const dateInput of dateInputs) {
+ luxon.Settings.defaultLocale = dateInput.dataset.locale ?? 'en';
// Compute the time difference between a time in the browser timezone and the same time in the course timezone.
// flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone.
@@ -189,17 +210,17 @@
new Date(dateTime.toLocaleString('en-US')).getTime() -
new Date(
dateTime.toLocaleString('en-US', {
- timeZone: importDateShift.dataset.timezone ?? 'America/New_York'
+ timeZone: dateInput.dataset.timezone ?? 'America/New_York'
})
).getTime()
);
};
- let fallbackDate = importDateShift.value
- ? new Date(parseInt(importDateShift.value) * 1000 - timezoneAdjustment(parseInt(importDateShift.value)))
+ let fallbackDate = dateInput.value
+ ? new Date(parseInt(dateInput.value) * 1000 - timezoneAdjustment(parseInt(dateInput.value)))
: new Date();
- const fp = flatpickr(importDateShift.parentNode, {
+ const fp = flatpickr(dateInput.parentNode, {
allowInput: true,
enableTime: true,
minuteIncrement: 1,
@@ -216,27 +237,28 @@
disableMobile: true,
wrap: true,
plugins: [
- new confirmDatePlugin({ confirmText: importDateShift.dataset.doneText, showAlways: true }),
+ new confirmDatePlugin({ confirmText: dateInput.dataset.doneText, showAlways: true }),
new ShortcutButtonsPlugin({
button: [
{
- label: importDateShift.dataset.todayText ?? 'Today',
+ label: dateInput.dataset.todayText ?? 'Today',
attributes: { class: 'btn btn-sm btn-secondary ms-auto me-1 mb-1' }
},
{
- label: importDateShift.dataset.nowText ?? 'Now',
+ label: dateInput.dataset.nowText ?? 'Now',
attributes: { class: 'btn btn-sm btn-secondary mx-auto mb-1' }
}
],
onClick: (index, fp) => {
if (index === 0) {
- const today = new Date();
- // If there isn't a selected date, then use 12:00 am on the current date.
- const selectedDate = fp.selectedDates[0] ?? new Date(new Date().toDateString());
- selectedDate.setFullYear(today.getFullYear());
- selectedDate.setMonth(today.getMonth());
- selectedDate.setDate(today.getDate());
- fp.setDate(selectedDate);
+ // The initial date represents 12:00 am on the current date.
+ const today = new Date(new Date().toDateString());
+ if (fp.selectedDates[0]) {
+ today.setHours(fp.selectedDates[0].getHours());
+ today.setMinutes(fp.selectedDates[0].getMinutes());
+ today.setSeconds(fp.selectedDates[0].getSeconds());
+ }
+ fp.setDate(today, true);
} else if (index === 1) {
fp.setDate(new Date());
}
@@ -251,6 +273,10 @@
// Make the alternate input left-to-right even for right-to-left languages.
this.altInput.dir = 'ltr';
+
+ // Move the id of the now hidden input onto the added input so the labels still work.
+ this.altInput.id = this.input.id;
+ this.input.removeAttribute('id');
},
parseDate(datestr, format) {
// Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for
@@ -278,11 +304,46 @@
}
});
- importDateShift.nextElementSibling.addEventListener('keydown', (e) => {
+ dateInput.nextElementSibling.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
fp.open();
}
});
}
+
+ if (commonDateInput) {
+ document.getElementById('apply-common-date')?.addEventListener('click', () => {
+ const dateTypeInput = document.getElementById('set-date-choice');
+ if (!dateTypeInput?.value) {
+ show_errors(['choose_date_type_err_msg'], [dateTypeInput]);
+ return;
+ }
+
+ if (!commonDateInput.value) {
+ show_errors(
+ ['choose_common_date_err_msg'],
+ [commonDateInput.parentNode?._flatpickr?.input, commonDateInput.parentNode?._flatpickr?.altInput]
+ );
+ return;
+ }
+
+ const selectedSets = Array.from(document.getElementsByName('apply_date_sets')).filter((c) => c.checked);
+ if (!selectedSets.length) {
+ show_errors(['select_set_err_msg'], []);
+ event_listeners.set_table_id = hide_errors(
+ ['set_table_id'],
+ [document.getElementById('select_set_err_msg')]
+ );
+ document.getElementById('set_table_id')?.addEventListener('change', event_listeners.set_table_id);
+ }
+
+ for (const set of selectedSets) {
+ const inputPicker = document.getElementsByName(`set.${set.value}.${dateTypeInput.value}`)[0]?.parentNode
+ ?._flatpickr;
+ inputPicker?.setDate(commonDateInput.value, true);
+ inputPicker?.close(); // The picker isn't actually open, but this triggers the onClose handler.
+ }
+ });
+ }
})();
diff --git a/htdocs/js/RenderProblem/renderproblem.js b/htdocs/js/RenderProblem/renderproblem.js
index ec4503a64a..f3bb2bbebb 100644
--- a/htdocs/js/RenderProblem/renderproblem.js
+++ b/htdocs/js/RenderProblem/renderproblem.js
@@ -69,6 +69,7 @@
iframe = document.createElement('iframe');
iframe.id = `${renderArea.id}_iframe`;
iframe.style.border = 'none';
+ iframe.style.colorScheme = 'light';
while (renderArea.firstChild) renderArea.firstChild.remove();
renderArea.append(iframe);
diff --git a/htdocs/js/SampleProblemViewer/documentation-search.js b/htdocs/js/SampleProblemViewer/documentation-search.js
deleted file mode 100644
index 5e61bc64ec..0000000000
--- a/htdocs/js/SampleProblemViewer/documentation-search.js
+++ /dev/null
@@ -1,76 +0,0 @@
-(async () => {
- const searchBox = document.getElementById('search-box');
- const resultList = document.getElementById('result-list');
- if (!resultList || !searchBox) return;
-
- const webwork2URL = webworkConfig?.webwork_url ?? '/webwork2';
-
- let searchData;
- try {
- const result = await fetch(`${webwork2URL}/sampleproblems/search_data`);
- searchData = await result.json();
- } catch (e) {
- console.log(e);
- return;
- }
-
- const miniSearch = new MiniSearch({
- fields: ['filename', 'name', 'description', 'terms', 'macros', 'subjects'],
- storeFields: ['type', 'filename', 'dir', 'description']
- });
- miniSearch.addAll(searchData);
-
- const searchMacrosCheck = document.getElementById('search-macros');
- const searchSampleProblemsCheck = document.getElementById('search-sample-problems');
-
- document.getElementById('clear-search-button')?.addEventListener('click', () => {
- searchBox.value = '';
- while (resultList.firstChild) resultList.firstChild.remove();
- });
-
- const searchDocumentation = () => {
- const searchMacros = searchMacrosCheck?.checked;
- const searchSampleProblems = searchSampleProblemsCheck?.checked;
-
- while (resultList.firstChild) resultList.firstChild.remove();
-
- if (!searchBox.value) return;
-
- for (const result of miniSearch.search(searchBox.value, { prefix: true })) {
- if (
- (searchSampleProblems && result.type === 'sample problem') ||
- (searchMacros && result.type === 'macro')
- ) {
- const link = document.createElement('a');
- link.classList.add('list-group-item', 'list-group-item-action');
- link.href = `${webwork2URL}/${
- result.type === 'sample problem' ? 'sampleproblems' : result.type === 'macro' ? 'pod' : ''
- }/${result.dir}/${result.filename.replace('.pg', '')}`;
-
- const linkText = document.createElement('span');
- linkText.classList.add('h4');
- linkText.textContent = `${result.filename} (${result.type})`;
- link.append(linkText);
-
- if (result.description) {
- const summary = document.createElement('div');
- summary.textContent = result.description;
- link.append(summary);
- }
-
- resultList.append(link);
- }
- }
-
- if (resultList.children.length == 0) {
- const item = document.createElement('div');
- item.classList.add('alert', 'alert-info');
- item.innerHTML = 'No results found';
- resultList.append(item);
- }
- };
-
- searchBox.addEventListener('keyup', searchDocumentation);
- searchMacrosCheck?.addEventListener('change', searchDocumentation);
- searchSampleProblemsCheck?.addEventListener('change', searchDocumentation);
-})();
diff --git a/htdocs/js/SetMaker/setmaker.js b/htdocs/js/SetMaker/setmaker.js
index 581c165371..566774c053 100644
--- a/htdocs/js/SetMaker/setmaker.js
+++ b/htdocs/js/SetMaker/setmaker.js
@@ -79,6 +79,17 @@
const countLine = document.getElementById('library_count_line');
+ const settingStoreID = `WW.${document.getElementsByName('hidden_course_id')[0]?.value ?? 'unknownCourse'}.${
+ document.getElementsByName('user')[0]?.value ?? 'unknownUser'
+ }.setmaker`;
+ const includeOPLInitialStatus = includeOPL?.checked;
+ const includeContribInitialStatus = includeContrib?.checked;
+ if (includeOPL)
+ includeOPL.checked = localStorage.getItem(`${settingStoreID}.includeOPLChecked`) !== 'false' ? true : false;
+ if (includeContrib)
+ includeContrib.checked =
+ localStorage.getItem(`${settingStoreID}.includeContribChecked`) !== 'false' ? true : false;
+
const lib_update = async (who, what) => {
const child = { subject: 'chapter', chapter: 'section', section: 'count' };
@@ -200,11 +211,22 @@
libraryChapter?.addEventListener('change', () => lib_update('section', 'get'));
librarySubject?.addEventListener('change', () => lib_update('chapter', 'get'));
librarySection?.addEventListener('change', () => lib_update('count', 'clear'));
- includeOPL?.addEventListener('change', () => lib_update('count', 'clear'));
- includeContrib?.addEventListener('change', () => lib_update('count', 'clear'));
+ includeOPL?.addEventListener('change', () => {
+ localStorage.setItem(`${settingStoreID}.includeOPLChecked`, includeOPL.checked);
+ lib_update('count', 'clear');
+ });
+ includeContrib?.addEventListener('change', () => {
+ localStorage.setItem(`${settingStoreID}.includeContribChecked`, includeContrib.checked);
+ lib_update('count', 'clear');
+ });
levels.forEach((level) => level.addEventListener('change', () => lib_update('count', 'clear')));
libraryKeywords?.addEventListener('change', () => lib_update('count', 'clear'));
+ // If the local storage status of the checks are different than what they
+ // were when the page loaded, then the count needs to be updated.
+ if (includeOPL?.checked !== includeOPLInitialStatus || includeContrib?.checked !== includeContribInitialStatus)
+ lib_update('count', 'clear');
+
// Set up the advanced view selects to submit the form when changed.
const libraryBrowserForm = document.forms['library_browser_form'];
if (libraryBrowserForm) {
diff --git a/htdocs/js/System/color-scheme.js b/htdocs/js/System/color-scheme.js
new file mode 100644
index 0000000000..cf0164193a
--- /dev/null
+++ b/htdocs/js/System/color-scheme.js
@@ -0,0 +1,75 @@
+'use strict';
+
+(() => {
+ const getPreferredTheme = () => {
+ const storedTheme = localStorage.getItem('WW.color-scheme');
+ if (storedTheme) return storedTheme;
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+ };
+
+ let flatpickrDarkTheme;
+
+ const setTheme = (theme) => {
+ const themeValue =
+ theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme;
+ document.documentElement.setAttribute('data-bs-theme', themeValue);
+
+ if (!flatpickrDarkTheme) flatpickrDarkTheme = document.getElementById('flatpickr-dark-theme');
+ if (flatpickrDarkTheme) {
+ if (themeValue === 'dark') document.head.append(flatpickrDarkTheme);
+ else flatpickrDarkTheme.remove();
+ }
+ };
+
+ setTheme(getPreferredTheme());
+
+ const showActiveTheme = (theme, focus = false) => {
+ const themeSwitcher = document.getElementById('color-scheme-chooser');
+ if (!themeSwitcher) return;
+
+ const activeThemeIcon = themeSwitcher.querySelector('.theme-icon-active');
+ const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
+
+ for (const element of document.querySelectorAll('[data-bs-theme-value]')) {
+ element.classList.remove('active');
+ element.setAttribute('aria-pressed', 'false');
+ }
+
+ btnToActive.classList.add('active');
+ btnToActive.setAttribute('aria-pressed', 'true');
+ activeThemeIcon.classList.remove('fa-sun', 'fa-moon', 'fa-circle-half-stroke');
+ activeThemeIcon.classList.add(
+ theme === 'light' ? 'fa-sun' : theme === 'dark' ? 'fa-moon' : 'fa-circle-half-stroke'
+ );
+ themeSwitcher.setAttribute(
+ 'aria-label',
+ `${themeSwitcher.title} (${
+ themeSwitcher.dataset[`${btnToActive.dataset.bsThemeValue}Text`] ?? btnToActive.dataset.bsThemeValue
+ })`
+ );
+
+ if (focus) themeSwitcher.focus();
+ };
+
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
+ const storedTheme = localStorage.getItem('WW.color-scheme');
+ if (storedTheme !== 'light' && storedTheme !== 'dark') {
+ const preferredTheme = getPreferredTheme();
+ setTheme(preferredTheme);
+ showActiveTheme(preferredTheme);
+ }
+ });
+
+ window.addEventListener('DOMContentLoaded', () => {
+ showActiveTheme(getPreferredTheme());
+
+ for (const toggle of document.querySelectorAll('[data-bs-theme-value]')) {
+ toggle.addEventListener('click', () => {
+ const theme = toggle.getAttribute('data-bs-theme-value');
+ localStorage.setItem('WW.color-scheme', theme);
+ setTheme(theme);
+ showActiveTheme(theme, true);
+ });
+ }
+ });
+})();
diff --git a/htdocs/js/System/system.js b/htdocs/js/System/system.js
index 634f07a87c..4b32aca942 100644
--- a/htdocs/js/System/system.js
+++ b/htdocs/js/System/system.js
@@ -74,7 +74,8 @@
// FIXME: These are really general purpose tooltips and not just in the homework sets editor. So the class name
// should be chosen to better reflect this.
document.querySelectorAll('.set-id-tooltip').forEach((el) => {
- if (el.dataset.bsTitle) new bootstrap.Tooltip(el, { fallbackPlacements: [] });
+ if (el.dataset.bsTitle)
+ new bootstrap.Tooltip(el, { fallbackPlacements: el.dataset.fallbackPlacements?.split(' ') || [] });
});
// Hardcopy tooltips shown on the Problem Sets page.
diff --git a/htdocs/js/System/system.scss b/htdocs/js/System/system.scss
index c6a9468d58..b436d6f98d 100644
--- a/htdocs/js/System/system.scss
+++ b/htdocs/js/System/system.scss
@@ -3,7 +3,7 @@
table caption {
font-weight: bold;
font-size: larger;
- color: black;
+ color: var(--bs-emphasis-color, black);
}
.help-popup {
@@ -20,6 +20,10 @@ table caption {
.required-field {
color: #dc3545;
+
+ [data-bs-theme='dark'] & {
+ color: #f85149;
+ }
}
.visually-hidden-focusable:active,
@@ -28,7 +32,6 @@ table caption {
}
$masthead-height: 70px !default;
-$layout-divider-color: #aaa !default;
$site-nav-width: 250px !default;
/* Banner */
@@ -40,7 +43,7 @@ $site-nav-width: 250px !default;
display: flex;
height: $masthead-height;
background-color: var(--bs-primary, #038);
- border-bottom: 1px solid $layout-divider-color;
+ border-bottom: 1px solid var(--ww-layout-divider-color, #aaa);
margin: 0;
padding: 0;
z-index: 20;
@@ -63,7 +66,8 @@ $site-nav-width: 250px !default;
display: flex;
align-items: center;
justify-content: space-between;
- padding: 5px 0;
+ padding: 5px 0.5rem;
+ gap: 0.25rem;
background-color: var(--ww-logo-background-color, #104aad);
z-index: 20;
width: $site-nav-width;
@@ -83,18 +87,18 @@ $site-nav-width: 250px !default;
}
}
- a,
- span {
+ a {
display: inline-block;
- margin-right: 0.5rem;
}
}
.institution-logo {
display: flex;
flex-grow: 1;
+ gap: 2rem;
align-items: center;
- padding: 8px 0;
+ justify-content: space-between;
+ padding: 0;
max-height: $masthead-height - 1px;
@media only screen and (max-width: 768px) {
@@ -108,8 +112,14 @@ $site-nav-width: 250px !default;
a {
display: block;
- margin-left: 0.5rem;
- margin-right: 0.5rem;
+ }
+
+ #color-scheme-chooser {
+ --bs-btn-color: var(--ww-primary-foreground-color, white) !important;
+ --bs-btn-hover-color: var(--ww-color-chooser-hover-color, #ccc);
+ --bs-btn-active-color: var(--ww-color-chooser-hover-color, #ccc);
+ --bs-btn-focus-shadow-rgb: var(--ww-color-chooser-focus-outline-color-rgb, 255, 255, 255);
+ text-decoration: none;
}
}
@@ -118,16 +128,11 @@ $site-nav-width: 250px !default;
height: $masthead-height - 1px;
padding: 4px 10px 4px 0;
color: var(--ww-primary-foreground-color, white);
- text-align: right;
font-size: 0.85em;
font-weight: normal;
a {
color: black;
-
- &:first-child {
- margin-bottom: 5px;
- }
}
}
}
@@ -144,7 +149,7 @@ $site-nav-width: 250px !default;
overflow-y: auto;
transition-property: left, border-right-width;
transition-duration: 0.3s;
- border-right: 1px solid $layout-divider-color;
+ border-right: 1px solid var(--ww-layout-divider-color, #aaa);
padding: 2px;
&.toggle-width {
@@ -176,7 +181,7 @@ $site-nav-width: 250px !default;
.info-box {
border-radius: 0;
border: none;
- border-top: 1px solid $layout-divider-color;
+ border-top: 1px solid var(--ww-layout-divider-color, #aaa);
}
.nav {
@@ -208,7 +213,7 @@ $site-nav-width: 250px !default;
padding-right: 0;
li a:hover {
- background: #e1e1e1;
+ background: var(--ww-site-nav-link-hover-background-color, #e1e1e1);
}
ul.nav {
@@ -268,22 +273,15 @@ $site-nav-width: 250px !default;
}
#toggle-sidebar {
- #toggle-sidebar-icon i {
- padding: 0.25rem;
- border-radius: 5px;
- color: rgba(255, 255, 255, 0.85);
- transition:
- color 0.15s ease-in-out,
- background-color 0.15s ease-in-out,
- border-color 0.15s ease-in-out;
+ --bs-navbar-color: rgba(var(--ww-toggle-sidebar-icon-color-rgb, 255, 255, 255), 0.85);
+ --bs-navbar-toggler-border-radius: 0.375rem;
+ --bs-navbar-toggler-focus-width: 0.25rem;
+ --bs-navbar-toggler-padding-x: 0.25rem;
+ --bs-navbar-toggler-padding-y: 0.25rem;
+ --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;
- &:hover {
- color: #fff;
- }
- }
-
- &:focus #toggle-sidebar-icon i {
- outline: 1px solid var(--bs-link-hover-color);
+ &:hover {
+ --bs-navbar-color: var(--ww-toggle-sidebar-icon-hover-color, #fff);
}
}
@@ -297,6 +295,12 @@ $site-nav-width: 250px !default;
margin-bottom: 10px;
align-items: center;
+ [data-bs-theme='dark'] & {
+ box-shadow:
+ inset 0 0 3px 2px #000,
+ 0 0 2px 1px #fff;
+ }
+
.progress-bar {
box-shadow: inset 0 0 3px 2px #000;
height: 100%;
@@ -329,6 +333,7 @@ $site-nav-width: 250px !default;
/* Show me another */
div.showMeAnotherBox {
+ color: #212529;
background-color: #ede275;
border-radius: 5px;
border: 2px solid #fdd017;
@@ -344,7 +349,7 @@ div.showMeAnotherBox {
padding-left: 0.5rem;
min-height: 38px;
align-items: center;
- border: 1px solid #e6e6e6;
+ border: 1px solid var(--ww-layout-border-color, #e6e6e6);
border-radius: 4px;
}
}
@@ -362,10 +367,6 @@ h1.page-title {
}
}
-h2.page-title {
- border-bottom: 1px solid #ccc;
-}
-
.problem-sub-header {
margin-top: 0.25rem;
font-weight: bold;
@@ -373,13 +374,6 @@ h2.page-title {
line-height: 1.4;
}
-.Warnings {
- code {
- white-space: normal;
- color: inherit;
- }
-}
-
.error-output {
word-wrap: break-word;
color: #d63384;
@@ -387,6 +381,10 @@ h2.page-title {
direction: ltr;
font-family: monospace;
font-size: 9pt;
+
+ [data-bs-theme='dark'] & {
+ color: var(--bs-danger-text-emphasis);
+ }
}
/* Question nav section */
@@ -396,10 +394,10 @@ h2.page-title {
gap: 0.5rem;
align-content: space-between;
justify-content: space-between;
- z-index: 20;
+ z-index: 19;
position: sticky;
top: $masthead-height;
- background-color: white;
+ background-color: var(--bs-body-bg, white);
margin-bottom: 1rem;
padding: 0.25rem;
margin-left: 0;
@@ -449,6 +447,10 @@ h2.page-title {
width: 60%;
padding: 10px;
text-align: left;
+
+ [data-bs-theme='dark'] & {
+ background-color: #292900;
+ }
}
/* Home Page */
@@ -457,7 +459,7 @@ ul.courses-list {
margin: 0;
a {
- border: 1px solid #e6e6e6;
+ border: 1px solid var(--ww-layout-border-color, #e6e6e6);
display: block;
padding: 0.5em;
margin-bottom: 0.5em;
@@ -465,6 +467,11 @@ ul.courses-list {
width: 95%;
font-weight: bold;
+ [data-bs-theme='dark'] & {
+ background: var(--bs-primary-bg-subtle, black);
+ color: var(--bs-primary-text-emphasis, white);
+ }
+
&:hover {
text-decoration: none;
background: var(--bs-primary, #038);
@@ -493,11 +500,28 @@ ul.courses-list {
td {
white-space: nowrap;
min-width: 20px;
+
+ &.correct {
+ color: #060;
+ }
+
+ &.incorrect {
+ color: #600;
+ }
+
+ [data-bs-theme='dark'] & {
+ &.correct {
+ color: #0b0;
+ }
+
+ &.incorrect {
+ color: #f66;
+ }
+ }
}
.table-rule {
- border-top: 3px solid #d5d5d5;
- padding-top: 5px;
+ border-top: 3px solid var(--ww-layout-divider-color);
}
.essay,
@@ -583,7 +607,7 @@ ul.courses-list {
.info-box {
padding: 0.5em;
border-radius: 8px;
- border: 1px solid #e6e6e6;
+ border: 1px solid var(--ww-layout-border-color, #e6e6e6);
h2,
h3,
@@ -660,6 +684,10 @@ ul.courses-list {
background-color: #f5f5f5;
margin-top: 10px;
margin-bottom: 0;
+
+ [data-bs-theme='dark'] & {
+ background-color: var(--bs-primary-bg-subtle, 'black');
+ }
}
.lb-mlt-group {
@@ -710,6 +738,14 @@ div.AuthorComment {
a {
color: #555;
}
+
+ [data-bs-theme='dark'] & {
+ color: #c6c6c6;
+
+ a {
+ color: #999;
+ }
+ }
}
input.changed[type='text'] {
@@ -740,6 +776,11 @@ input.changed[type='text'] {
border-spacing: 2px;
border-color: gray;
border-radius: 0.25rem;
+
+ [data-bs-theme='dark'] & {
+ background-color: #4a4a4a;
+ border-color: #939393;
+ }
}
.submit-buttons-container {
@@ -760,55 +801,46 @@ input.changed[type='text'] {
font-style: italic;
color: #ca5000;
background-color: inherit;
+
+ [data-bs-theme='dark'] & {
+ color: #ca8253;
+ }
}
/* Text colors for Auditing, Current, and Dropped students */
.Audit {
font-style: normal;
color: purple;
- background-color: inherit;
}
.Enrolled {
font-weight: normal;
- color: black;
- background-color: inherit;
}
.Drop {
font-style: italic;
color: #555;
- background-color: inherit;
}
.Observer {
font-style: normal;
color: green;
- background-color: inherit;
}
-/* Styles for the PGProblemEditor Page */
-
-#editor {
- .tab-content {
- min-height: 140px;
+[data-bs-theme='dark'] {
+ .Audit {
+ color: #f400f4;
+ }
+ .Drop {
+ color: #958888;
+ }
+ .Observer {
+ color: #04a404;
}
}
-#pgedit-render-area {
- border: 1px solid #ddd;
- min-height: 400px;
- height: 600px;
- resize: vertical;
- display: flex;
- flex-direction: column;
-
- @media only screen and (max-width: 992px) {
- min-height: 200px;
- height: 300px;
- }
+/* Common styles for pages containing an editor. */
- #pgedit-render-iframe {
- flex-grow: 1;
- border: none;
- width: 100%;
+#editor {
+ .tab-content {
+ min-height: 140px;
}
}
@@ -854,6 +886,14 @@ input.changed[type='text'] {
.table {
--bs-table-bg: #f5f5f5;
}
+
+ [data-bs-theme='dark'] & {
+ background-color: var(--bs-primary-bg-subtle, 'black');
+
+ .table {
+ --bs-table-bg: var(--bs-primary-bg-subtle, 'black');
+ }
+ }
}
.pdr_placeholder {
@@ -882,10 +922,6 @@ input.changed[type='text'] {
}
}
-.sortable-ghost {
- opacity: 0.5;
-}
-
#psd_list {
padding-left: 0;
padding-bottom: 0.25rem;
@@ -893,6 +929,10 @@ input.changed[type='text'] {
.psd_list_item {
list-style-type: none;
+
+ &.sortable-ghost {
+ opacity: 0.5;
+ }
}
&:not(.disable_renumber) .pdr_handle {
@@ -908,6 +948,10 @@ input.changed[type='text'] {
.rpc_render_area_container {
background-color: #f5f5f5;
+
+ [data-bs-theme='dark'] & {
+ background-color: var(--bs-primary-bg-subtle, 'black');
+ }
}
.rpc_render_area iframe {
@@ -943,6 +987,21 @@ input.changed[type='text'] {
color: inherit;
background-color: #88ecff;
}
+
+ [data-bs-theme='dark'] & {
+ &.correct {
+ color: black;
+ }
+
+ &.incorrect {
+ color: white;
+ background-color: #bf5454;
+ }
+
+ &.unattempted {
+ color: black;
+ }
+ }
}
}
@@ -953,6 +1012,10 @@ input.changed[type='text'] {
font-weight: bold;
color: inherit;
border-radius: 0;
+
+ &:focus-visible {
+ box-shadow: 0 0 0 0.25rem var(--ww-course-config-tab-link-focus-outline-color, #00338840);
+ }
}
&:not(.active) {
@@ -964,12 +1027,30 @@ input.changed[type='text'] {
color: inherit;
}
+ [data-bs-theme='dark'] & {
+ &:not(.active) {
+ background-color: #565656;
+ }
+
+ &:not(.active):hover {
+ background-color: #414141;
+ }
+ }
+
&:focus {
z-index: 2;
}
}
}
+/* Stats */
+
+[data-bs-theme='dark'] .stats-image {
+ text {
+ fill: white;
+ }
+}
+
/* File manager */
.file-manager-btn {
margin-bottom: 0.25rem;
@@ -988,25 +1069,55 @@ input.changed[type='text'] {
/* Problem graders */
-span.needs-grading,
-td.needs-grading {
- background-color: #fff3cd;
+#problem-grader-form {
+ .needs-grading {
+ background-color: #fff3cd;
- div {
- font-weight: bold;
+ [data-bs-theme='dark'] & {
+ background-color: #261d00;
+ }
+
+ div {
+ font-weight: bold;
+ }
}
-}
-span.alt-source,
-td.alt-source {
- background-color: #e6e7e9;
-}
+ .alt-source {
+ background-color: #e6e7e9;
-#problem-grader-form {
- .past-answer:not(:last-child) {
- border-bottom: 1px solid #d5d5d5;
- margin-bottom: 2px;
- padding-bottom: 5px;
+ [data-bs-theme='dark'] & {
+ background-color: #555;
+ }
+ }
+
+ .problem-grader-legend-key span {
+ border: 1px solid var(--ww-layout-border-color);
+ }
+
+ .past-answer {
+ &:not(:last-child) {
+ border-bottom: 1px solid var(--bs-table-border-color);
+ margin-bottom: 2px;
+ padding-bottom: 5px;
+ }
+
+ &.correct {
+ color: #060;
+ }
+
+ &.incorrect {
+ color: #600;
+ }
+
+ [data-bs-theme='dark'] & {
+ &.correct {
+ color: #0b0;
+ }
+
+ &.incorrect {
+ color: #f66;
+ }
+ }
}
.restricted-width-col {
@@ -1018,6 +1129,17 @@ td.alt-source {
}
}
+.problem-grader.accordion {
+ .accordion-header {
+ .accordion-button {
+ --bs-accordion-btn-padding-x: 0.75rem;
+ --bs-accordion-btn-padding-y: 0.375rem;
+ --bs-accordion-btn-bg: var(--bs-primary, #038);
+ --bs-accordion-btn-color: var(--ww-primary-foreground-color, white);
+ }
+ }
+}
+
.problem-grader-table {
.col-fixed {
width: 11rem;
@@ -1054,3 +1176,13 @@ td.alt-source {
width: 100%;
}
}
+
+mjx-help-background {
+ z-index: 1055;
+}
+
+[data-bs-theme='dark'] .flatpickr-confirm {
+ svg {
+ fill: white;
+ }
+}
diff --git a/htdocs/js/TagWidget/tagwidget.js b/htdocs/js/TagWidget/tagwidget.js
index 7961a17ca7..3ac04cd62b 100644
--- a/htdocs/js/TagWidget/tagwidget.js
+++ b/htdocs/js/TagWidget/tagwidget.js
@@ -48,7 +48,7 @@
// Load the library taxonomy from the JSON file.
const response = await fetch(tagWidgetScript.dataset.taxonomy).catch(
- (err) => `Could not load the OPL taxonomy from the server: ${err.messsage ?? err}`
+ (err) => `Could not load the OPL taxonomy from the server: ${err.message ?? err}`
);
if (typeof response === 'string') return showMessage(response);
if (!response.ok) return showMessage('Could not load the OPL taxonomy from the server.');
diff --git a/htdocs/js/UserList/userlist.js b/htdocs/js/UserList/userlist.js
index 617594371f..483dbc1e19 100644
--- a/htdocs/js/UserList/userlist.js
+++ b/htdocs/js/UserList/userlist.js
@@ -58,7 +58,7 @@
}
} else {
element?.classList.remove('is-invalid');
- if (element.id in event_listeners) {
+ if (element && element.id in event_listeners) {
element?.removeEventListener('change', event_listeners[element.id]);
delete event_listeners[element.id];
}
diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json
index 426c624553..c2997b2dbb 100644
--- a/htdocs/package-lock.json
+++ b/htdocs/package-lock.json
@@ -7,35 +7,35 @@
"name": "webwork.javascript_package_manager",
"license": "GPL-2.0+",
"dependencies": {
- "@fortawesome/fontawesome-free": "^6.5.2",
- "@openwebwork/pg-codemirror-editor": "^0.0.5",
- "bootstrap": "~5.3.3",
+ "@fortawesome/fontawesome-free": "^7.0.0",
+ "@openwebwork/pg-codemirror-editor": "^0.0.10",
+ "bootstrap": "~5.3.7",
"flatpickr": "^4.6.13",
- "iframe-resizer": "^4.3.11",
+ "iframe-resizer": "^4.4.2",
"jquery": "^3.7.1",
- "jquery-ui-dist": "^1.13.2",
- "luxon": "^3.4.4",
- "mathjax": "^3.2.2",
+ "jquery-ui-dist": "^1.13.3",
+ "luxon": "^3.7.1",
+ "mathjax": "^4.1.1",
"minisearch": "^7.1.2",
"shortcut-buttons-flatpickr": "^0.4.0",
- "sortablejs": "^1.15.2"
+ "sortablejs": "^1.15.6"
},
"devDependencies": {
- "autoprefixer": "^10.4.19",
- "chokidar": "^3.6.0",
- "cssnano": "^6.1.2",
- "postcss": "^8.5.10",
- "prettier": "^3.2.5",
- "rtlcss": "^4.1.1",
- "sass": "^1.75.0",
- "terser": "^5.30.4",
- "yargs": "^17.7.2"
+ "autoprefixer": "^10.4.21",
+ "chokidar": "^4.0.3",
+ "cssnano": "^7.1.0",
+ "postcss": "^8.5.12",
+ "prettier": "^3.6.2",
+ "rtlcss": "^4.3.0",
+ "sass": "^1.90.0",
+ "terser": "^5.43.1",
+ "yargs": "^18.0.0"
}
},
"node_modules/@codemirror/autocomplete": {
- "version": "6.18.6",
- "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
- "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
+ "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -45,13 +45,13 @@
}
},
"node_modules/@codemirror/commands": {
- "version": "6.8.1",
- "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
- "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz",
+ "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
- "@codemirror/state": "^6.4.0",
+ "@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
@@ -70,9 +70,9 @@
}
},
"node_modules/@codemirror/lang-html": {
- "version": "6.4.9",
- "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
- "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+ "version": "6.4.11",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
+ "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@@ -83,13 +83,13 @@
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/css": "^1.1.0",
- "@lezer/html": "^1.3.0"
+ "@lezer/html": "^1.3.12"
}
},
"node_modules/@codemirror/lang-javascript": {
- "version": "6.2.4",
- "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
- "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
+ "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@@ -116,23 +116,23 @@
}
},
"node_modules/@codemirror/language": {
- "version": "6.11.1",
- "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
- "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
+ "version": "6.12.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz",
+ "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
- "@lezer/common": "^1.1.0",
+ "@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
- "version": "6.8.5",
- "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
- "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
+ "version": "6.9.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz",
+ "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@@ -141,20 +141,20 @@
}
},
"node_modules/@codemirror/search": {
- "version": "6.5.11",
- "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
- "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz",
+ "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
- "@codemirror/view": "^6.0.0",
+ "@codemirror/view": "^6.37.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
- "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
+ "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
@@ -173,38 +173,35 @@
}
},
"node_modules/@codemirror/view": {
- "version": "6.37.2",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz",
- "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==",
+ "version": "6.41.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz",
+ "integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==",
"license": "MIT",
"dependencies": {
- "@codemirror/state": "^6.5.0",
+ "@codemirror/state": "^6.6.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@fortawesome/fontawesome-free": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
- "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==",
- "hasInstallScript": true,
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz",
+ "integrity": "sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ==",
+ "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -212,55 +209,50 @@
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
+ "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lezer/common": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
- "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
+ "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
"license": "MIT"
},
"node_modules/@lezer/css": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
- "integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz",
+ "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -269,18 +261,18 @@
}
},
"node_modules/@lezer/highlight": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
- "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
+ "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"license": "MIT",
"dependencies": {
- "@lezer/common": "^1.0.0"
+ "@lezer/common": "^1.3.0"
}
},
"node_modules/@lezer/html": {
- "version": "1.3.10",
- "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
- "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+ "version": "1.3.13",
+ "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
+ "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -289,9 +281,9 @@
}
},
"node_modules/@lezer/javascript": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
- "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
+ "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -300,9 +292,9 @@
}
},
"node_modules/@lezer/lr": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
- "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz",
+ "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
@@ -325,27 +317,33 @@
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
+ "node_modules/@mathjax/mathjax-newcm-font": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@mathjax/mathjax-newcm-font/-/mathjax-newcm-font-4.1.1.tgz",
+ "integrity": "sha512-LeV5AWzoR7k/k2tg5mW0Ad3Jr9oK9guW/zBUIP8aoiIZcZIhvAV9dlbtUSqSe1wgEBUP1KOcJXcrE/NxOqkxlQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/@openwebwork/codemirror-lang-pg": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@openwebwork/codemirror-lang-pg/-/codemirror-lang-pg-0.0.3.tgz",
- "integrity": "sha512-ZKtb2r0Ck6tVz44wwMKY7w/82P470whIzM5C8pi2GiBlnFWmkQcadmOaCBz31FELY1SrKE3AO2TmMrwUt2EKPA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/@openwebwork/codemirror-lang-pg/-/codemirror-lang-pg-0.0.5.tgz",
+ "integrity": "sha512-3zNm9aFc6/IHBG4lJq8QOc2bF2rIFkSPX2hwCexgEcmchVBOsYPSfB0+bRsftfQQ4YdPFP64uDlSZBKu1yRcZQ==",
"license": "MIT",
"dependencies": {
- "@codemirror/language": "^6.11.0",
- "@lezer/highlight": "^1.2.1",
- "@lezer/lr": "^1.4.2"
+ "@codemirror/language": "^6.12.3",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.8"
}
},
"node_modules/@openwebwork/pg-codemirror-editor": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.5.tgz",
- "integrity": "sha512-ue4aciJzF7PPdQwWvYQkSf7h3pqOSM21fXyQ1vXRNndiQ7TCNX6FBI5JalMWDzi7CttGR+Mj+PgffmADwmKIMg==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.10.tgz",
+ "integrity": "sha512-kX7aJaWl2UagEX6kVToxuMUpYyh8zlSbg2chUv71e9nIw4jcwpvis8WkCSsYGRJw45hQTN1N42MPJOYqV+8fzQ==",
"license": "MIT",
"dependencies": {
- "@codemirror/lang-html": "^6.4.9",
+ "@codemirror/lang-html": "^6.4.11",
"@codemirror/lang-xml": "^6.1.0",
- "@codemirror/theme-one-dark": "^6.1.2",
- "@openwebwork/codemirror-lang-pg": "^0.0.3",
+ "@codemirror/theme-one-dark": "^6.1.3",
+ "@openwebwork/codemirror-lang-pg": "^0.0.5",
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.3.0",
"cm6-theme-basic-dark": "^0.2.0",
@@ -356,17 +354,328 @@
"cm6-theme-nord": "^0.2.0",
"cm6-theme-solarized-dark": "^0.2.0",
"cm6-theme-solarized-light": "^0.2.0",
- "codemirror": "^6.0.1",
- "codemirror-lang-mt": "^0.0.3",
- "codemirror-lang-perl": "^0.1.6",
- "style-mod": "^4.1.2",
+ "codemirror": "^6.0.2",
+ "codemirror-lang-mt": "^0.0.5",
+ "codemirror-lang-perl": "^0.1.8",
+ "style-mod": "^4.1.3",
"thememirror": "^2.0.1"
}
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
@@ -400,10 +709,11 @@
}
},
"node_modules/acorn": {
- "version": "8.11.3",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
- "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
+ "license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -412,46 +722,35 @@
}
},
"node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
- "dependencies": {
- "color-convert": "^2.0.1"
- },
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/autoprefixer": {
- "version": "10.4.19",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
- "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"dev": true,
"funding": [
{
@@ -467,12 +766,13 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
- "caniuse-lite": "^1.0.30001599",
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
+ "picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
@@ -485,25 +785,17 @@
"postcss": "^8.1.0"
}
},
- "node_modules/binary-extensions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
- "dev": true
+ "dev": true,
+ "license": "ISC"
},
"node_modules/bootstrap": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
- "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
+ "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"funding": [
{
"type": "github",
@@ -514,6 +806,7 @@
"url": "https://opencollective.com/bootstrap"
}
],
+ "license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
@@ -523,6 +816,8 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"dependencies": {
"fill-range": "^7.1.1"
},
@@ -531,9 +826,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.23.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
- "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"dev": true,
"funding": [
{
@@ -549,11 +844,12 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "caniuse-lite": "^1.0.30001587",
- "electron-to-chromium": "^1.4.668",
- "node-releases": "^2.0.14",
- "update-browserslist-db": "^1.0.13"
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
},
"bin": {
"browserslist": "cli.js"
@@ -566,13 +862,15 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
"integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"browserslist": "^4.0.0",
"caniuse-lite": "^1.0.0",
@@ -581,9 +879,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001723",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
- "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
+ "version": "1.0.30001759",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
+ "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
"dev": true,
"funding": [
{
@@ -602,41 +900,34 @@
"license": "CC-BY-4.0"
},
"node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^4.0.1"
},
"engines": {
- "node": ">= 8.10.0"
+ "node": ">= 14.16.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
}
},
"node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
"dev": true,
+ "license": "ISC",
"dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=20"
}
},
"node_modules/cm6-theme-basic-dark": {
@@ -751,61 +1042,45 @@
}
},
"node_modules/codemirror-lang-mt": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/codemirror-lang-mt/-/codemirror-lang-mt-0.0.3.tgz",
- "integrity": "sha512-OC2qG6GQI96BTgKzD4XVJd8fLOTyeB0pb7gy41BHjqot2gES6Bi/3esIgTygYUxw/SspMNSZnQZdWHPmD7/YYA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/codemirror-lang-mt/-/codemirror-lang-mt-0.0.5.tgz",
+ "integrity": "sha512-Gb7Di+IDN5zruiKhcbqCA95b+DzGagC1YJas4o9gQvGSHt3u3iaktLG19TO2F4miKLYhrV+3UOMGsOb+4iKZLQ==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-css": "^6.3.1",
- "@codemirror/lang-html": "^6.4.9",
- "@codemirror/lang-javascript": "^6.2.3",
- "@codemirror/language": "^6.11.0",
- "@lezer/highlight": "^1.2.1",
- "@lezer/lr": "^1.4.2"
+ "@codemirror/lang-html": "^6.4.11",
+ "@codemirror/lang-javascript": "^6.2.5",
+ "@codemirror/language": "^6.12.3",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.8"
}
},
"node_modules/codemirror-lang-perl": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/codemirror-lang-perl/-/codemirror-lang-perl-0.1.6.tgz",
- "integrity": "sha512-cYBmCQa7/itIRNx1AHfn9OQkInJQpL0sqCoikJXhQZwYNOHFSMd0L16pofEOU/1/f+s7CPA+XucUrNr/f1qigQ==",
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/codemirror-lang-perl/-/codemirror-lang-perl-0.1.8.tgz",
+ "integrity": "sha512-l3z73XLzFjrkd1LN/9KwR3tsO9oegmCBTgQC4Bg9vJhmv4js0RtnEAN3mUMWtib1Qecb/dQc5mpNqClYGhHuYA==",
"license": "MIT",
"dependencies": {
- "@codemirror/language": "^6.11.0",
- "@lezer/highlight": "^1.2.1",
- "@lezer/lr": "^1.4.2"
+ "@codemirror/language": "^6.12.3",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.8"
}
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
"node_modules/colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">= 10"
+ "node": ">=16"
}
},
"node_modules/crelt": {
@@ -819,6 +1094,7 @@
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
"integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==",
"dev": true,
+ "license": "ISC",
"engines": {
"node": "^14 || ^16 || >=18"
},
@@ -827,10 +1103,11 @@
}
},
"node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
@@ -843,12 +1120,13 @@
}
},
"node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "mdn-data": "2.0.30",
+ "mdn-data": "2.12.2",
"source-map-js": "^1.0.1"
},
"engines": {
@@ -856,10 +1134,11 @@
}
},
"node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
@@ -872,6 +1151,7 @@
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
+ "license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
@@ -880,79 +1160,82 @@
}
},
"node_modules/cssnano": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz",
- "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz",
+ "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "cssnano-preset-default": "^6.1.2",
- "lilconfig": "^3.1.1"
+ "cssnano-preset-default": "^7.0.8",
+ "lilconfig": "^3.1.3"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/cssnano"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/cssnano-preset-default": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz",
- "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==",
+ "version": "7.0.8",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz",
+ "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"css-declaration-sorter": "^7.2.0",
- "cssnano-utils": "^4.0.2",
- "postcss-calc": "^9.0.1",
- "postcss-colormin": "^6.1.0",
- "postcss-convert-values": "^6.1.0",
- "postcss-discard-comments": "^6.0.2",
- "postcss-discard-duplicates": "^6.0.3",
- "postcss-discard-empty": "^6.0.3",
- "postcss-discard-overridden": "^6.0.2",
- "postcss-merge-longhand": "^6.0.5",
- "postcss-merge-rules": "^6.1.1",
- "postcss-minify-font-values": "^6.1.0",
- "postcss-minify-gradients": "^6.0.3",
- "postcss-minify-params": "^6.1.0",
- "postcss-minify-selectors": "^6.0.4",
- "postcss-normalize-charset": "^6.0.2",
- "postcss-normalize-display-values": "^6.0.2",
- "postcss-normalize-positions": "^6.0.2",
- "postcss-normalize-repeat-style": "^6.0.2",
- "postcss-normalize-string": "^6.0.2",
- "postcss-normalize-timing-functions": "^6.0.2",
- "postcss-normalize-unicode": "^6.1.0",
- "postcss-normalize-url": "^6.0.2",
- "postcss-normalize-whitespace": "^6.0.2",
- "postcss-ordered-values": "^6.0.2",
- "postcss-reduce-initial": "^6.1.0",
- "postcss-reduce-transforms": "^6.0.2",
- "postcss-svgo": "^6.0.3",
- "postcss-unique-selectors": "^6.0.4"
- },
- "engines": {
- "node": "^14 || ^16 || >=18.0"
+ "cssnano-utils": "^5.0.1",
+ "postcss-calc": "^10.1.1",
+ "postcss-colormin": "^7.0.4",
+ "postcss-convert-values": "^7.0.6",
+ "postcss-discard-comments": "^7.0.4",
+ "postcss-discard-duplicates": "^7.0.2",
+ "postcss-discard-empty": "^7.0.1",
+ "postcss-discard-overridden": "^7.0.1",
+ "postcss-merge-longhand": "^7.0.5",
+ "postcss-merge-rules": "^7.0.6",
+ "postcss-minify-font-values": "^7.0.1",
+ "postcss-minify-gradients": "^7.0.1",
+ "postcss-minify-params": "^7.0.4",
+ "postcss-minify-selectors": "^7.0.5",
+ "postcss-normalize-charset": "^7.0.1",
+ "postcss-normalize-display-values": "^7.0.1",
+ "postcss-normalize-positions": "^7.0.1",
+ "postcss-normalize-repeat-style": "^7.0.1",
+ "postcss-normalize-string": "^7.0.1",
+ "postcss-normalize-timing-functions": "^7.0.1",
+ "postcss-normalize-unicode": "^7.0.4",
+ "postcss-normalize-url": "^7.0.1",
+ "postcss-normalize-whitespace": "^7.0.1",
+ "postcss-ordered-values": "^7.0.2",
+ "postcss-reduce-initial": "^7.0.4",
+ "postcss-reduce-transforms": "^7.0.1",
+ "postcss-svgo": "^7.1.0",
+ "postcss-unique-selectors": "^7.0.4"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/cssnano-utils": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz",
- "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz",
+ "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/csso": {
@@ -960,6 +1243,7 @@
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"css-tree": "~2.2.0"
},
@@ -973,6 +1257,7 @@
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"mdn-data": "2.0.28",
"source-map-js": "^1.0.1"
@@ -986,13 +1271,29 @@
"version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
- "dev": true
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
@@ -1012,13 +1313,15 @@
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
- ]
+ ],
+ "license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
@@ -1030,10 +1333,11 @@
}
},
"node_modules/domutils": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
- "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
@@ -1044,22 +1348,25 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.4.747",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz",
- "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==",
- "dev": true
+ "version": "1.5.183",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.183.tgz",
+ "integrity": "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
+ "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
@@ -1068,10 +1375,11 @@
}
},
"node_modules/escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -1081,6 +1389,8 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1091,13 +1401,15 @@
"node_modules/flatpickr": {
"version": "4.6.13",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
- "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw=="
+ "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==",
+ "license": "MIT"
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": "*"
},
@@ -1106,94 +1418,68 @@
"url": "https://github.com/sponsors/rawify"
}
},
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
+ "license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "node_modules/get-east-asian-width": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
+ "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
"dev": true,
- "dependencies": {
- "is-glob": "^4.0.1"
- },
+ "license": "MIT",
"engines": {
- "node": ">= 6"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/iframe-resizer": {
- "version": "4.3.11",
- "resolved": "https://registry.npmjs.org/iframe-resizer/-/iframe-resizer-4.3.11.tgz",
- "integrity": "sha512-5QtnsmfH11GDsuC7Gxd/eNzojudX3346Gb0E+Ku8ln8AtfSq+cWCZtnhCrthrtE7f1CI2/kwHkZ9G4sFYzHP7A==",
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/iframe-resizer/-/iframe-resizer-4.4.2.tgz",
+ "integrity": "sha512-2SupFCq9V9osWac4q+PodF0E9QdWY5A9VdCpKrrE7HlDrcIsaTp7D6k14mkGXWoWMS9jCavYusik25wTc0YB2Q==",
+ "hasInstallScript": true,
+ "license": "GPL-3.0",
"engines": {
"node": ">=0.8.0"
},
"funding": {
"type": "individual",
- "url": "https://github.com/davidjbradshaw/iframe-resizer/blob/master/FUNDING.md"
+ "url": "https://iframe-resizer.com//pricing"
}
},
"node_modules/immutable": {
- "version": "4.3.8",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz",
- "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==",
- "dev": true
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
+ "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"dev": true,
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT"
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -1206,6 +1492,8 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"engines": {
"node": ">=0.12.0"
}
@@ -1213,21 +1501,24 @@
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
- "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
+ "license": "MIT"
},
"node_modules/jquery-ui-dist": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.13.2.tgz",
- "integrity": "sha512-oVDRd1NLtTbBwpRKAYdIRgpWVDzeBhfy7Gu0RmY6JEaZtmBq6kDn1pm5SgDiAotrnDS+RoTRXO6xvcNTxA9tOA==",
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.13.3.tgz",
+ "integrity": "sha512-qeTR3SOSQ0jgxaNXSFU6+JtxdzNUSJKgp8LCzVrVKntM25+2YBJW1Ea8B2AwjmmSHfPLy2dSlZxJQN06OfVFhg==",
+ "license": "MIT",
"dependencies": {
"jquery": ">=1.8.0 <4.0.0"
}
},
"node_modules/lilconfig": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
- "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=14"
},
@@ -1239,32 +1530,55 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/luxon": {
- "version": "3.4.4",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
- "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",
+ "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==",
+ "license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/mathjax": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.2.tgz",
- "integrity": "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw=="
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-4.1.1.tgz",
+ "integrity": "sha512-NyvA8c39LUUM/m+oCg7sfA13hmw7yGkre5kiRWN9qzChCyhce39lecnbjgMA/oEUgq9Vyetk6u78apwcIXpW/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mathjax/mathjax-newcm-font": "^4.1.1"
+ }
},
"node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
- "dev": true
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
},
"node_modules/minisearch": {
"version": "7.1.2",
@@ -1283,6 +1597,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -1290,26 +1605,27 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/node-releases": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
- "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
- "dev": true
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
},
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
+ "license": "MIT"
},
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -1319,6 +1635,7 @@
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
@@ -1330,13 +1647,16 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true
+ "dev": true,
+ "license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"engines": {
"node": ">=8.6"
},
@@ -1345,9 +1665,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.10",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
- "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "version": "8.5.12",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
+ "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
"dev": true,
"funding": [
{
@@ -1363,6 +1683,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -1373,386 +1694,416 @@
}
},
"node_modules/postcss-calc": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz",
- "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==",
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz",
+ "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "postcss-selector-parser": "^6.0.11",
+ "postcss-selector-parser": "^7.0.0",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12 || ^20.9 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.2.2"
+ "postcss": "^8.4.38"
}
},
"node_modules/postcss-colormin": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz",
- "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz",
+ "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"caniuse-api": "^3.0.0",
"colord": "^2.9.3",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-convert-values": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz",
- "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz",
+ "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-discard-comments": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz",
- "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz",
+ "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==",
"dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^7.1.0"
+ },
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-discard-duplicates": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz",
- "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz",
+ "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-discard-empty": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz",
- "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz",
+ "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-discard-overridden": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz",
- "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz",
+ "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-merge-longhand": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz",
- "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz",
+ "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0",
- "stylehacks": "^6.1.1"
+ "stylehacks": "^7.0.5"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-merge-rules": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz",
- "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz",
+ "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"caniuse-api": "^3.0.0",
- "cssnano-utils": "^4.0.2",
- "postcss-selector-parser": "^6.0.16"
+ "cssnano-utils": "^5.0.1",
+ "postcss-selector-parser": "^7.1.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-minify-font-values": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz",
- "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz",
+ "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-minify-gradients": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz",
- "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz",
+ "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"colord": "^2.9.3",
- "cssnano-utils": "^4.0.2",
+ "cssnano-utils": "^5.0.1",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-minify-params": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz",
- "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz",
+ "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
- "cssnano-utils": "^4.0.2",
+ "browserslist": "^4.25.1",
+ "cssnano-utils": "^5.0.1",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-minify-selectors": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz",
- "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz",
+ "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "postcss-selector-parser": "^6.0.16"
+ "cssesc": "^3.0.0",
+ "postcss-selector-parser": "^7.1.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-charset": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz",
- "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz",
+ "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-display-values": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz",
- "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz",
+ "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-positions": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz",
- "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz",
+ "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-repeat-style": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz",
- "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz",
+ "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-string": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz",
- "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz",
+ "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-timing-functions": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz",
- "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz",
+ "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-unicode": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz",
- "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz",
+ "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-url": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz",
- "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz",
+ "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-normalize-whitespace": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz",
- "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz",
+ "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-ordered-values": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz",
- "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz",
+ "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "cssnano-utils": "^4.0.2",
+ "cssnano-utils": "^5.0.1",
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-reduce-initial": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz",
- "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz",
+ "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"caniuse-api": "^3.0.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-reduce-transforms": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz",
- "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz",
+ "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-selector-parser": {
- "version": "6.0.16",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
- "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
+ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -1762,47 +2113,51 @@
}
},
"node_modules/postcss-svgo": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz",
- "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz",
+ "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.2.0",
- "svgo": "^3.2.0"
+ "svgo": "^4.0.0"
},
"engines": {
- "node": "^14 || ^16 || >= 18"
+ "node": "^18.12.0 || ^20.9.0 || >= 18"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-unique-selectors": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz",
- "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz",
+ "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "postcss-selector-parser": "^6.0.16"
+ "postcss-selector-parser": "^7.1.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/prettier": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
- "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
+ "license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -1814,31 +2169,25 @@
}
},
"node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/rtlcss": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz",
- "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz",
+ "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0",
@@ -1853,13 +2202,14 @@
}
},
"node_modules/sass": {
- "version": "1.75.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
- "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
+ "version": "1.90.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
+ "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "chokidar": ">=3.0.0 <4.0.0",
- "immutable": "^4.0.0",
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
@@ -1867,6 +2217,9 @@
},
"engines": {
"node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
}
},
"node_modules/sax": {
@@ -1874,6 +2227,7 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz",
"integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==",
"dev": true,
+ "license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
@@ -1881,18 +2235,21 @@
"node_modules/shortcut-buttons-flatpickr": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/shortcut-buttons-flatpickr/-/shortcut-buttons-flatpickr-0.4.0.tgz",
- "integrity": "sha512-JKmT4my3Hm1e18OvG4Q6RcFhN4WRqqpTMkHrvZ7fup/dp6aTIWGVCHdRYtASkp/FCzDlJh6iCLQ/VcwwNpAMoQ=="
+ "integrity": "sha512-JKmT4my3Hm1e18OvG4Q6RcFhN4WRqqpTMkHrvZ7fup/dp6aTIWGVCHdRYtASkp/FCzDlJh6iCLQ/VcwwNpAMoQ==",
+ "license": "MIT"
},
"node_modules/sortablejs": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz",
- "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA=="
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
+ "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==",
+ "license": "MIT"
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -1902,6 +2259,7 @@
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -1911,35 +2269,44 @@
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "ansi-regex": "^6.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-json-comments": {
@@ -1947,6 +2314,7 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
},
@@ -1955,46 +2323,48 @@
}
},
"node_modules/style-mod": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
- "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
+ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"license": "MIT"
},
"node_modules/stylehacks": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz",
- "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz",
+ "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "browserslist": "^4.23.0",
- "postcss-selector-parser": "^6.0.16"
+ "browserslist": "^4.25.1",
+ "postcss-selector-parser": "^7.1.0"
},
"engines": {
- "node": "^14 || ^16 || >=18.0"
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
},
"peerDependencies": {
- "postcss": "^8.4.31"
+ "postcss": "^8.4.32"
}
},
"node_modules/svgo": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz",
- "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz",
+ "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "commander": "^7.2.0",
+ "commander": "^11.1.0",
"css-select": "^5.1.0",
- "css-tree": "^2.3.1",
+ "css-tree": "^3.0.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
- "picocolors": "^1.0.0",
+ "picocolors": "^1.1.1",
"sax": "^1.5.0"
},
"bin": {
- "svgo": "bin/svgo"
+ "svgo": "bin/svgo.js"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=16"
},
"funding": {
"type": "opencollective",
@@ -2002,13 +2372,14 @@
}
},
"node_modules/terser": {
- "version": "5.30.4",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz",
- "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==",
+ "version": "5.43.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
+ "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
+ "acorn": "^8.14.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
@@ -2023,7 +2394,8 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/thememirror": {
"version": "2.0.1",
@@ -2041,6 +2413,8 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "license": "MIT",
+ "optional": true,
"dependencies": {
"is-number": "^7.0.0"
},
@@ -2049,9 +2423,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.0.13",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
- "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"funding": [
{
@@ -2067,9 +2441,10 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "escalade": "^3.1.1",
- "picocolors": "^1.0.0"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -2082,7 +2457,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
@@ -2091,17 +2467,18 @@
"license": "MIT"
},
"node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
+ "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
@@ -2112,43 +2489,45 @@
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
+ "license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
+ "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "cliui": "^8.0.1",
+ "cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
+ "string-width": "^7.2.0",
"y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
+ "yargs-parser": "^22.0.0"
},
"engines": {
- "node": ">=12"
+ "node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"dev": true,
+ "license": "ISC",
"engines": {
- "node": ">=12"
+ "node": "^20.19.0 || ^22.12.0 || >=23"
}
}
},
"dependencies": {
"@codemirror/autocomplete": {
- "version": "6.18.6",
- "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
- "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
+ "version": "6.20.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz",
+ "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==",
"requires": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
@@ -2157,12 +2536,12 @@
}
},
"@codemirror/commands": {
- "version": "6.8.1",
- "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
- "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
+ "version": "6.10.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz",
+ "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==",
"requires": {
"@codemirror/language": "^6.0.0",
- "@codemirror/state": "^6.4.0",
+ "@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
@@ -2180,9 +2559,9 @@
}
},
"@codemirror/lang-html": {
- "version": "6.4.9",
- "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
- "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
+ "version": "6.4.11",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
+ "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
"requires": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/lang-css": "^6.0.0",
@@ -2192,13 +2571,13 @@
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/css": "^1.1.0",
- "@lezer/html": "^1.3.0"
+ "@lezer/html": "^1.3.12"
}
},
"@codemirror/lang-javascript": {
- "version": "6.2.4",
- "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
- "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz",
+ "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==",
"requires": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
@@ -2223,22 +2602,22 @@
}
},
"@codemirror/language": {
- "version": "6.11.1",
- "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
- "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
+ "version": "6.12.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz",
+ "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
"requires": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
- "@lezer/common": "^1.1.0",
+ "@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"@codemirror/lint": {
- "version": "6.8.5",
- "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
- "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
+ "version": "6.9.5",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz",
+ "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==",
"requires": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
@@ -2246,19 +2625,19 @@
}
},
"@codemirror/search": {
- "version": "6.5.11",
- "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
- "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz",
+ "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==",
"requires": {
"@codemirror/state": "^6.0.0",
- "@codemirror/view": "^6.0.0",
+ "@codemirror/view": "^6.37.0",
"crelt": "^1.0.5"
}
},
"@codemirror/state": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
- "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
+ "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
"requires": {
"@marijn/find-cluster-break": "^1.0.0"
}
@@ -2275,29 +2654,28 @@
}
},
"@codemirror/view": {
- "version": "6.37.2",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz",
- "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==",
+ "version": "6.41.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz",
+ "integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==",
"requires": {
- "@codemirror/state": "^6.5.0",
+ "@codemirror/state": "^6.6.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"@fortawesome/fontawesome-free": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz",
- "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q=="
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.0.tgz",
+ "integrity": "sha512-X48nISrSOa89zu2VMljC4XaRf8NmgTwQBVHfS2Nu5G00ZwM31oOVrAtGxZF3b6wDYf9lJsf/Eq4cCSFKIkOWPQ=="
},
"@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dev": true,
"requires": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
@@ -2307,16 +2685,10 @@
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true
},
- "@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true
- },
"@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
+ "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -2324,15 +2696,15 @@
}
},
"@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"dev": true
},
"@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -2340,14 +2712,14 @@
}
},
"@lezer/common": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
- "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
+ "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="
},
"@lezer/css": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
- "integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz",
+ "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==",
"requires": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
@@ -2355,17 +2727,17 @@
}
},
"@lezer/highlight": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
- "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
+ "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"requires": {
- "@lezer/common": "^1.0.0"
+ "@lezer/common": "^1.3.0"
}
},
"@lezer/html": {
- "version": "1.3.10",
- "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
- "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
+ "version": "1.3.13",
+ "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
+ "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
"requires": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
@@ -2373,9 +2745,9 @@
}
},
"@lezer/javascript": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
- "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
+ "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
"requires": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
@@ -2383,9 +2755,9 @@
}
},
"@lezer/lr": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
- "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz",
+ "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==",
"requires": {
"@lezer/common": "^1.0.0"
}
@@ -2405,25 +2777,30 @@
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
},
+ "@mathjax/mathjax-newcm-font": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@mathjax/mathjax-newcm-font/-/mathjax-newcm-font-4.1.1.tgz",
+ "integrity": "sha512-LeV5AWzoR7k/k2tg5mW0Ad3Jr9oK9guW/zBUIP8aoiIZcZIhvAV9dlbtUSqSe1wgEBUP1KOcJXcrE/NxOqkxlQ=="
+ },
"@openwebwork/codemirror-lang-pg": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/@openwebwork/codemirror-lang-pg/-/codemirror-lang-pg-0.0.3.tgz",
- "integrity": "sha512-ZKtb2r0Ck6tVz44wwMKY7w/82P470whIzM5C8pi2GiBlnFWmkQcadmOaCBz31FELY1SrKE3AO2TmMrwUt2EKPA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/@openwebwork/codemirror-lang-pg/-/codemirror-lang-pg-0.0.5.tgz",
+ "integrity": "sha512-3zNm9aFc6/IHBG4lJq8QOc2bF2rIFkSPX2hwCexgEcmchVBOsYPSfB0+bRsftfQQ4YdPFP64uDlSZBKu1yRcZQ==",
"requires": {
- "@codemirror/language": "^6.11.0",
- "@lezer/highlight": "^1.2.1",
- "@lezer/lr": "^1.4.2"
+ "@codemirror/language": "^6.12.3",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.8"
}
},
"@openwebwork/pg-codemirror-editor": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.5.tgz",
- "integrity": "sha512-ue4aciJzF7PPdQwWvYQkSf7h3pqOSM21fXyQ1vXRNndiQ7TCNX6FBI5JalMWDzi7CttGR+Mj+PgffmADwmKIMg==",
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.10.tgz",
+ "integrity": "sha512-kX7aJaWl2UagEX6kVToxuMUpYyh8zlSbg2chUv71e9nIw4jcwpvis8WkCSsYGRJw45hQTN1N42MPJOYqV+8fzQ==",
"requires": {
- "@codemirror/lang-html": "^6.4.9",
+ "@codemirror/lang-html": "^6.4.11",
"@codemirror/lang-xml": "^6.1.0",
- "@codemirror/theme-one-dark": "^6.1.2",
- "@openwebwork/codemirror-lang-pg": "^0.0.3",
+ "@codemirror/theme-one-dark": "^6.1.3",
+ "@openwebwork/codemirror-lang-pg": "^0.0.5",
"@replit/codemirror-emacs": "^6.1.0",
"@replit/codemirror-vim": "^6.3.0",
"cm6-theme-basic-dark": "^0.2.0",
@@ -2434,13 +2811,130 @@
"cm6-theme-nord": "^0.2.0",
"cm6-theme-solarized-dark": "^0.2.0",
"cm6-theme-solarized-light": "^0.2.0",
- "codemirror": "^6.0.1",
- "codemirror-lang-mt": "^0.0.3",
- "codemirror-lang-perl": "^0.1.6",
- "style-mod": "^4.1.2",
+ "codemirror": "^6.0.2",
+ "codemirror-lang-mt": "^0.0.5",
+ "codemirror-lang-perl": "^0.1.8",
+ "style-mod": "^4.1.3",
"thememirror": "^2.0.1"
}
},
+ "@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1",
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ }
+ },
+ "@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "dev": true,
+ "optional": true
+ },
"@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -2460,56 +2954,37 @@
"requires": {}
},
"acorn": {
- "version": "8.11.3",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
- "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true
},
"ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true
},
"ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "requires": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- }
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true
},
"autoprefixer": {
- "version": "10.4.19",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
- "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
- "caniuse-lite": "^1.0.30001599",
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
+ "picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
}
},
- "binary-extensions": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
- "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
- "dev": true
- },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -2517,9 +2992,9 @@
"dev": true
},
"bootstrap": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
- "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
+ "version": "5.3.7",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
+ "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"requires": {}
},
"braces": {
@@ -2527,20 +3002,21 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
+ "optional": true,
"requires": {
"fill-range": "^7.1.1"
}
},
"browserslist": {
- "version": "4.23.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
- "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+ "version": "4.25.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"dev": true,
"requires": {
- "caniuse-lite": "^1.0.30001587",
- "electron-to-chromium": "^1.4.668",
- "node-releases": "^2.0.14",
- "update-browserslist-db": "^1.0.13"
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
}
},
"buffer-from": {
@@ -2562,36 +3038,29 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001723",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
- "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
+ "version": "1.0.30001759",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
+ "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
"dev": true
},
"chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"requires": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "fsevents": "~2.3.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^4.0.1"
}
},
"cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
"dev": true,
"requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
}
},
"cm6-theme-basic-dark": {
@@ -2657,43 +3126,28 @@
}
},
"codemirror-lang-mt": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/codemirror-lang-mt/-/codemirror-lang-mt-0.0.3.tgz",
- "integrity": "sha512-OC2qG6GQI96BTgKzD4XVJd8fLOTyeB0pb7gy41BHjqot2gES6Bi/3esIgTygYUxw/SspMNSZnQZdWHPmD7/YYA==",
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/codemirror-lang-mt/-/codemirror-lang-mt-0.0.5.tgz",
+ "integrity": "sha512-Gb7Di+IDN5zruiKhcbqCA95b+DzGagC1YJas4o9gQvGSHt3u3iaktLG19TO2F4miKLYhrV+3UOMGsOb+4iKZLQ==",
"requires": {
"@codemirror/lang-css": "^6.3.1",
- "@codemirror/lang-html": "^6.4.9",
- "@codemirror/lang-javascript": "^6.2.3",
- "@codemirror/language": "^6.11.0",
- "@lezer/highlight": "^1.2.1",
- "@lezer/lr": "^1.4.2"
+ "@codemirror/lang-html": "^6.4.11",
+ "@codemirror/lang-javascript": "^6.2.5",
+ "@codemirror/language": "^6.12.3",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.8"
}
},
"codemirror-lang-perl": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/codemirror-lang-perl/-/codemirror-lang-perl-0.1.6.tgz",
- "integrity": "sha512-cYBmCQa7/itIRNx1AHfn9OQkInJQpL0sqCoikJXhQZwYNOHFSMd0L16pofEOU/1/f+s7CPA+XucUrNr/f1qigQ==",
- "requires": {
- "@codemirror/language": "^6.11.0",
- "@lezer/highlight": "^1.2.1",
- "@lezer/lr": "^1.4.2"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/codemirror-lang-perl/-/codemirror-lang-perl-0.1.8.tgz",
+ "integrity": "sha512-l3z73XLzFjrkd1LN/9KwR3tsO9oegmCBTgQC4Bg9vJhmv4js0RtnEAN3mUMWtib1Qecb/dQc5mpNqClYGhHuYA==",
"requires": {
- "color-name": "~1.1.4"
+ "@codemirror/language": "^6.12.3",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.8"
}
},
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true
- },
"colord": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@@ -2701,9 +3155,9 @@
"dev": true
},
"commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"dev": true
},
"crelt": {
@@ -2719,9 +3173,9 @@
"requires": {}
},
"css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"dev": true,
"requires": {
"boolbase": "^1.0.0",
@@ -2732,19 +3186,19 @@
}
},
"css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
"dev": true,
"requires": {
- "mdn-data": "2.0.30",
+ "mdn-data": "2.12.2",
"source-map-js": "^1.0.1"
}
},
"css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"dev": true
},
"cssesc": {
@@ -2754,57 +3208,57 @@
"dev": true
},
"cssnano": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz",
- "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz",
+ "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==",
"dev": true,
"requires": {
- "cssnano-preset-default": "^6.1.2",
- "lilconfig": "^3.1.1"
+ "cssnano-preset-default": "^7.0.8",
+ "lilconfig": "^3.1.3"
}
},
"cssnano-preset-default": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz",
- "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==",
+ "version": "7.0.8",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz",
+ "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"css-declaration-sorter": "^7.2.0",
- "cssnano-utils": "^4.0.2",
- "postcss-calc": "^9.0.1",
- "postcss-colormin": "^6.1.0",
- "postcss-convert-values": "^6.1.0",
- "postcss-discard-comments": "^6.0.2",
- "postcss-discard-duplicates": "^6.0.3",
- "postcss-discard-empty": "^6.0.3",
- "postcss-discard-overridden": "^6.0.2",
- "postcss-merge-longhand": "^6.0.5",
- "postcss-merge-rules": "^6.1.1",
- "postcss-minify-font-values": "^6.1.0",
- "postcss-minify-gradients": "^6.0.3",
- "postcss-minify-params": "^6.1.0",
- "postcss-minify-selectors": "^6.0.4",
- "postcss-normalize-charset": "^6.0.2",
- "postcss-normalize-display-values": "^6.0.2",
- "postcss-normalize-positions": "^6.0.2",
- "postcss-normalize-repeat-style": "^6.0.2",
- "postcss-normalize-string": "^6.0.2",
- "postcss-normalize-timing-functions": "^6.0.2",
- "postcss-normalize-unicode": "^6.1.0",
- "postcss-normalize-url": "^6.0.2",
- "postcss-normalize-whitespace": "^6.0.2",
- "postcss-ordered-values": "^6.0.2",
- "postcss-reduce-initial": "^6.1.0",
- "postcss-reduce-transforms": "^6.0.2",
- "postcss-svgo": "^6.0.3",
- "postcss-unique-selectors": "^6.0.4"
+ "cssnano-utils": "^5.0.1",
+ "postcss-calc": "^10.1.1",
+ "postcss-colormin": "^7.0.4",
+ "postcss-convert-values": "^7.0.6",
+ "postcss-discard-comments": "^7.0.4",
+ "postcss-discard-duplicates": "^7.0.2",
+ "postcss-discard-empty": "^7.0.1",
+ "postcss-discard-overridden": "^7.0.1",
+ "postcss-merge-longhand": "^7.0.5",
+ "postcss-merge-rules": "^7.0.6",
+ "postcss-minify-font-values": "^7.0.1",
+ "postcss-minify-gradients": "^7.0.1",
+ "postcss-minify-params": "^7.0.4",
+ "postcss-minify-selectors": "^7.0.5",
+ "postcss-normalize-charset": "^7.0.1",
+ "postcss-normalize-display-values": "^7.0.1",
+ "postcss-normalize-positions": "^7.0.1",
+ "postcss-normalize-repeat-style": "^7.0.1",
+ "postcss-normalize-string": "^7.0.1",
+ "postcss-normalize-timing-functions": "^7.0.1",
+ "postcss-normalize-unicode": "^7.0.4",
+ "postcss-normalize-url": "^7.0.1",
+ "postcss-normalize-whitespace": "^7.0.1",
+ "postcss-ordered-values": "^7.0.2",
+ "postcss-reduce-initial": "^7.0.4",
+ "postcss-reduce-transforms": "^7.0.1",
+ "postcss-svgo": "^7.1.0",
+ "postcss-unique-selectors": "^7.0.4"
}
},
"cssnano-utils": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz",
- "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz",
+ "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==",
"dev": true,
"requires": {}
},
@@ -2835,6 +3289,13 @@
}
}
},
+ "detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "optional": true
+ },
"dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -2862,9 +3323,9 @@
}
},
"domutils": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
- "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"dev": true,
"requires": {
"dom-serializer": "^2.0.0",
@@ -2873,15 +3334,15 @@
}
},
"electron-to-chromium": {
- "version": "1.4.747",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz",
- "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==",
+ "version": "1.5.183",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.183.tgz",
+ "integrity": "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==",
"dev": true
},
"emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
+ "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true
},
"entities": {
@@ -2891,9 +3352,9 @@
"dev": true
},
"escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true
},
"fill-range": {
@@ -2901,6 +3362,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
+ "optional": true,
"requires": {
"to-regex-range": "^5.0.1"
}
@@ -2916,65 +3378,42 @@
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true
},
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
- },
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
- "glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "requires": {
- "is-glob": "^4.0.1"
- }
+ "get-east-asian-width": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
+ "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
+ "dev": true
},
"iframe-resizer": {
- "version": "4.3.11",
- "resolved": "https://registry.npmjs.org/iframe-resizer/-/iframe-resizer-4.3.11.tgz",
- "integrity": "sha512-5QtnsmfH11GDsuC7Gxd/eNzojudX3346Gb0E+Ku8ln8AtfSq+cWCZtnhCrthrtE7f1CI2/kwHkZ9G4sFYzHP7A=="
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/iframe-resizer/-/iframe-resizer-4.4.2.tgz",
+ "integrity": "sha512-2SupFCq9V9osWac4q+PodF0E9QdWY5A9VdCpKrrE7HlDrcIsaTp7D6k14mkGXWoWMS9jCavYusik25wTc0YB2Q=="
},
"immutable": {
- "version": "4.3.8",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz",
- "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==",
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
+ "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"dev": true
},
- "is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "requires": {
- "binary-extensions": "^2.0.0"
- }
- },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
+ "optional": true,
"requires": {
"is-extglob": "^2.1.1"
}
@@ -2983,7 +3422,8 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"jquery": {
"version": "3.7.1",
@@ -2991,17 +3431,17 @@
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"jquery-ui-dist": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.13.2.tgz",
- "integrity": "sha512-oVDRd1NLtTbBwpRKAYdIRgpWVDzeBhfy7Gu0RmY6JEaZtmBq6kDn1pm5SgDiAotrnDS+RoTRXO6xvcNTxA9tOA==",
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/jquery-ui-dist/-/jquery-ui-dist-1.13.3.tgz",
+ "integrity": "sha512-qeTR3SOSQ0jgxaNXSFU6+JtxdzNUSJKgp8LCzVrVKntM25+2YBJW1Ea8B2AwjmmSHfPLy2dSlZxJQN06OfVFhg==",
"requires": {
"jquery": ">=1.8.0 <4.0.0"
}
},
"lilconfig": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
- "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true
},
"lodash.memoize": {
@@ -3017,21 +3457,35 @@
"dev": true
},
"luxon": {
- "version": "3.4.4",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
- "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA=="
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz",
+ "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg=="
},
"mathjax": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-3.2.2.tgz",
- "integrity": "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw=="
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-4.1.1.tgz",
+ "integrity": "sha512-NyvA8c39LUUM/m+oCg7sfA13hmw7yGkre5kiRWN9qzChCyhce39lecnbjgMA/oEUgq9Vyetk6u78apwcIXpW/A==",
+ "requires": {
+ "@mathjax/mathjax-newcm-font": "^4.1.1"
+ }
},
"mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
"dev": true
},
+ "micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ }
+ },
"minisearch": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.2.tgz",
@@ -3043,16 +3497,17 @@
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true
},
- "node-releases": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
- "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
- "dev": true
+ "node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "optional": true
},
- "normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true
},
"normalize-range": {
@@ -3080,12 +3535,13 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"postcss": {
- "version": "8.5.10",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
- "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "version": "8.5.12",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
+ "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
"dev": true,
"requires": {
"nanoid": "^3.3.11",
@@ -3094,240 +3550,243 @@
}
},
"postcss-calc": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz",
- "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==",
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz",
+ "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==",
"dev": true,
"requires": {
- "postcss-selector-parser": "^6.0.11",
+ "postcss-selector-parser": "^7.0.0",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-colormin": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz",
- "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz",
+ "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"caniuse-api": "^3.0.0",
"colord": "^2.9.3",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-convert-values": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz",
- "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz",
+ "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-discard-comments": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz",
- "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz",
+ "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==",
"dev": true,
- "requires": {}
+ "requires": {
+ "postcss-selector-parser": "^7.1.0"
+ }
},
"postcss-discard-duplicates": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz",
- "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz",
+ "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==",
"dev": true,
"requires": {}
},
"postcss-discard-empty": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz",
- "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz",
+ "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==",
"dev": true,
"requires": {}
},
"postcss-discard-overridden": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz",
- "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz",
+ "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==",
"dev": true,
"requires": {}
},
"postcss-merge-longhand": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz",
- "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz",
+ "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0",
- "stylehacks": "^6.1.1"
+ "stylehacks": "^7.0.5"
}
},
"postcss-merge-rules": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz",
- "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz",
+ "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"caniuse-api": "^3.0.0",
- "cssnano-utils": "^4.0.2",
- "postcss-selector-parser": "^6.0.16"
+ "cssnano-utils": "^5.0.1",
+ "postcss-selector-parser": "^7.1.0"
}
},
"postcss-minify-font-values": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz",
- "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz",
+ "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-minify-gradients": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz",
- "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz",
+ "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==",
"dev": true,
"requires": {
"colord": "^2.9.3",
- "cssnano-utils": "^4.0.2",
+ "cssnano-utils": "^5.0.1",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-minify-params": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz",
- "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz",
+ "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
- "cssnano-utils": "^4.0.2",
+ "browserslist": "^4.25.1",
+ "cssnano-utils": "^5.0.1",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-minify-selectors": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz",
- "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==",
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz",
+ "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==",
"dev": true,
"requires": {
- "postcss-selector-parser": "^6.0.16"
+ "cssesc": "^3.0.0",
+ "postcss-selector-parser": "^7.1.0"
}
},
"postcss-normalize-charset": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz",
- "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz",
+ "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==",
"dev": true,
"requires": {}
},
"postcss-normalize-display-values": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz",
- "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz",
+ "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-positions": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz",
- "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz",
+ "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-repeat-style": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz",
- "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz",
+ "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-string": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz",
- "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz",
+ "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-timing-functions": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz",
- "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz",
+ "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-unicode": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz",
- "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz",
+ "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-url": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz",
- "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz",
+ "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-normalize-whitespace": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz",
- "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz",
+ "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-ordered-values": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz",
- "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz",
+ "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==",
"dev": true,
"requires": {
- "cssnano-utils": "^4.0.2",
+ "cssnano-utils": "^5.0.1",
"postcss-value-parser": "^4.2.0"
}
},
"postcss-reduce-initial": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz",
- "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz",
+ "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
+ "browserslist": "^4.25.1",
"caniuse-api": "^3.0.0"
}
},
"postcss-reduce-transforms": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz",
- "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz",
+ "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0"
}
},
"postcss-selector-parser": {
- "version": "6.0.16",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
- "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
+ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
@@ -3335,22 +3794,22 @@
}
},
"postcss-svgo": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz",
- "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz",
+ "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.2.0",
- "svgo": "^3.2.0"
+ "svgo": "^4.0.0"
}
},
"postcss-unique-selectors": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz",
- "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz",
+ "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==",
"dev": true,
"requires": {
- "postcss-selector-parser": "^6.0.16"
+ "postcss-selector-parser": "^7.1.0"
}
},
"postcss-value-parser": {
@@ -3360,30 +3819,21 @@
"dev": true
},
"prettier": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
- "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true
},
"readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "requires": {
- "picomatch": "^2.2.1"
- }
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true
},
"rtlcss": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz",
- "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==",
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz",
+ "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==",
"dev": true,
"requires": {
"escalade": "^3.1.1",
@@ -3393,13 +3843,14 @@
}
},
"sass": {
- "version": "1.75.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
- "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
+ "version": "1.90.0",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
+ "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
"requires": {
- "chokidar": ">=3.0.0 <4.0.0",
- "immutable": "^4.0.0",
+ "@parcel/watcher": "^2.4.1",
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
@@ -3415,9 +3866,9 @@
"integrity": "sha512-JKmT4my3Hm1e18OvG4Q6RcFhN4WRqqpTMkHrvZ7fup/dp6aTIWGVCHdRYtASkp/FCzDlJh6iCLQ/VcwwNpAMoQ=="
},
"sortablejs": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz",
- "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA=="
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
+ "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A=="
},
"source-map": {
"version": "0.6.1",
@@ -3442,23 +3893,23 @@
}
},
"string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
}
},
"strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"requires": {
- "ansi-regex": "^5.0.1"
+ "ansi-regex": "^6.0.1"
}
},
"strip-json-comments": {
@@ -3468,43 +3919,43 @@
"dev": true
},
"style-mod": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
- "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
+ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
},
"stylehacks": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz",
- "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz",
+ "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==",
"dev": true,
"requires": {
- "browserslist": "^4.23.0",
- "postcss-selector-parser": "^6.0.16"
+ "browserslist": "^4.25.1",
+ "postcss-selector-parser": "^7.1.0"
}
},
"svgo": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz",
- "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz",
+ "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==",
"dev": true,
"requires": {
- "commander": "^7.2.0",
+ "commander": "^11.1.0",
"css-select": "^5.1.0",
- "css-tree": "^2.3.1",
+ "css-tree": "^3.0.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
- "picocolors": "^1.0.0",
+ "picocolors": "^1.1.1",
"sax": "^1.5.0"
}
},
"terser": {
- "version": "5.30.4",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz",
- "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==",
+ "version": "5.43.1",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
+ "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
+ "acorn": "^8.14.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
@@ -3528,18 +3979,19 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
+ "optional": true,
"requires": {
"is-number": "^7.0.0"
}
},
"update-browserslist-db": {
- "version": "1.0.13",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
- "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dev": true,
"requires": {
- "escalade": "^3.1.1",
- "picocolors": "^1.0.0"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
}
},
"util-deprecate": {
@@ -3554,14 +4006,14 @@
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
},
"wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
+ "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
"requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
}
},
"y18n": {
@@ -3571,24 +4023,23 @@
"dev": true
},
"yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "version": "18.0.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
+ "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
"dev": true,
"requires": {
- "cliui": "^8.0.1",
+ "cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
+ "string-width": "^7.2.0",
"y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
+ "yargs-parser": "^22.0.0"
}
},
"yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"dev": true
}
}
diff --git a/htdocs/package.json b/htdocs/package.json
index 6bfce3e11a..99621ec7f9 100644
--- a/htdocs/package.json
+++ b/htdocs/package.json
@@ -13,29 +13,29 @@
"url": "https://github.com/openwebwork/webwork2"
},
"dependencies": {
- "@fortawesome/fontawesome-free": "^6.5.2",
- "@openwebwork/pg-codemirror-editor": "^0.0.5",
- "bootstrap": "~5.3.3",
+ "@fortawesome/fontawesome-free": "^7.0.0",
+ "@openwebwork/pg-codemirror-editor": "^0.0.10",
+ "bootstrap": "~5.3.7",
"flatpickr": "^4.6.13",
- "iframe-resizer": "^4.3.11",
+ "iframe-resizer": "^4.4.2",
"jquery": "^3.7.1",
- "jquery-ui-dist": "^1.13.2",
- "luxon": "^3.4.4",
- "mathjax": "^3.2.2",
+ "jquery-ui-dist": "^1.13.3",
+ "luxon": "^3.7.1",
+ "mathjax": "^4.1.1",
"minisearch": "^7.1.2",
"shortcut-buttons-flatpickr": "^0.4.0",
- "sortablejs": "^1.15.2"
+ "sortablejs": "^1.15.6"
},
"devDependencies": {
- "autoprefixer": "^10.4.19",
- "chokidar": "^3.6.0",
- "cssnano": "^6.1.2",
- "postcss": "^8.5.10",
- "prettier": "^3.2.5",
- "rtlcss": "^4.1.1",
- "sass": "^1.75.0",
- "terser": "^5.30.4",
- "yargs": "^17.7.2"
+ "autoprefixer": "^10.4.21",
+ "chokidar": "^4.0.3",
+ "cssnano": "^7.1.0",
+ "postcss": "^8.5.12",
+ "prettier": "^3.6.2",
+ "rtlcss": "^4.3.0",
+ "sass": "^1.90.0",
+ "terser": "^5.43.1",
+ "yargs": "^18.0.0"
},
"browserslist": [
"last 10 Chrome versions",
diff --git a/htdocs/show-source.cgi b/htdocs/show-source.cgi
index 8cf298e894..af3181a2ad 100755
--- a/htdocs/show-source.cgi
+++ b/htdocs/show-source.cgi
@@ -14,10 +14,12 @@ my $filedir = $file =~ s!/[^/]+$!!r;
$file =~ s!.*/!!;
my @PGdirs = (
- "../templates$filedir", '../templates/macros', "$root/pg/macros", "$root/pg/macros/answers",
- "$root/pg/macros/capa", "$root/pg/macros/contexts", "$root/pg/macros/core", "$root/pg/macros/deprecated",
- "$root/pg/macros/graph", "$root/pg/macros/math", "$root/pg/macros/misc", "$root/pg/macros/parsers",
- "$root/pg/macros/ui",
+ "../templates$filedir", '../templates/macros',
+ "$root/pg/macros", "$root/pg/macros/answers",
+ "$root/pg/macros/contexts", "$root/pg/macros/core",
+ "$root/pg/macros/deprecated", "$root/pg/macros/graph",
+ "$root/pg/macros/math", "$root/pg/macros/misc",
+ "$root/pg/macros/parsers", "$root/pg/macros/ui",
);
for my $dir (@PGdirs) { ShowSource("$dir/$file") if (-e "$dir/$file") }
diff --git a/htdocs/themes/README.md b/htdocs/themes/README.md
new file mode 100644
index 0000000000..048ca09644
--- /dev/null
+++ b/htdocs/themes/README.md
@@ -0,0 +1,78 @@
+# Theming WeBWorK 2
+
+This folder contains the themes for webwork2. If you would like to create a custom theme, then copy one of the existing
+theme directories, and modify it as desired. It is recommended that you use either the `math4-green`, `math4-red`, or
+`math4-yellow` theme as the basis for a new custom theme, but for more advanced theming you can copy the `math4`
+directory itself. Generally only the `_theme-colors.scss` and `_theme-overrides.scss` files need to be modified. The
+important things to change are the colors in the `_theme-colors.scss` file. Overrides for special cases can be done in
+the `_theme-overrides.scss` file. The `math4-yellow` theme uses a light primary color, and so it is a good example to
+follow if you also need a light primary color. It also shows how the `_theme-overrides.scss` file can be used to handle
+certain special cases.
+
+The `math4-overrides.css` and `math4-overrides.js` files can also be created in the theme directory and customizations
+can also be added to those files. However, usage of these files is deprecated and support for them will eventually be
+dropped. There are `.dist` files in the `math4` directory you can copy, but the `.dist` files do not have anything of
+value in them.
+
+Note that any changes made to the files in the `math4`, `math4-green`, `math4-red`, and `math4-yellow` files will cause
+problems when you upgrade webwork2, (other than copies of the `math4-overrides.css.dist` and `math4-overrides.css.js`
+files).
+
+To make the custom theme available for webwork2 to use, run `npm ci` from the `htdocs` directory. Then either set the
+theme as the `$defaultTheme` in `conf/localOverrides.conf` or choose the theme in the `Course Configuration` of a course
+to use it. Note that for any changes in the theme files to take effect, you must run `npm ci` again. You can also
+execute `./generate-assets.js` from the `htdocs` directory to update the theme (this is actually part of what `npm ci`
+does). See more details on theme creation on [the WeBWork Wiki](https://wiki.openwebwork.org/wiki/Customizing_WeBWorK).
+
+The theming system uses Sass which is an extension of CSS, and is compiled into CSS (by the `generate-assets.js`
+script). Sass variables can be set in the `_theme-colors.scss` file that control many display aspects of the user
+interface. Bootstrap has many Sass variables that can be customized. In addition there are CSS variables that can be
+set. Many of these are set initially from the Sass variables, but they can also be changed in the
+`_theme-overrides.scss` file. See the [Bootstrap documentation](https://getbootstrap.com/docs/5.3) for available Sass
+and CSS variables. There are also Sass and CSS variables specifically for webwork2 that can be used. These are
+documented below. In addition Bootstrap functions can be used to manipulate colors in the `_theme-colors.scss` file.
+See the [Boottrap Sass function documentation](https://getbootstrap.com/docs/5.3/customize/sass/#functions).
+
+## WeBWorK 2 Sass Variables
+
+These must be set in the `_theme-colors.scss` file.
+
+- `$ww-logo-background-color`: WeBWorK logo background color in the banner.
+- `$ww-achievement-level-color`: Color of the level progress bar on the achievements page.
+
+## WeBWorK 2 CSS Variables
+
+All of these are set to a default value in the `bootstrap.scss` file, but can be overridden in the
+`_theme-overrides.scss` file. Note that values can even be set for a specific CSS selector to only apply to the elements
+that match the selector and descendants of those elements.
+
+- `--ww-primary-foreground-color`: The color of text that is displayed before a primary colored background in the page
+ header, site navigation menu, and the course list on the webwork2 home page. This defaults to the result of
+ `#{color-contrast($primary)}` and rarely needs to be changed.
+- `--ww-layout-divider-color`: This is the color of the border that separates the page header, site navigation menu, and
+ main content area. It is also used for the color of the border separates the primary part of the site navigation menu
+ from page specific sub menus (such as the list of problems when viewing a problem in a homework set). This defaults to
+ `#aaa` in light mode and `#666` in dark mode.
+- `--ww-layout-border-color`: This is the border color for other regions such as the breadcrumb navigation at the top of
+ every page and the info box shown (the course information or set header box). This defaults to `#e6e6e6` in light mode
+ and `#495057` in dark mode.
+- `--ww-toggle-sidebar-icon-color-rgb`: The color of the site navigation menu toggle button in RGB color components.
+ This defaults to `255, 255, 255`.
+- `--ww-toggle-sidebar-icon-hover-color`: The color of the site navigation menu toggle button when it is hovered over
+ with the mouse cursor or has keyboard focus. This defaults to `#fff`.
+- `--ww-site-nav-link-active-background-color`: The background color of the links in the site navigation menu when they
+ have keyboard focus. This defaults to `#{$primary}`.
+- `--ww-site-nav-link-hover-background-color`: The background color of the links in the site navigation menu when the
+ mouse cursor hovers over them. This defaults to `#e1e1e1` in light mode and `#{shade-color($primary, 40%)}` in dark
+ mode.
+- `--ww-course-config-tab-link-focus-outline-color`: The outline color of the tab selection buttons on the course
+ configuration page when they have keyboard focus. This defaults to `#{rgba($primary, $focus-ring-opacity)}` in light
+ mode and `#{rgba(color-contrast($body-bg-dark), $focus-ring-opacity)}` in dark mode.
+- `--ww-logo-background-color`: The background color for the top left region of the page header that contains the
+ WeBWorK logo. This is set to the value of the `#{$ww-logo-background-color}` Sass variable, and there is no need to
+ ever modify this. Just set the Sass variable directly to what this should be. This is really only needed to get the
+ theme color to the other CSS files used by webwork2.
+- `--ww-achievement-level-color`: The color of the level progress bar on the achievements page. This defaults to the
+ value of the `#{$ww-achievement-level-color}` Sass variable, and there is no need to ever modify this. Just set the
+ Sass variable directly to what this should be. This is really only needed to get the theme color to the other CSS
+ files used by webwork2.
diff --git a/htdocs/themes/math4-green/README b/htdocs/themes/math4-green/README
deleted file mode 100644
index e607958755..0000000000
--- a/htdocs/themes/math4-green/README
+++ /dev/null
@@ -1,8 +0,0 @@
-This is an "alternative" colorization to math4. If you want to provide
-multiple themes to your users you should follow this as an example.
-
-Everything except for the math4-overrides.js, math4-overrides.css,
-_theme-colors.scss, and _theme-overrides.scss files should be links pointing
-back to the corresponding files in math4. This will make it so that all your
-themes will automatically get updates. All of your changes should be in the
-listed override files.
diff --git a/htdocs/themes/math4-green/_theme-colors.scss b/htdocs/themes/math4-green/_theme-colors.scss
index 89fa61322f..4a0d5d4e6e 100644
--- a/htdocs/themes/math4-green/_theme-colors.scss
+++ b/htdocs/themes/math4-green/_theme-colors.scss
@@ -7,7 +7,6 @@ $info: #618265;
// Link colors
$link-color: #283f2b;
-$link-hover-color: #618265;
// Webwork logo background color in the banner
$ww-logo-background-color: darken($info, 8%);
diff --git a/htdocs/themes/math4-red/README b/htdocs/themes/math4-red/README
deleted file mode 100644
index e607958755..0000000000
--- a/htdocs/themes/math4-red/README
+++ /dev/null
@@ -1,8 +0,0 @@
-This is an "alternative" colorization to math4. If you want to provide
-multiple themes to your users you should follow this as an example.
-
-Everything except for the math4-overrides.js, math4-overrides.css,
-_theme-colors.scss, and _theme-overrides.scss files should be links pointing
-back to the corresponding files in math4. This will make it so that all your
-themes will automatically get updates. All of your changes should be in the
-listed override files.
diff --git a/htdocs/themes/math4-red/_theme-colors.scss b/htdocs/themes/math4-red/_theme-colors.scss
index b34e6f2682..26de64dda7 100644
--- a/htdocs/themes/math4-red/_theme-colors.scss
+++ b/htdocs/themes/math4-red/_theme-colors.scss
@@ -8,6 +8,7 @@ $info: #c30;
// Link colors
$link-color: $primary;
$link-hover-color: #c00;
+$link-color-dark: tint-color($primary, 50%);
// Webwork logo background color in the banner
$ww-logo-background-color: darken($info, 8%);
diff --git a/htdocs/themes/math4-red/_theme-overrides.scss b/htdocs/themes/math4-red/_theme-overrides.scss
index 20b5856367..e69de29bb2 100644
--- a/htdocs/themes/math4-red/_theme-overrides.scss
+++ b/htdocs/themes/math4-red/_theme-overrides.scss
@@ -1,3 +0,0 @@
-a:not(.btn):focus {
- outline-color: #{lighten($link-hover-color, 26%)};
-}
diff --git a/htdocs/themes/math4-yellow/README b/htdocs/themes/math4-yellow/README
deleted file mode 100644
index e607958755..0000000000
--- a/htdocs/themes/math4-yellow/README
+++ /dev/null
@@ -1,8 +0,0 @@
-This is an "alternative" colorization to math4. If you want to provide
-multiple themes to your users you should follow this as an example.
-
-Everything except for the math4-overrides.js, math4-overrides.css,
-_theme-colors.scss, and _theme-overrides.scss files should be links pointing
-back to the corresponding files in math4. This will make it so that all your
-themes will automatically get updates. All of your changes should be in the
-listed override files.
diff --git a/htdocs/themes/math4-yellow/_theme-colors.scss b/htdocs/themes/math4-yellow/_theme-colors.scss
index cbc8f078bb..527681abff 100644
--- a/htdocs/themes/math4-yellow/_theme-colors.scss
+++ b/htdocs/themes/math4-yellow/_theme-colors.scss
@@ -5,13 +5,15 @@
$primary: #ffc700;
$info: black;
+$primary-bg-subtle-dark: shade-color($primary, 90%);
+$info-text-emphasis-dark: tint-color($info, 50%);
+
// Override the default white for the foreground color of active components.
// White has poor color contrast with the yellow primary color.
$component-active-color: black;
// Link colors
-$link-color: darken(#bf5454, 30%);
-$link-hover-color: lighten($link-color, 30%);
+$link-color: shade-color($primary, 60%);
// Webwork logo background color in the banner
$ww-logo-background-color: $info;
@@ -19,9 +21,6 @@ $ww-logo-background-color: $info;
// Achievment level bar
$ww-achievement-level-color: darken($primary, 15%);
-// Make accordion buttons darker.
-$accordion-button-active-color: shade-color($primary, 50%);
-
// Make the navbar colors dark.
$navbar-dark-color: rgba(#000, 0.55);
$navbar-dark-hover-color: rgba(#000, 0.75);
diff --git a/htdocs/themes/math4-yellow/_theme-overrides.scss b/htdocs/themes/math4-yellow/_theme-overrides.scss
index 0cf795757f..1046436e60 100644
--- a/htdocs/themes/math4-yellow/_theme-overrides.scss
+++ b/htdocs/themes/math4-yellow/_theme-overrides.scss
@@ -38,10 +38,30 @@
color: $link-color !important;
}
-a:not(.btn):focus {
- outline-color: #{darken($link-hover-color, 1%)};
-}
-
:root {
--ww-site-nav-link-active-background-color: #{$primary};
+ --ww-color-chooser-hover-color: #555;
+ --ww-color-chooser-focus-outline-color-rgb: 0, 0, 0;
+ --ww-course-config-tab-link-focus-outline-color: #{rgba(shade-color($primary, 40%), $focus-ring-opacity)};
+}
+
+@include color-mode(dark) {
+ --ww-site-nav-link-hover-background-color: #{shade-color($primary, 60%)};
+ --ww-course-config-tab-link-focus-outline-color: #{rgba(color-contrast($body-bg-dark), $focus-ring-opacity)};
+
+ .btn-outline-primary {
+ --#{$prefix}btn-color: #{tint_color($primary, 40%)};
+ }
+}
+
+.masthead {
+ .institution-logo {
+ a:not(.btn):focus {
+ outline-color: #{shade-color($link-hover-color-dark, 70%)};
+ }
+ }
+
+ .login-status .btn.btn-light {
+ --bs-btn-focus-shadow-rgb: 100, 100, 100;
+ }
}
diff --git a/htdocs/themes/math4/README b/htdocs/themes/math4/README
deleted file mode 100644
index 0b92a6b6d6..0000000000
--- a/htdocs/themes/math4/README
+++ /dev/null
@@ -1,18 +0,0 @@
-This folder contains the files necessary for the math4 theme. These files are
-tracked by git and any changes made to them will be overwritten when you
-upgrade. The two exceptions are math4-overrides.css and math4-overrides.js.
-
-These files do not need to be present, but if they are they will be included in
-system.conf and can be used for general overrides. They can created by copying
-math4-overrides.css.dist and math4-overrides.js.dist. This is similar to how
-localOverrides.conf interacts with defaults.conf and localOverrides.conf.dist.
-In particular if you upgrade your server math4-overrides.js and
-math4-overrides.css will not change, but their .dist versions and the other
-math4 theme files may change. This might cause problems until you merge the
-changes.
-
-If you want to customize math4 you should only change math4-overrides.css and
-math4-overrides.js. Note: Because you can include arbitrary JavaScript in
-math4-overrides.js you can actually change pretty much anything, including
-adding new html or changing existing html.
-
diff --git a/htdocs/themes/math4/_theme-colors.scss b/htdocs/themes/math4/_theme-colors.scss
index bd57046fb7..b40d760909 100644
--- a/htdocs/themes/math4/_theme-colors.scss
+++ b/htdocs/themes/math4/_theme-colors.scss
@@ -7,7 +7,7 @@ $info: #1a67ea;
// Link colors
$link-color: $primary;
-$link-hover-color: $info;
+$link-color-dark: tint-color($primary, 50%);
// Webwork logo background color in the banner
$ww-logo-background-color: darken($info, 14%);
diff --git a/htdocs/themes/math4/bootstrap.scss b/htdocs/themes/math4/bootstrap.scss
index 72cbc9cbd0..3114fe8445 100644
--- a/htdocs/themes/math4/bootstrap.scss
+++ b/htdocs/themes/math4/bootstrap.scss
@@ -17,10 +17,6 @@ $headings-font-weight: 600;
$link-decoration: none;
$link-hover-decoration: underline;
-// Make breadcrumb dividers and active items a bit darker.
-$breadcrumb-divider-color: #495057;
-$breadcrumb-active-color: #495057;
-
@import './theme-colors';
// Include the remainder of bootstrap's scss configuration
@@ -75,14 +71,55 @@ $breadcrumb-active-color: #495057;
--ww-primary-foreground-color: #{color-contrast($primary)};
--ww-achievement-level-color: #{$ww-achievement-level-color};
--ww-site-nav-link-active-background-color: #{$primary};
+ --ww-site-nav-link-hover-background-color: #e1e1e1;
+ --ww-layout-divider-color: #aaa;
+ --ww-layout-border-color: #e6e6e6;
+ --ww-course-config-tab-link-focus-outline-color: #{rgba($primary, $focus-ring-opacity)};
}
// Overrides
+
a:not(.btn):focus {
- color: $link-hover-color;
outline-style: solid;
- outline-color: #{lighten($link-hover-color, 8%)};
outline-width: 1px;
+ outline-color: #{$link_hover_color};
+ box-shadow: none;
+}
+
+@include color-mode(dark) {
+ --ww-site-nav-link-hover-background-color: #{shade-color($primary, 40%)};
+ --ww-layout-divider-color: #666;
+ --ww-layout-border-color: #495057;
+ --ww-course-config-tab-link-focus-outline-color: #{rgba(color-contrast($body-bg-dark), $focus-ring-opacity)};
+
+ .bg-light {
+ color: var(--bs-body-color) !important;
+ background-color: var(--bs-primary-bg-subtle) !important;
+ }
+
+ .btn-outline-primary {
+ --#{$prefix}btn-color: #{tint_color($primary, 60%)};
+ --#{$prefix}btn-border-color: #{tint_color($primary, 20%)};
+ }
+
+ .text-danger {
+ color: var(--bs-danger-text-emphasis) !important;
+ }
+
+ .text-success {
+ color: var(--bs-success-text-emphasis) !important;
+ }
+
+ a:not(.btn):focus {
+ outline-color: #{$link_hover_color-dark};
+ }
+}
+
+.masthead {
+ a:not(.btn):focus {
+ outline-color: #{$link_hover_color_dark};
+ outline-width: 2px;
+ }
}
@import 'theme-overrides';
diff --git a/lib/FormatRenderedProblem.pm b/lib/FormatRenderedProblem.pm
index 1c7f0194f3..029de024af 100644
--- a/lib/FormatRenderedProblem.pm
+++ b/lib/FormatRenderedProblem.pm
@@ -63,7 +63,7 @@ sub formatRenderedProblem {
# Add CSS files requested by problems via ADD_CSS_FILE() in the PG file
# or via a setting of $ce->{pg}{specialPGEnvironmentVars}{extra_css_files}
- # which can be set in course.conf (the value should be an anonomous array).
+ # which can be set in course.conf (the value should be an anonymous array).
my @cssFiles;
if (ref($ce->{pg}{specialPGEnvironmentVars}{extra_css_files}) eq 'ARRAY') {
push(@cssFiles, { file => $_, external => 0 }) for @{ $ce->{pg}{specialPGEnvironmentVars}{extra_css_files} };
@@ -90,12 +90,12 @@ sub formatRenderedProblem {
[ 'node_modules/jquery/dist/jquery.min.js', 0, {} ],
[ 'node_modules/jquery-ui-dist/jquery-ui.min.js', 0, {} ],
[ 'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js', 0, {} ],
- [ 'js/MathJaxConfig/mathjax-config.js', 0, { defer => undef } ],
- [ 'node_modules/mathjax/es5/tex-svg.js', 0, { defer => undef, id => 'MathJax-script' } ],
- [ 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', 0, { defer => undef } ],
- [ 'js/Problem/problem.js', 0, { defer => undef } ],
- [ 'js/System/system.js', 0, { defer => undef } ],
- [ 'math4-overrides.js', 1, { defer => undef } ]
+ [ 'js/MathJaxConfig/mathjax-config.js', 0, { defer => undef } ],
+ [ 'node_modules/mathjax/tex-svg.js', 0, { defer => undef } ],
+ [ 'node_modules/bootstrap/dist/js/bootstrap.bundle.min.js', 0, { defer => undef } ],
+ [ 'js/Problem/problem.js', 0, { defer => undef } ],
+ [ 'js/System/system.js', 0, { defer => undef } ],
+ [ 'math4-overrides.js', 1, { defer => undef } ]
);
# Get the requested format.
@@ -196,11 +196,12 @@ sub formatRenderedProblem {
$output->{input} = $ws->{input};
# The following could be constructed from the above, but this is a convenience
- $output->{resultSummary} = $resultSummary->to_string if $resultSummary;
- $output->{lang} = $PROBLEM_LANG_AND_DIR{lang};
- $output->{dir} = $PROBLEM_LANG_AND_DIR{dir};
- $output->{extra_css_files} = \@extra_css_files;
- $output->{extra_js_files} = \@extra_js_files;
+ $output->{resultSummary} = $resultSummary->to_string if $resultSummary;
+ $output->{lang} = $PROBLEM_LANG_AND_DIR{lang};
+ $output->{dir} = $PROBLEM_LANG_AND_DIR{dir};
+ $output->{extra_css_files} = \@extra_css_files;
+ $output->{extra_js_files} = \@extra_js_files;
+ $output->{webwork_js_config} = $ws->c->webwork_js_config($ws->{inputs_ref}{showMathJaxErrors} // 0);
# Include third party css and javascript files. Only jquery, jquery-ui, mathjax, and bootstrap are needed for
# PG. See the comments before the subroutine definitions for load_css and load_js in pg/macros/PG.pl.
@@ -267,6 +268,7 @@ sub formatRenderedProblem {
showCorrectAnswersOnlyButton => $ws->{inputs_ref}{showCorrectAnswersOnlyButton} // 0,
showFooter => $ws->{inputs_ref}{showFooter} // '',
problem_data => encode_json($rh_result->{PERSISTENCE_HASH}),
+ showMathJaxErrors => $ws->{inputs_ref}{showMathJaxErrors} // 0,
pretty_print => \&pretty_print
);
@@ -368,7 +370,7 @@ EOS
$LTIGradeMessage = $ws->c->tag('p', "Unable to update LMS grade. Error: $message")->to_string;
push(@{ $rh_result->{debug_messages} }, xml_escape($response->content));
} else {
- $LTIGradeMessage = $ws->c->tag('p', 'Grade sucessfully saved.')->to_string;
+ $LTIGradeMessage = $ws->c->tag('p', 'Grade successfully saved.')->to_string;
}
} else {
$LTIGradeMessage = $ws->c->tag('p', 'Unable to update LMS grade. Error: ' . $response->message)->to_string;
diff --git a/lib/HardcopyRenderedProblem.pm b/lib/HardcopyRenderedProblem.pm
index 8786cd033d..4f1fcb654b 100644
--- a/lib/HardcopyRenderedProblem.pm
+++ b/lib/HardcopyRenderedProblem.pm
@@ -2,7 +2,7 @@
=head1 NAME
HardcopyRenderedProblem.pm -- Generate a pdf file or zip file containing a tex
-file and the neccessary files to generate the pdf file from the result of the
+file and the necessary files to generate the pdf file from the result of the
renderProblem method.
=cut
@@ -120,7 +120,7 @@ sub generate_hardcopy_tex {
if $@;
}
my $pgAssetsTex_dir = path($ce->{pg}{directories}{assetsTex});
- for (qw{pg.sty PGML.tex CAPA.tex}) {
+ for (qw{pg.sty PGML.tex}) {
eval { $pgAssetsTex_dir->child($_)->copy_to($working_dir) };
push(@$errors, qq{Failed to copy "$ce->{pg}{directories}{assetsTex}/$_" into directory "$working_dir": $@})
if $@;
diff --git a/lib/Mojolicious/WeBWorK.pm b/lib/Mojolicious/WeBWorK.pm
index c9315547c4..428d411144 100644
--- a/lib/Mojolicious/WeBWorK.pm
+++ b/lib/Mojolicious/WeBWorK.pm
@@ -100,13 +100,24 @@ sub startup ($app) {
);
# Add a hook to add extra headers if set in the config file.
- if (ref $config->{extra_headers} eq 'HASH') {
+ if (ref $config->{extra_headers} eq 'HASH' || ref $config->{extra_ssl_headers} eq 'HASH') {
+ my $extraHeaders = ref $config->{extra_headers} eq 'HASH' ? $config->{extra_headers} : {};
+ my $extraSSLHeaders = ref $config->{extra_ssl_headers} eq 'HASH' ? $config->{extra_ssl_headers} : {};
$app->hook(
before_dispatch => sub ($c) {
- for my $path (keys %{ $config->{extra_headers} }) {
+ for my $path (keys %$extraHeaders) {
if ($c->req->url->path =~ /^$path/) {
- for (keys %{ $config->{extra_headers}{$path} }) {
- $c->res->headers->header($_ => $config->{extra_headers}{$path}{$_});
+ for (keys %{ $extraHeaders->{$path} }) {
+ $c->res->headers->header($_ => $extraHeaders->{$path}{$_});
+ }
+ }
+ }
+ if ($c->req->is_secure) {
+ for my $path (keys %$extraSSLHeaders) {
+ if ($c->req->url->path =~ /^$path/) {
+ for (keys %{ $extraSSLHeaders->{$path} }) {
+ $c->res->headers->header($_ => $extraSSLHeaders->{$path}{$_});
+ }
}
}
}
diff --git a/lib/WeBWorK.pm b/lib/WeBWorK.pm
index a80dd817ce..85333bd79f 100644
--- a/lib/WeBWorK.pm
+++ b/lib/WeBWorK.pm
@@ -140,10 +140,11 @@ async sub dispatch ($c) {
# This route could have the courseID set, but does not need authentication.
return 1 if $c->current_route eq 'saml2_metadata';
- return (0, 'This course does not exist.')
+ return (0, "The course $routeCaptures{courseID} does not exist.")
unless (-e $ce->{courseDirs}{root}
|| -e "$ce->{webwork_courses_dir}/$ce->{admin_course_id}/archives/$routeCaptures{courseID}.tar.gz");
- return (0, 'This course has been archived and closed.') unless -e $ce->{courseDirs}{root};
+ return (0, "The course $routeCaptures{courseID} has been archived and closed.")
+ unless -e $ce->{courseDirs}{root};
my $db = WeBWorK::DB->new($ce);
debug("(here's the DB handle: $db)\n");
diff --git a/lib/WeBWorK/AchievementEvaluator.pm b/lib/WeBWorK/AchievementEvaluator.pm
index 249d34ea22..bf4d0cf9de 100644
--- a/lib/WeBWorK/AchievementEvaluator.pm
+++ b/lib/WeBWorK/AchievementEvaluator.pm
@@ -16,24 +16,24 @@ use WeBWorK::WWSafe;
our @EXPORT_OK = qw(checkForAchievements);
-sub checkForAchievements ($problem_in, $pg, $c, %options) {
+sub checkForAchievements ($problem_in, $c, %options) {
my $db = $c->db;
my $ce = $c->ce;
# Make a copy of the problem so that local modifications do not persist.
our $problem = $db->newUserProblem($problem_in);
- # Date and time for course timezone (may differ from the server timezone)
- # Saved into separate array
+ # Date and time for course timezone (may differ from the server timezone).
+ # Saved into separate array:
# https://metacpan.org/pod/DateTime
my $dtCourseTime = DateTime->from_epoch(epoch => time(), time_zone => $ce->{siteDefaults}{timezone} || 'local');
- # Set up variables and get achievements
+ # Set up variables and get achievements.
my $cheevoMessage = $c->c;
my $user_id = $problem->user_id;
my $set_id = $problem->set_id;
- # exit early if the set is to be ignored by achievements
+ # Exit early if the set is to be ignored by achievements.
foreach my $excludedSet (@{ $ce->{achievementExcludeSet} }) {
return '' if $set_id eq $excludedSet;
}
@@ -45,12 +45,12 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
my $isGatewaySet = ($set->assignment_type =~ /gateway/) ? 1 : 0;
my $isJitarSet = ($set->assignment_type eq 'jitar') ? 1 : 0;
- # If its a gateway set get the current version
+ # If its a gateway set get the current version.
if ($isGatewaySet) {
$set = $db->getSetVersion($user_id, $set_id, $options{setVersion});
}
- # If no global data then initialize
+ # If no global data then initialize.
if (not $globalUserAchievement) {
$globalUserAchievement = $db->newGlobalUserAchievement();
$globalUserAchievement->user_id($user_id);
@@ -58,7 +58,7 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
$db->addGlobalUserAchievement($globalUserAchievement);
}
- #These need to be "our" so that they can share to the safe container
+ # These need to be "our" so that they can share to the safe container.
our $counter;
our $maxCounter;
our $achievementPoints = $globalUserAchievement->achievement_points;
@@ -75,21 +75,21 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
my $compartment = WeBWorK::WWSafe->new;
- #initialize things that are ""
+ # Initialize things that are "".
if (not $achievementPoints) {
$achievementPoints = 0;
$globalUserAchievement->achievement_points(0);
}
- #Methods alowed in the safe container
+ # Methods allowed in the safe container.
$compartment->permit(qw(time localtime));
- #Thaw_Base64 globalData hash
+ # Thaw_Base64 globalData hash.
if ($globalUserAchievement->frozen_hash) {
$globalData = thaw_base64($globalUserAchievement->frozen_hash);
}
- #Generate hash of user achievements:
+ # Generate hash of user achievements:
foreach my $achievement (@achievements) {
next unless $achievement->enabled;
my $userAchievement = $db->getUserAchievement($user_id, $achievement->achievement_id);
@@ -132,10 +132,15 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
}
}
- $globalData->{completeSets}++ if ($allcorrect);
+ # Store the setID of all completed sets so they can be used in achievements and not double counted.
+ $globalData->{completedSetIds} //= {};
+ if ($allcorrect) {
+ ++$globalData->{completeSets} unless $globalData->{completedSetIds}{$set_id};
+ $globalData->{completedSetIds}{$set_id} = 1;
+ }
- # get the problem tags if its not a gatway
- # if it is a gateway get rid of $problem since it doensn't make sense
+ # Get the problem tags if its not a gateway.
+ # If it is a gateway get rid of $problem since it doesn't make sense.
if ($isGatewaySet) {
$problem = undef;
} else {
@@ -143,8 +148,8 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
$tags = WeBWorK::Utils::Tags->new($templateDir . '/' . $problem->source_file());
}
- #These variables are shared with the safe compartment. The achievement evaulators
- # have access too
+ # These variables are shared with the safe compartment.
+ # The achievement evaluators have access too:
# $problem - the problem data;
# @setProblems - the problem data for everything from this set;
# $localData - the hash that is used only for this achievement
@@ -161,7 +166,7 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
$compartment->share(qw( $problem @setProblems $localData $maxCounter $userAchievements
$globalData $counter $nextLevelPoints $set $achievementPoints $tags @courseDateTime));
- #load any preamble code
+ # Load any preamble code.
my $preamble = '';
my $source;
if (-e "$ce->{courseDirs}{achievements}/$ce->{achievementPreambleFile}") {
@@ -170,27 +175,27 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
$preamble = <$PREAMB>;
close($PREAMB);
}
- #loop through the various achievements, see if they have been obtained,
+ # Loop through the various achievements, see if they have been obtained.
foreach my $achievement (@achievements) {
- #skip achievements not assigned, not enabled, and that are already earned, or if it doesn't match the set type
+ # Skip achievements not assigned, not enabled, and that are already earned, or if it doesn't match the set type.
next unless $achievement->enabled;
my $achievement_id = $achievement->achievement_id;
- next unless ($db->existsUserAchievement($user_id, $achievement_id));
+ next unless $db->existsUserAchievement($user_id, $achievement_id);
my $userAchievement = $db->getUserAchievement($user_id, $achievement_id);
- next if ($userAchievement->earned);
+ next if $userAchievement->earned;
my $setType = $set->assignment_type;
next unless $achievement->assignment_type =~ /$setType/;
- #thaw_base64 localData hash
+ # Thaw_base64 localData hash.
if ($userAchievement->frozen_hash) {
$localData = thaw_base64($userAchievement->frozen_hash);
}
- #recover counter information (for progress bar achievements)
+ # Recover counter information (for progress bar achievements).
$counter = $userAchievement->counter;
$maxCounter = $achievement->max_counter;
- #check the achievement using Safe
+ # Check the achievement using Safe.
my $sourceFilePath = $ce->{courseDirs}{achievements} . '/' . $achievement->test;
if (-e $sourceFilePath) {
local $/ = undef;
@@ -205,11 +210,11 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
my $earned = $compartment->reval($preamble . "\n" . $source);
warn "There were errors in achievement $achievement_id\n" . $@ if $@;
- #if we have a new achievement then update achievement points
+ # If we have a new achievement then update achievement points.
if ($earned) {
$userAchievement->earned(1);
- # update userAchievements hash with earned status.
+ # Update userAchievements hash with earned status.
$userAchievements->{$achievement_id} = $earned;
if ($achievement->category eq 'level') {
@@ -223,14 +228,14 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
push(@$cheevoMessage, $c->include('AchievementEvaluator/cheevoMessage', achievement => $achievement));
my $points = $achievement->points;
- #just in case points is an uninitialized variable
+ # Just in case points is an uninitialized variable.
$points = 0 unless $points;
$globalUserAchievement->achievement_points($globalUserAchievement->achievement_points + $points);
- #this variable is shared and should be considered iffy
+ # This variable is shared and should be considered iffy.
$achievementPoints += $points;
- # if email_template is defined, send an email to the user
+ # If email_template is defined, send an email to the user.
$c->minion->enqueue(
send_achievement_email => [ {
recipient => $user_id,
@@ -245,14 +250,14 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
) if ($ce->{mail}{achievementEmailFrom} && $achievement->email_template);
}
- #update counter, nfreeze_base64 localData and store
+ # Update counter, nfreeze_base64 localData and store.
$userAchievement->counter($counter);
$userAchievement->frozen_hash(nfreeze_base64($localData));
$db->putUserAchievement($userAchievement);
- } #end for loop
+ } # End for loop.
- #nfreeze_base64 globalData and store
+ # nfreeze_base64 globalData and store.
$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
$db->putGlobalUserAchievement($globalUserAchievement);
diff --git a/lib/WeBWorK/AchievementItems.pm b/lib/WeBWorK/AchievementItems.pm
index f54d407e67..b2f1443918 100644
--- a/lib/WeBWorK/AchievementItems.pm
+++ b/lib/WeBWorK/AchievementItems.pm
@@ -59,9 +59,6 @@ sub UserItems ($c, $userName, $set, $records) {
# Return unless achievement items are enabled.
return unless $ce->{achievementsEnabled} && $ce->{achievementItemsEnabled};
- # When acting as another user, achievement items can be listed but not used.
- return if $set && $userName ne $c->param('user');
-
# Return unless the user has global achievement data.
my $globalUserAchievement = $c->{globalData} // $db->getGlobalUserAchievement($userName);
return unless $globalUserAchievement && $globalUserAchievement->frozen_hash;
@@ -70,9 +67,12 @@ sub UserItems ($c, $userName, $set, $records) {
my $use_item_id = $c->param('use_achievement_item_id') // '';
my @items;
+ # When acting as another user, achievement items can be listed but not used.
+ $use_item_id = '' if $userName ne $c->param('user');
+
for my $item (@{ +ITEMS }) {
next unless $globalData->{$item};
- my $achievementItem = "WeBWorK::AchievementItems::$item"->new;
+ my $achievementItem = "WeBWorK::AchievementItems::$item"->new($c);
$achievementItem->{count} = $globalData->{$item};
# Return list of achievements items if $set is not defined.
@@ -80,7 +80,7 @@ sub UserItems ($c, $userName, $set, $records) {
push(@items, $achievementItem);
next;
}
- next unless $achievementItem->can_use($set, $records);
+ next unless $achievementItem->can_use($set, $records, $c);
# Use the achievement item.
if ($use_item_id eq $item) {
@@ -90,7 +90,7 @@ sub UserItems ($c, $userName, $set, $records) {
$achievementItem->{count}--;
$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
$db->putGlobalUserAchievement($globalUserAchievement);
- $c->addgoodmessage($c->maketext('[_1] successfuly used. [_2]', $achievementItem->name, $message));
+ $c->addgoodmessage($c->maketext('[_1] successfully used. [_2]', $achievementItem->name, $message));
}
}
@@ -104,7 +104,7 @@ sub UserItems ($c, $userName, $set, $records) {
my @new_items;
for (@items) {
my $item = $_->[0];
- next unless $item->{count} && $item->can_use($set, $records);
+ next unless $item->{count} && $item->can_use($set, $records, $c);
push(@new_items, [ $item, $item->print_form($set, $records, $c) ]);
}
return \@new_items;
diff --git a/lib/WeBWorK/AchievementItems/AddNewTestGW.pm b/lib/WeBWorK/AchievementItems/AddNewTestGW.pm
index 9eadafbe69..308e20a4df 100644
--- a/lib/WeBWorK/AchievementItems/AddNewTestGW.pm
+++ b/lib/WeBWorK/AchievementItems/AddNewTestGW.pm
@@ -6,18 +6,20 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
use WeBWorK::Utils::DateTime qw(between);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'AddNewTestGW',
name => x('Oil of Cleansing'),
- description => x(
- 'Unlock an additional version of a test. If used before the close date of '
- . 'the test this will allow you to generate a new version of the test.'
- )
+ description => [
+ x(
+ 'Unlock an additional version of a test. If used before the close date of '
+ . 'the test this will allow you to generate a new version of the test.'
+ )
+ ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return
$set->assignment_type =~ /gateway/
&& $set->set_id !~ /,v\d+$/
diff --git a/lib/WeBWorK/AchievementItems/DoubleProb.pm b/lib/WeBWorK/AchievementItems/DoubleProb.pm
index 15ca7dc072..dc44112312 100644
--- a/lib/WeBWorK/AchievementItems/DoubleProb.pm
+++ b/lib/WeBWorK/AchievementItems/DoubleProb.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
use WeBWorK::Utils::DateTime qw(after);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'DoubleProb',
name => x('Cupcake of Enlargement'),
- description => x('Causes a single homework problem to be worth twice as much.')
+ description => [ x('Causes a single homework problem to be worth twice as much.') ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return $set->assignment_type eq 'default' && after($set->open_date);
}
diff --git a/lib/WeBWorK/AchievementItems/DoubleSet.pm b/lib/WeBWorK/AchievementItems/DoubleSet.pm
index b91629f717..ace5324250 100644
--- a/lib/WeBWorK/AchievementItems/DoubleSet.pm
+++ b/lib/WeBWorK/AchievementItems/DoubleSet.pm
@@ -6,15 +6,16 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
use WeBWorK::Utils::DateTime qw(after);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'DoubleSet',
name => x('Cake of Enlargement'),
- description => x('Cause the selected homework set to count for twice as many points as it normally would.')
+ description =>
+ [ x('Cause the selected homework set to count for twice as many points as it normally would.') ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return $set->assignment_type eq 'default' && after($set->open_date);
}
diff --git a/lib/WeBWorK/AchievementItems/DuplicateProb.pm b/lib/WeBWorK/AchievementItems/DuplicateProb.pm
index 20c90d726d..1147e3406c 100644
--- a/lib/WeBWorK/AchievementItems/DuplicateProb.pm
+++ b/lib/WeBWorK/AchievementItems/DuplicateProb.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
use WeBWorK::Utils::DateTime qw(between);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'DuplicateProb',
name => x('Box of Transmogrification'),
- description => x('Causes a homework problem to become a clone of another problem from the same set.')
+ description => [ x('Causes a homework problem to become a clone of another problem from the same set.') ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date);
}
diff --git a/lib/WeBWorK/AchievementItems/ExtendDueDate.pm b/lib/WeBWorK/AchievementItems/ExtendDueDate.pm
index 2391ab916d..02c1424bfb 100644
--- a/lib/WeBWorK/AchievementItems/ExtendDueDate.pm
+++ b/lib/WeBWorK/AchievementItems/ExtendDueDate.pm
@@ -1,38 +1,101 @@
package WeBWorK::AchievementItems::ExtendDueDate;
use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
-# Item to extend a close date by 24 hours.
+# Item to extend a close date by 24 * $achievementExtensionFactor hours.
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(after between);
+use WeBWorK::Utils::DateTime qw(before after between getExtensionTime);
-use constant ONE_DAY => 86400;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 1);
-sub new ($class) {
return bless {
id => 'ExtendDueDate',
name => x('Tunic of Extension'),
- description => x(
- 'Adds 24 hours to the close date of a homework. '
- . 'This will randomize problem details if used after the original close date.'
- )
+ description => [
+ x(
+ 'Adds [_1] to the close date of a homework. '
+ . 'This will randomize problem details if used after the original close date.',
+ $timeText
+ )
+ ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use ($self, $set, $records) {
- return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + ONE_DAY);
+sub can_use ($self, $set, $records, $c) {
+ return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + $self->{time});
}
sub print_form ($self, $set, $records, $c) {
my $randomization_statement = after($set->due_date) ? $c->maketext('All problems will be rerandomized.') : '';
- return $c->tag(
- 'p',
- $c->maketext(
- 'Extend the close date of this assignment to [_1] (an additional 24 hours). [_2]',
- $c->formatDateTime($set->due_date + ONE_DAY, $c->ce->{studentDateDisplayFormat}),
- $randomization_statement
- )
- );
+ if ($set->enable_reduced_scoring) {
+ if (before($set->reduced_scoring_date + $self->{time})) {
+ return $c->c(
+ $c->tag(
+ 'p',
+ $c->maketext('Extend the deadline by [_1]. [_2]', $self->{timeText}, $randomization_statement)
+ ),
+ $c->tag(
+ 'ul',
+ $c->c(
+ $c->tag(
+ 'li',
+ $c->maketext(
+ 'You will be able to receive full credit until [_1].',
+ $c->formatDateTime(
+ $set->reduced_scoring_date + $self->{time},
+ $c->ce->{studentDateDisplayFormat}
+ )
+ )
+ ),
+ $c->tag(
+ 'li',
+ $c->maketext(
+ 'You will be able to receive reduced credit until [_1].',
+ $c->formatDateTime(
+ $set->due_date + $self->{time},
+ $c->ce->{studentDateDisplayFormat}
+ )
+ )
+ )
+ )->join('')
+ ),
+ )->join('');
+ } else {
+ return $c->c(
+ $c->tag(
+ 'p',
+ $c->maketext(
+ 'Extend the reduced credit deadline of this assignment to [_1] (an additional [_2]). [_3]',
+ $c->formatDateTime($set->due_date + $self->{time}, $c->ce->{studentDateDisplayFormat}),
+ $self->{timeText},
+ $randomization_statement
+ )
+ ),
+ $c->tag(
+ 'p',
+ $c->maketext(
+ 'Because the deadline has already passed you will only '
+ . 'receive reduced credit during this extension.'
+ )
+ )
+ )->join('');
+ }
+
+ } else {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Extend the close date of this assignment to [_1] (an additional [_2]). [_3]',
+ $c->formatDateTime($set->due_date + $self->{time}, $c->ce->{studentDateDisplayFormat}),
+ $self->{timeText},
+ $randomization_statement
+ )
+ );
+ }
+
}
sub use_item ($self, $set, $records, $c) {
@@ -54,11 +117,11 @@ sub use_item ($self, $set, $records, $c) {
# Add time to the reduced scoring date if it was defined in the first place
if ($set->reduced_scoring_date) {
- $set->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY);
+ $set->reduced_scoring_date($set->reduced_scoring_date + $self->{time});
$userSet->reduced_scoring_date($set->reduced_scoring_date);
}
# Add time to the close date
- $set->due_date($set->due_date + ONE_DAY);
+ $set->due_date($set->due_date + $self->{time});
$userSet->due_date($set->due_date);
# This may require also extending the answer date.
if ($set->due_date > $set->answer_date) {
@@ -67,10 +130,8 @@ sub use_item ($self, $set, $records, $c) {
}
$db->putUserSet($userSet);
- return $c->maketext(
- 'Close date of this assignment extended by 24 hours to [_1].',
- $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
- );
+ return $c->maketext('Close date of this assignment extended by [_1] to [_2].',
+ $self->{timeText}, $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}));
}
1;
diff --git a/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm b/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm
index 6900d1f828..a098abced8 100644
--- a/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm
+++ b/lib/WeBWorK/AchievementItems/ExtendDueDateGW.pm
@@ -4,34 +4,88 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
# Item to extend the close date on a test
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(between);
+use WeBWorK::Utils::DateTime qw(before between getExtensionTime);
-use constant ONE_DAY => 86400;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 1);
-sub new ($class) {
return bless {
id => 'ExtendDueDateGW',
name => x('Amulet of Extension'),
- description =>
- x('Extends the close date of a test by 24 hours. Note: The test must still be open for this to work.')
+ description => [ x('Extends the close date of a test by [_1].', $timeText) ],
+ time => $time,
+ timeText => $timeText,
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return
$set->assignment_type =~ /gateway/
&& $set->set_id !~ /,v\d+$/
- && between($set->open_date, $set->due_date + ONE_DAY);
+ && between($set->open_date, $set->due_date + $self->{time});
}
sub print_form ($self, $set, $records, $c) {
- return $c->tag(
- 'p',
- $c->maketext(
- 'Extend the close date of this test to [_1] (an additional 24 hours).',
- $c->formatDateTime($set->due_date + ONE_DAY, $c->ce->{studentDateDisplayFormat})
- )
- );
+ if ($set->enable_reduced_scoring) {
+ if (before($set->reduced_scoring_date + $self->{time})) {
+ return $c->c(
+ $c->tag('p', $c->maketext('Extend the deadline by [_1].', $self->{timeText})),
+ $c->tag(
+ 'ul',
+ $c->c(
+ $c->tag(
+ 'li',
+ $c->maketext(
+ 'You will be able to receive full credit until [_1].',
+ $c->formatDateTime(
+ $set->reduced_scoring_date + $self->{time},
+ $c->ce->{studentDateDisplayFormat}
+ )
+ )
+ ),
+ $c->tag(
+ 'li',
+ $c->maketext(
+ 'You will be able to receive reduced credit until [_1].',
+ $c->formatDateTime(
+ $set->due_date + $self->{time},
+ $c->ce->{studentDateDisplayFormat}
+ )
+ )
+ )
+ )->join('')
+ ),
+ )->join('');
+ } else {
+ return $c->c(
+ $c->tag(
+ 'p',
+ $c->maketext(
+ 'Extend the reduced credit deadline of this assignment to [_1] (an additional [_2]).',
+ $c->formatDateTime($set->due_date + $self->{time}, $c->ce->{studentDateDisplayFormat}),
+ $self->{timeText}
+ )
+ ),
+ $c->tag(
+ 'p',
+ $c->maketext(
+ 'Because the deadline has already passed you will only '
+ . 'receive reduced credit during this extension.'
+ )
+ )
+ )->join('');
+ }
+
+ } else {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Extend the close date of this assignment to [_1] (an additional [_2]).',
+ $c->formatDateTime($set->due_date + $self->{time}, $c->ce->{studentDateDisplayFormat}),
+ $self->{timeText}
+ )
+ );
+ }
}
sub use_item ($self, $set, $records, $c) {
@@ -40,12 +94,12 @@ sub use_item ($self, $set, $records, $c) {
# Add time to the reduced scoring date, due date, and answer date.
if ($set->reduced_scoring_date) {
- $set->reduced_scoring_date($set->reduced_scoring_date + ONE_DAY);
+ $set->reduced_scoring_date($set->reduced_scoring_date + $self->{time});
$userSet->reduced_scoring_date($set->reduced_scoring_date);
}
- $set->due_date($set->due_date + ONE_DAY);
+ $set->due_date($set->due_date + $self->{time});
$userSet->due_date($set->due_date);
- $set->answer_date($set->answer_date + ONE_DAY);
+ $set->answer_date($set->answer_date + $self->{time});
$userSet->answer_date($set->answer_date);
$db->putUserSet($userSet);
@@ -55,15 +109,15 @@ sub use_item ($self, $set, $records, $c) {
#my @versions = $db->listSetVersions($userName, $setID);
#for my $version (@versions) {
# $set = $db->getSetVersion($userName, $setID, $version);
- # $set->reduced_scoring_date($set->reduced_scoring_date() + ONE_DAY)
+ # $set->reduced_scoring_date($set->reduced_scoring_date() + $self->{time})
# if defined($set->reduced_scoring_date()) && $set->reduced_scoring_date();
- # $set->due_date($set->due_date() + ONE_DAY);
- # $set->answer_date($set->answer_date() + ONE_DAY);
+ # $set->due_date($set->due_date() + $self->{time});
+ # $set->answer_date($set->answer_date() + $self->{time});
# $db->putSetVersion($set);
#}
- return $c->maketext('Close date of this test extended by 24 hours to [_1].',
- $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}));
+ return $c->maketext('Close date of this test extended by [_1] to [_2].',
+ $self->{timeText}, $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}));
}
1;
diff --git a/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm b/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm
index f6e24bb3b3..25703d5ffc 100644
--- a/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm
+++ b/lib/WeBWorK/AchievementItems/ExtendReducedDate.pm
@@ -1,33 +1,39 @@
package WeBWorK::AchievementItems::ExtendReducedDate;
use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
-# Item to extend a close date by 24 hours.
+# Item to extend a close date by 24 * $achievementExtensionFactor hours.
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(between);
+use WeBWorK::Utils::DateTime qw(between getExtensionTime);
-use constant ONE_DAY => 86400;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 1);
-sub new ($class) {
return bless {
id => 'ExtendReducedDate',
name => x('Scroll of Extension'),
- description => x(
- 'Adds 24 hours to the reduced scoring date of an assignment. You will have to resubmit '
- . 'any problems that have already been penalized to earn full credit. You cannot '
- . 'extend the reduced scoring date beyond the due date of an assignment.'
- )
+ description => [
+ x(
+ 'Adds [_1] to the reduced scoring date of an assignment. You will have to resubmit '
+ . 'any problems that have already been penalized to earn full credit. You cannot '
+ . 'extend the reduced scoring date beyond the due date of an assignment.',
+ $timeText
+ )
+ ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return 0
unless $set->assignment_type eq 'default'
+ && $c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
&& $set->enable_reduced_scoring
&& $set->reduced_scoring_date
&& $set->reduced_scoring_date < $set->due_date;
- $self->{new_date} = $set->reduced_scoring_date + ONE_DAY;
+ $self->{new_date} = $set->reduced_scoring_date + $self->{time};
$self->{new_date} = $set->due_date if $set->due_date < $self->{new_date};
return between($set->open_date, $self->{new_date});
}
@@ -37,15 +43,16 @@ sub print_form ($self, $set, $records, $c) {
'p',
$c->maketext(
q{This item won't work unless your instructor enables the reduced scoring feature. }
- . 'Let your instructor know that you recieved this message.'
+ . 'Let your instructor know that you received this message.'
)
) unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
return $c->tag(
'p',
$c->maketext(
- 'Extend the reduced scoring date to [_1] (an additional 24 hours).',
- $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat})
+ 'Extend the reduced scoring date to [_1] (an additional [_2]).',
+ $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat}),
+ $self->{timeText}
)
);
}
@@ -60,10 +67,8 @@ sub use_item ($self, $set, $records, $c) {
$userSet->reduced_scoring_date($set->reduced_scoring_date);
$db->putUserSet($userSet);
- return $c->maketext(
- 'Reduced scoring date of this assignment extended by 24 hours to [_1].',
- $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat})
- );
+ return $c->maketext('Reduced scoring date of this assignment extended by [_1] to [_2].',
+ $self->{timeText}, $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat}));
}
1;
diff --git a/lib/WeBWorK/AchievementItems/FullCreditProb.pm b/lib/WeBWorK/AchievementItems/FullCreditProb.pm
index 50e15639b5..fa1d26f22b 100644
--- a/lib/WeBWorK/AchievementItems/FullCreditProb.pm
+++ b/lib/WeBWorK/AchievementItems/FullCreditProb.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x wwRound);
use WeBWorK::Utils::DateTime qw(after);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'FullCreditProb',
name => x('Greater Rod of Revelation'),
- description => x('Gives full credit on a single homework problem.')
+ description => [ x('Gives full credit on a single homework problem.') ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return 0
unless $set->assignment_type eq 'default'
&& after($set->open_date);
diff --git a/lib/WeBWorK/AchievementItems/FullCreditSet.pm b/lib/WeBWorK/AchievementItems/FullCreditSet.pm
index 6c9065fa48..ec3f5bd223 100644
--- a/lib/WeBWorK/AchievementItems/FullCreditSet.pm
+++ b/lib/WeBWorK/AchievementItems/FullCreditSet.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x wwRound);
use WeBWorK::Utils::DateTime qw(after);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'FullCreditSet',
name => x('Greater Tome of Enlightenment'),
- description => x('Gives full credit on every problem in a set.')
+ description => [ x('Gives full credit on every problem in a set.') ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return 0
unless $set->assignment_type eq 'default'
&& after($set->open_date);
@@ -25,6 +25,8 @@ sub can_use ($self, $set, $records) {
$grade += $problem->status * $problem->value;
$total += $problem->value;
}
+ return 0 unless $total;
+
$self->{old_grade} = 100 * wwRound(2, $grade / $total);
return $self->{old_grade} == 100 ? 0 : 1;
}
diff --git a/lib/WeBWorK/AchievementItems/HalfCreditProb.pm b/lib/WeBWorK/AchievementItems/HalfCreditProb.pm
index 307e3d32af..817184c59c 100644
--- a/lib/WeBWorK/AchievementItems/HalfCreditProb.pm
+++ b/lib/WeBWorK/AchievementItems/HalfCreditProb.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x wwRound);
use WeBWorK::Utils::DateTime qw(after);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'HalfCreditProb',
name => x('Lesser Rod of Revelation'),
- description => x('Increases the grade of a single problem by 50%, to a maximum of 100%.')
+ description => [ x('Increases the grade of a single problem by 50%, to a maximum of 100%.') ]
}, $class;
}
-sub can_use($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return 0
unless $set->assignment_type eq 'default'
&& after($set->open_date);
diff --git a/lib/WeBWorK/AchievementItems/HalfCreditSet.pm b/lib/WeBWorK/AchievementItems/HalfCreditSet.pm
index 37496e9bdd..0b8e8396b8 100644
--- a/lib/WeBWorK/AchievementItems/HalfCreditSet.pm
+++ b/lib/WeBWorK/AchievementItems/HalfCreditSet.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x wwRound);
use WeBWorK::Utils::DateTime qw(after);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'HalfCreditSet',
name => x('Lesser Tome of Enlightenment'),
- description => x('Increases the score of every problem in an assignment by 50%, to a maximum of 100%.')
+ description => [ x('Increases the score of every problem in an assignment by 50%, to a maximum of 100%.') ]
}, $class;
}
-sub can_use($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return 0
unless $set->assignment_type eq 'default'
&& after($set->open_date);
@@ -27,6 +27,8 @@ sub can_use($self, $set, $records) {
$new_grade += ($problem->status > 0.5 ? 1 : $problem->status + 0.5) * $problem->value;
$total += $problem->value;
}
+ return 0 unless $total;
+
$self->{old_grade} = 100 * wwRound(2, $old_grade / $total);
$self->{new_grade} = 100 * wwRound(2, $new_grade / $total);
return $self->{old_grade} == 100 ? 0 : 1;
diff --git a/lib/WeBWorK/AchievementItems/NoReducedCred.pm b/lib/WeBWorK/AchievementItems/NoReducedCred.pm
index 8863196463..92bc0a1178 100644
--- a/lib/WeBWorK/AchievementItems/NoReducedCred.pm
+++ b/lib/WeBWorK/AchievementItems/NoReducedCred.pm
@@ -7,20 +7,23 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
use WeBWorK::Utils::DateTime qw(between);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'NoReducedCred',
name => x('Potion of Power'),
- description => x(
- 'Remove reduced scoring penalties from an open assignemnt. You will have to resubmit '
- . 'any problems that have already been penalized to earn full credit on them.'
- )
+ description => [
+ x(
+ 'Remove reduced scoring penalties from an open assignment. You will have to resubmit '
+ . 'any problems that have already been penalized to earn full credit on them.'
+ )
+ ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return
$set->assignment_type eq 'default'
+ && $c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
&& $set->enable_reduced_scoring
&& $set->reduced_scoring_date
&& $set->reduced_scoring_date < $set->due_date
@@ -32,7 +35,7 @@ sub print_form ($self, $set, $records, $c) {
'p',
$c->maketext(
q{This item won't work unless your instructor enables the reduced scoring feature. }
- . 'Let your instructor know that you recieved this message.'
+ . 'Let your instructor know that you received this message.'
)
) unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
diff --git a/lib/WeBWorK/AchievementItems/ReducedCred.pm b/lib/WeBWorK/AchievementItems/ReducedCred.pm
index 340d960815..a473e76227 100644
--- a/lib/WeBWorK/AchievementItems/ReducedCred.pm
+++ b/lib/WeBWorK/AchievementItems/ReducedCred.pm
@@ -1,28 +1,33 @@
package WeBWorK::AchievementItems::ReducedCred;
use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
-# Item to extend a close date by 24 hours for reduced credit
+# Item to extend a close date by 24 * $achievementExtensionFactor hours for reduced credit
# Reduced scoring needs to be enabled for this item to work.
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(after between);
+use WeBWorK::Utils::DateTime qw(after between getExtensionTime);
-use constant ONE_DAY => 86400;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 1);
-sub new ($class) {
return bless {
id => 'ReducedCred',
name => x('Ring of Reduction'),
- description => x(
- 'Enable reduced scoring for a homework set. This will allow you to submit answers '
- . 'for partial credit for 24 hours after the close date. '
- . 'This will randomize problem details if used after the original close date.'
- )
+ description => [
+ x(
+ 'Enable reduced scoring for a homework set. This will allow you to submit answers '
+ . 'for partial credit for [_1] after the close date. '
+ . 'This will randomize problem details if used after the original close date.',
+ $timeText
+ )
+ ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use ($self, $set, $records) {
- return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + ONE_DAY);
+sub can_use ($self, $set, $records, $c) {
+ return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + $self->{time});
}
sub print_form ($self, $set, $records, $c) {
@@ -40,9 +45,10 @@ sub print_form ($self, $set, $records, $c) {
return $c->tag(
'p',
$c->maketext(
- 'Extend the close date of this assignment to [_1] (an additional 24 hours). Any submissions during '
- . 'this additional time will be reduced and are worth [_2]% of their full value. [_3]',
- $c->formatDateTime($set->due_date + ONE_DAY, $ce->{studentDateDisplayFormat}),
+ 'Extend the close date of this assignment to [_1] (an additional [_2]). Any submissions during '
+ . 'this additional time will be reduced and are worth [_3]% of their full value. [_4]',
+ $c->formatDateTime($set->due_date + $self->{time}, $ce->{studentDateDisplayFormat}),
+ $self->{timeText},
100 * $ce->{pg}{ansEvalDefaults}{reducedScoringValue},
$randomization_statement
)
@@ -79,7 +85,7 @@ sub use_item ($self, $set, $records, $c) {
$set->enable_reduced_scoring(1);
$userSet->enable_reduced_scoring(1);
# Add time to the close date
- $set->due_date($set->due_date + ONE_DAY);
+ $set->due_date($set->due_date + $self->{time});
$userSet->due_date($set->due_date);
# This may require also extending the answer date.
if ($set->due_date > $set->answer_date) {
@@ -88,10 +94,8 @@ sub use_item ($self, $set, $records, $c) {
}
$db->putUserSet($userSet);
- return $c->maketext(
- 'Close date of this assignment extended by 24 hours to [_1].',
- $c->formatDateTime($set->due_date, $ce->{studentDateDisplayFormat})
- );
+ return $c->maketext('Close date of this assignment extended by [_1] to [_2].',
+ $self->{timeText}, $c->formatDateTime($set->due_date, $ce->{studentDateDisplayFormat}));
}
1;
diff --git a/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm b/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm
index cd0ef5a330..ef40f88706 100644
--- a/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm
+++ b/lib/WeBWorK/AchievementItems/ResetIncorrectAttempts.pm
@@ -6,15 +6,15 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
use WeBWorK::Utils::DateTime qw(between);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'ResetIncorrectAttempts',
name => x('Potion of Forgetfulness'),
- description => x('Resets the number of incorrect attempts on a single homework problem.')
+ description => [ x('Resets the number of incorrect attempts on a single homework problem.') ]
}, $class;
}
-sub can_use ($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return 0
unless $set->assignment_type eq 'default'
&& between($set->open_date, $set->due_date);
diff --git a/lib/WeBWorK/AchievementItems/ResurrectGW.pm b/lib/WeBWorK/AchievementItems/ResurrectGW.pm
index 00b99ca4ce..e841efd1fb 100644
--- a/lib/WeBWorK/AchievementItems/ResurrectGW.pm
+++ b/lib/WeBWorK/AchievementItems/ResurrectGW.pm
@@ -4,35 +4,91 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
# Item to extend the due date on a gateway
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(after);
+use WeBWorK::Utils::DateTime qw(after getExtensionTime);
-use constant ONE_DAY => 86400;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 1);
-sub new ($class) {
return bless {
id => 'ResurrectGW',
name => x('Necromancers Charm'),
- description => x(
- 'Reopens any test for an additional 24 hours. This allows you to take a test even if the '
- . 'close date has past. This item does not allow you to take additional versions of the test.'
- )
+ description => [
+ x(
+ 'Reopens any test for an additional [_1]. If you are allowed to start new versions of the test, '
+ . 'then this allows you to start a new test even if the close date has past. '
+ . 'If you were not allowed to start a new version of the test, '
+ . 'then this item will not allow you to take additional versions of the test. '
+ . 'This item will not extend the time limit for any tests that you have already started.',
+ $timeText
+ )
+ ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return $set->assignment_type =~ /gateway/
- && (after($set->due_date) || ($set->reduced_scoring_date && after($set->reduced_scoring_date)));
+ && (
+ after($set->due_date)
+ || ($c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
+ && $set->enable_reduced_scoring
+ && after($set->reduced_scoring_date))
+ );
# TODO: Check if a new version can be created, and only allow using this reward in that case.
}
sub print_form ($self, $set, $records, $c) {
- return $c->tag(
- 'p',
- $c->maketext(
- 'Reopen this test for the next 24 hours. This item does not allow you to take any additional '
- . 'versions of the test.'
- )
- );
+ if (after($set->due_date)) {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Reopen this test for the next [_1]. If you were allowed to start new versions of the test, '
+ . 'then this will allow you to start a new test. '
+ . 'If you have already started all of the versions of the test that you are allowed to start, '
+ . 'then you should not use this item. '
+ . 'This item will not extend the time limit for any tests that you have already started.',
+ $self->{timeText}
+ )
+ );
+ } else {
+ if (after($set->due_date - $self->{time})) {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Reopen this test for full credit for the next [_1]. If you are allowed to start new versions '
+ . 'of the test, then this will allow you to start a new test. '
+ . 'If you have already started all of the versions of the test that you are allowed to start, '
+ . 'then you should not use this item. '
+ . 'This item will not extend the time limit for any tests that you have already started.',
+ $self->{timeText}
+ )
+ );
+ } else {
+ return $c->c(
+ $c->tag(
+ 'p',
+ $c->maketext(
+ 'Reopen this test for full credit for the next [_1]. After [_1] any tests will revert '
+ . 'to counting for [_2]% of their value until [_3].',
+ $c->{timeText},
+ $c->ce->{pg}{ansEvalDefaults}{reducedScoringValue} * 100,
+ $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
+ )
+ ),
+ $c->tag(
+ 'p',
+ $c->maketext(
+ ' If you are allowed to start new versions of the test, '
+ . 'then this will allow you to start a new test. '
+ . 'If you have already started all of the versions of the test that you are allowed to start, '
+ . 'then you should not use this item. '
+ . 'This item will not extend the time limit for any tests that you have already started.'
+ )
+ )
+ )->join('');
+ }
+ }
}
sub use_item ($self, $set, $records, $c) {
@@ -41,21 +97,36 @@ sub use_item ($self, $set, $records, $c) {
# Add time to the reduced scoring date, due date, and answer date.
if ($set->reduced_scoring_date) {
- $set->reduced_scoring_date(time + ONE_DAY);
+ $set->reduced_scoring_date(time + $self->{time});
$userSet->reduced_scoring_date($set->reduced_scoring_date);
}
- $set->due_date(time + ONE_DAY);
- $userSet->due_date($set->due_date);
- if ($set->due_date > $set->answer_date) {
- $set->answer_date(time + ONE_DAY);
- $userSet->answer_date($set->answer_date);
+ if (after($set->due_date - $self->{time})) {
+ $set->due_date(time + $self->{time});
+ $userSet->due_date($set->due_date);
+ if ($set->due_date > $set->answer_date) {
+ $set->answer_date($set->due_date);
+ $userSet->answer_date($set->answer_date);
+ }
}
$db->putUserSet($userSet);
- return $c->maketext(
- 'This assignment has been reopened and will now close on [_1].',
- $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
- );
+ if ($c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
+ && $set->enable_reduced_scoring
+ && ($set->reduced_scoring_date != $set->due_date))
+ {
+ return $c->maketext(
+ 'This assignment has been reopened and is due on [_1]. After that date any work '
+ . 'completed will count for [_2]% of its value until [_3].',
+ $c->formatDateTime($set->reduced_scoring_date, $c->ce->{studentDateDisplayFormat}),
+ $c->ce->{pg}{ansEvalDefaults}{reducedScoringValue} * 100,
+ $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
+ );
+ } else {
+ return $c->maketext(
+ 'This assignment has been reopened and will now close on [_1].',
+ $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
+ );
+ }
}
1;
diff --git a/lib/WeBWorK/AchievementItems/ResurrectHW.pm b/lib/WeBWorK/AchievementItems/ResurrectHW.pm
index da8364822f..2d9daf2a2c 100644
--- a/lib/WeBWorK/AchievementItems/ResurrectHW.pm
+++ b/lib/WeBWorK/AchievementItems/ResurrectHW.pm
@@ -1,63 +1,119 @@
package WeBWorK::AchievementItems::ResurrectHW;
use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
-# Item to resurrect a homework for 24 hours
+# Item to resurrect a homework for 24 * $achievementExtensionFactor hours
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(after);
+use WeBWorK::Utils::DateTime qw(after getExtensionTime);
-use constant ONE_DAY => 86400;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 1);
-sub new ($class) {
return bless {
id => 'ResurrectHW',
name => x('Scroll of Resurrection'),
- description => x("Reopens one closed homework set for 24 hours and rerandomizes all problems."),
+ description => [ x('Reopens one closed homework set for [_1] and rerandomizes all problems.', $timeText) ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use($self, $set, $records) {
+sub can_use ($self, $set, $records, $c) {
return $set->assignment_type eq 'default'
- && (after($set->due_date) || ($set->reduced_scoring_date && after($set->reduced_scoring_date)));
+ && (
+ after($set->due_date)
+ || ($c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
+ && $set->enable_reduced_scoring
+ && after($set->reduced_scoring_date))
+ );
}
sub print_form ($self, $set, $records, $c) {
- return $c->tag('p',
- $c->maketext('Reopen this homework assignment for the next 24 hours. All problems will be rerandomized.'));
+ if (after($set->due_date)) {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Reopen this homework assignment for the next [_1]. All problems will be rerandomized.',
+ $self->{timeText}
+ )
+ );
+ } else {
+ if (after($set->due_date - $self->{time})) {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Reopen this homework assignment for full credit for the next [_1]. ',
+ $self->{timeText}
+ )
+ );
+ } else {
+ return $c->tag(
+ 'p',
+ $c->maketext(
+ 'Reopen this homework assignment for full credit for the next [_1]. After [_1] '
+ . 'any progress will revert to counting for [_2]% of the value until [_3].',
+ $self->{timeText},
+ $c->ce->{pg}{ansEvalDefaults}{reducedScoringValue} * 100,
+ $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
+ )
+ );
+ }
+ }
}
sub use_item ($self, $set, $records, $c) {
- my $db = $c->db;
- my $userSet = $db->getUserSet($set->user_id, $set->set_id);
-
- # Change the seed for all of the problems since the set is currently closed.
- my %userProblems =
- map { $_->problem_id => $_ } $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id });
- for my $problem (@$records) {
- my $userProblem = $userProblems{ $problem->problem_id };
- $userProblem->problem_seed($userProblem->problem_seed % 2**31 + 1);
- $problem->problem_seed($userProblem->problem_seed);
- $db->putUserProblem($userProblem);
+ my $db = $c->db;
+ my $userSet = $db->getUserSet($set->user_id, $set->set_id);
+ my $rerandomizeMessage = '';
+
+ # Change the seed for all of the problems if the set is currently closed.
+ if (after($set->due_date)) {
+ my %userProblems =
+ map { $_->problem_id => $_ }
+ $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id });
+ for my $problem (@$records) {
+ my $userProblem = $userProblems{ $problem->problem_id };
+ $userProblem->problem_seed($userProblem->problem_seed % 2**31 + 1);
+ $problem->problem_seed($userProblem->problem_seed);
+ $db->putUserProblem($userProblem);
+ }
+ $rerandomizeMessage = $c->maketext('Problems have been rerandomized.');
}
# Add time to the reduced scoring date if it was defined in the first place
if ($set->reduced_scoring_date) {
- $set->reduced_scoring_date(time + ONE_DAY);
+ $set->reduced_scoring_date(time + $self->{time});
$userSet->reduced_scoring_date($set->reduced_scoring_date);
}
- # Add time to the close date
- $set->due_date(time + ONE_DAY);
- $userSet->due_date($set->due_date);
- # This may require also extending the answer date.
- if ($set->due_date > $set->answer_date) {
- $set->answer_date($set->due_date);
- $userSet->answer_date($set->answer_date);
+ # Add time to the close date if necessary
+ if (after($set->due_date - $self->{time})) {
+ $set->due_date(time + $self->{time});
+ $userSet->due_date($set->due_date);
+ # This may require also extending the answer date.
+ if ($set->due_date > $set->answer_date) {
+ $set->answer_date($set->due_date);
+ $userSet->answer_date($set->answer_date);
+ }
}
$db->putUserSet($userSet);
- return $c->maketext(
- 'This assignment has been reopened and will now close on [_1]. Problems have been rerandomized.',
- $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat}));
+ if ($c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
+ && $set->enable_reduced_scoring
+ && ($set->reduced_scoring_date != $set->due_date))
+ {
+ return $c->maketext(
+ 'This assignment has been reopened and is due on [_1]. After that date any work '
+ . 'completed will count for [_2]% of its value until [_3].',
+ $c->formatDateTime($set->reduced_scoring_date, $c->ce->{studentDateDisplayFormat}),
+ $c->ce->{pg}{ansEvalDefaults}{reducedScoringValue} * 100,
+ $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
+ ) . ($rerandomizeMessage ? " $rerandomizeMessage" : '');
+ } else {
+ return $c->maketext(
+ 'This assignment has been reopened and will now close on [_1].',
+ $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
+ ) . ($rerandomizeMessage ? " $rerandomizeMessage" : '');
+ }
}
1;
diff --git a/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm b/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm
index 7932e6de20..42da5b5162 100644
--- a/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm
+++ b/lib/WeBWorK/AchievementItems/SuperExtendDueDate.pm
@@ -1,76 +1,27 @@
package WeBWorK::AchievementItems::SuperExtendDueDate;
-use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
+use Mojo::Base 'WeBWorK::AchievementItems::ExtendDueDate', -signatures;
-# Item to extend a close date by 48 hours.
+# Item to extend a close date by 48 * $achievementExtensionFactor hours.
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(after between);
+use WeBWorK::Utils::DateTime qw(getExtensionTime);
-use constant TWO_DAYS => 172800;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 2);
-sub new ($class) {
return bless {
id => 'SuperExtendDueDate',
name => x('Robe of Longevity'),
- description => x(
- 'Adds 48 hours to the close date of a homework. '
- . 'This will randomize problem details if used after the original close date.'
- )
+ description => [
+ x(
+ 'Adds [_1] to the close date of a homework. '
+ . 'This will randomize problem details if used after the original close date.',
+ $timeText
+ )
+ ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use ($self, $set, $records) {
- return $set->assignment_type eq 'default' && between($set->open_date, $set->due_date + TWO_DAYS);
-}
-
-sub print_form ($self, $set, $records, $c) {
- my $randomization_statement = after($set->due_date) ? $c->maketext('All problems will be rerandomized.') : '';
- return $c->tag(
- 'p',
- $c->maketext(
- 'Extend the close date of this assignment to [_1] (an additional 48 hours). [_2]',
- $c->formatDateTime($set->due_date + TWO_DAYS, $c->ce->{studentDateDisplayFormat}),
- $randomization_statement
- )
- );
-}
-
-sub use_item ($self, $set, $records, $c) {
- my $db = $c->db;
- my $userSet = $db->getUserSet($set->user_id, $set->set_id);
-
- # Change the seed for all of the problems if the set is currently closed.
- if (after($set->due_date)) {
- my %userProblems =
- map { $_->problem_id => $_ }
- $db->getUserProblemsWhere({ user_id => $set->user_id, set_id => $set->set_id });
- for my $problem (@$records) {
- my $userProblem = $userProblems{ $problem->problem_id };
- $userProblem->problem_seed($userProblem->problem_seed % 2**31 + 1);
- $problem->problem_seed($userProblem->problem_seed);
- $db->putUserProblem($userProblem);
- }
- }
-
- # Add time to the reduced scoring date if it was defined in the first place
- if ($set->reduced_scoring_date) {
- $set->reduced_scoring_date($set->reduced_scoring_date + TWO_DAYS);
- $userSet->reduced_scoring_date($set->reduced_scoring_date);
- }
- # Add time to the close date
- $set->due_date($set->due_date + TWO_DAYS);
- $userSet->due_date($set->due_date);
- # This may require also extending the answer date.
- if ($set->due_date > $set->answer_date) {
- $set->answer_date($set->due_date);
- $userSet->answer_date($set->answer_date);
- }
- $db->putUserSet($userSet);
-
- return $c->maketext(
- 'Close date of this assignment extended by 48 hours to [_1].',
- $c->formatDateTime($set->due_date, $c->ce->{studentDateDisplayFormat})
- );
-}
-
1;
diff --git a/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm b/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm
index 3f2361b835..c214897b68 100644
--- a/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm
+++ b/lib/WeBWorK/AchievementItems/SuperExtendReducedDate.pm
@@ -1,69 +1,28 @@
package WeBWorK::AchievementItems::SuperExtendReducedDate;
-use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
+use Mojo::Base 'WeBWorK::AchievementItems::ExtendReducedDate', -signatures;
-# Item to extend a close date by 48 hours.
+# Item to extend a close date by 48 * $achievementExtensionFactor hours.
use WeBWorK::Utils qw(x);
-use WeBWorK::Utils::DateTime qw(between);
+use WeBWorK::Utils::DateTime qw(getExtensionTime);
-use constant TWO_DAYS => 172800;
+sub new ($class, $c) {
+ my ($time, $timeText) = getExtensionTime($c, 2);
-sub new ($class) {
return bless {
id => 'SuperExtendReducedDate',
name => x('Scroll of Longevity'),
- description => x(
- 'Adds 48 hours to the reduced scoring date of an assignment. You will have to resubmit '
- . 'any problems that have already been penalized to earn full credit. You cannot '
- . 'extend the reduced scoring date beyond the due date of an assignment.'
- )
+ description => [
+ x(
+ 'Adds [_1] to the reduced scoring date of an assignment. You will have to resubmit '
+ . 'any problems that have already been penalized to earn full credit. You cannot '
+ . 'extend the reduced scoring date beyond the due date of an assignment.',
+ $timeText
+ )
+ ],
+ time => $time,
+ timeText => $timeText
}, $class;
}
-sub can_use ($self, $set, $records) {
- return 0
- unless $set->assignment_type eq 'default'
- && $set->enable_reduced_scoring
- && $set->reduced_scoring_date
- && $set->reduced_scoring_date < $set->due_date;
-
- $self->{new_date} = $set->reduced_scoring_date + TWO_DAYS;
- $self->{new_date} = $set->due_date if $set->due_date < $self->{new_date};
- return between($set->open_date, $self->{new_date});
-}
-
-sub print_form ($self, $set, $records, $c) {
- return $c->tag(
- 'p',
- $c->maketext(
- q{This item won't work unless your instructor enables the reduced scoring feature. }
- . 'Let your instructor know that you recieved this message.'
- )
- ) unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
-
- return $c->tag(
- 'p',
- $c->maketext(
- 'Extend the reduced scoring date to [_1] (an additional 48 hours).',
- $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat})
- )
- );
-}
-
-sub use_item ($self, $set, $records, $c) {
- return '' unless $c->{ce}->{pg}{ansEvalDefaults}{enableReducedScoring};
-
- my $db = $c->db;
- my $userSet = $db->getUserSet($set->user_id, $set->set_id);
-
- $set->reduced_scoring_date($self->{new_date});
- $userSet->reduced_scoring_date($set->reduced_scoring_date);
- $db->putUserSet($userSet);
-
- return $c->maketext(
- 'Reduced scoring date of this assignment extended by 48 hours to [_1].',
- $c->formatDateTime($self->{new_date}, $c->ce->{studentDateDisplayFormat})
- );
-}
-
1;
diff --git a/lib/WeBWorK/AchievementItems/Surprise.pm b/lib/WeBWorK/AchievementItems/Surprise.pm
index b598b75352..f5140cbb79 100644
--- a/lib/WeBWorK/AchievementItems/Surprise.pm
+++ b/lib/WeBWorK/AchievementItems/Surprise.pm
@@ -5,11 +5,11 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
use WeBWorK::Utils qw(x);
-sub new ($class) {
+sub new ($class, $c) {
return bless {
id => 'Surprise',
name => x('Mysterious Package (with Ribbons)'),
- description => x('What could be inside?')
+ description => [ x('What could be inside?') ]
}, $class;
}
@@ -18,7 +18,7 @@ sub remaining_title ($self, $c) {
return $c->maketext($self->name);
}
-sub can_use ($self, $set, $records) { return 1; }
+sub can_use ($self, $set, $records, $c) { return 1; }
sub print_form ($self, $set, $records, $c) {
$self->{hideUseButton} = 1;
@@ -26,7 +26,7 @@ sub print_form ($self, $set, $records, $c) {
# The form opens the file "surprise_message.txt" in the achievements
# folder and prints the contents of the file.
open my $MESSAGE, '<', "$c->{ce}{courseDirs}{achievements}/surprise_message.txt"
- or return $c->tag('p', $c->maketext(q{I couldn't find the file [ACHIEVEMENT_DIR]/surprise_message.txt!}));
+ or return $c->tag('p', $c->maketext(q{I couldn't find the file ~[ACHIEVEMENT_DIR~]/surprise_message.txt!}));
local $/ = undef;
my $message = <$MESSAGE>;
close $MESSAGE;
diff --git a/lib/WeBWorK/Authen.pm b/lib/WeBWorK/Authen.pm
index db565f21f0..ccc02f2cad 100644
--- a/lib/WeBWorK/Authen.pm
+++ b/lib/WeBWorK/Authen.pm
@@ -327,7 +327,7 @@ sub get_credentials {
$self->{login_type} = "normal";
$self->{credential_source} = "params_and_cookie";
debug(
- 'credential soure: "cookie (password from params)", user: "',
+ 'credential source: "cookie (password from params)", user: "',
$self->{user_id}, '", key: "', $self->{session_key},
'", timestamp = "',
$self->{cookie_timestamp}, '"'
@@ -377,9 +377,9 @@ sub check_user {
return 0;
}
- my $User = $db->getUser($user_id);
+ $self->{user} = $db->getUser($user_id);
- unless ($User) {
+ unless ($self->{user}) {
$self->{log_error} = "user unknown";
$self->{error} = $c->maketext(GENERIC_ERROR_MESSAGE);
return 0;
@@ -388,6 +388,29 @@ sub check_user {
return 1;
}
+sub validate_user {
+ my $self = shift;
+ my $c = $self->{c};
+
+ # Deny access for certain roles (dropped students, proctor roles).
+ unless ($self->{login_type} =~ /^proctor/
+ || $c->ce->status_abbrev_has_behavior($self->{user}->status, 'allow_course_access'))
+ {
+ $self->{log_error} = 'user not allowed course access';
+ $self->{error} = $c->maketext('This user is not allowed to log in to this course');
+ return 0;
+ }
+
+ # Deny access for permission levels below 'login' permission level.
+ unless ($c->authz->hasPermissions($self->{user_id}, 'login')) {
+ $self->{log_error} = 'user not permitted to login';
+ $self->{error} = $c->maketext('This user is not allowed to log in to this course');
+ return 0;
+ }
+
+ return 1;
+}
+
sub verify_practice_user {
my $self = shift;
my $c = $self->{c};
@@ -485,6 +508,7 @@ sub verify_normal_user {
$c->stash(authen_error => $c->maketext('The security code is required.'));
}
}
+ return 0 unless $self->validate_user;
return 1;
} else {
my $auth_result = $self->authenticate;
@@ -494,20 +518,7 @@ sub verify_normal_user {
delete $self->session->{two_factor_verification_needed};
if ($auth_result > 0) {
- # Deny certain roles (dropped students, proctor roles).
- unless ($self->{login_type} =~ /^proctor/
- || $c->ce->status_abbrev_has_behavior($c->db->getUser($user_id)->status, "allow_course_access"))
- {
- $self->{log_error} = "user not allowed course access";
- $self->{error} = $c->maketext('This user is not allowed to log in to this course');
- return 0;
- }
- # Deny permission levels below "login" permission level.
- unless ($c->authz->hasPermissions($user_id, "login")) {
- $self->{log_error} = "user not permitted to login";
- $self->{error} = $c->maketext('This user is not allowed to log in to this course');
- return 0;
- }
+ return 0 unless $self->validate_user;
$self->{session_key} = $self->create_session($user_id);
$self->{initial_login} = 1;
return 1;
@@ -844,7 +855,7 @@ sub store_session {
}
}
- # The session parameters need to be set again, because another request may have occured during this
+ # The session parameters need to be set again, because another request may have occurred during this
# request in which case the session parameters for the app will now be set for that request.
$self->{c}->setSessionParams;
diff --git a/lib/WeBWorK/Authen/Cosign.pm b/lib/WeBWorK/Authen/Cosign.pm
deleted file mode 100644
index 3bdae8976c..0000000000
--- a/lib/WeBWorK/Authen/Cosign.pm
+++ /dev/null
@@ -1,90 +0,0 @@
-package WeBWorK::Authen::Cosign;
-use base qw/WeBWorK::Authen/;
-
-=head1 NAME
-
-WeBWorK::Authen::Cosign - Authentication plug in for cosign
-
-to use: include in localOverrides.conf or course.conf
- $authen{user_module} = "WeBWorK::Authen::Cosign";
-and add /webwork2 or /webwork2/courseName as a CosignProtected
-Location
-
-if $c->ce->{cosignoff} is set for a course, authentication reverts
-to standard WeBWorK authentication.
-
-=cut
-
-use strict;
-use warnings;
-use WeBWorK::Debug;
-
-# this is similar to the method in the base class, except that cosign
-# ensures that we don't get to the address without a login. this means
-# that we can't allow guest logins, but don't have to do any password
-# checking or cookie management.
-
-sub get_credentials {
- my ($self) = @_;
- my $c = $self->{c};
- my $ce = $c->ce;
- my $db = $c->db;
-
- if ($ce->{cosignoff}) {
- return $self->SUPER::get_credentials();
- } else {
- $c->stash(disable_cookies => 1);
-
- if (defined($ENV{'REMOTE_USER'})) {
- $self->{'user_id'} = $ENV{'REMOTE_USER'};
- $self->{c}->param("user", $ENV{'REMOTE_USER'});
- } else {
- return 0;
- }
- # set external auth parameter so that Login.pm knows
- # not to rely on internal logins if there's a check_user
- # failure.
- $self->{external_auth} = 1;
-
- # the session key isn't used (cosign is managing this
- # for us), and we want to force checking against the
- # site_checkPassword
- $self->{'session_key'} = undef;
- $self->{'password'} = 1;
- $self->{'credential_source'} = "params";
- $self->{login_type} = "cosign";
-
- return 1;
- }
-}
-
-sub site_checkPassword {
- my ($self, $userID, $clearTextPassword) = @_;
-
- if ($self->{c}->ce->{cosignoff}) {
- return 0;
- } else {
- # this is easy; if we're here at all, we've authenticated
- # through cosign
- return 1;
- }
-}
-
-# this is a bit of a cheat, because it does the redirect away from the
-# logout script or what have you, but I don't see a way around that.
-sub forget_verification {
- my ($self, @args) = @_;
- my $c = $self->{c};
-
- if ($c->ce->{cosignoff}) {
- return $self->SUPER::forget_verification(@args);
- } else {
- $self->{was_verified} = 0;
- # $c->headers_out->{"Location"} = $c->ce->{cosign_logout_script};
- # $c->send_http_header;
- # return;
- $self->{redirect} = $c->ce->{cosign_logout_script};
- }
-}
-
-1;
diff --git a/lib/WeBWorK/Authen/LTIAdvanced.pm b/lib/WeBWorK/Authen/LTIAdvanced.pm
index 0bd4706b3a..0df122c8f7 100644
--- a/lib/WeBWorK/Authen/LTIAdvanced.pm
+++ b/lib/WeBWorK/Authen/LTIAdvanced.pm
@@ -71,7 +71,7 @@ sub request_has_data_for_this_verification_module {
|| !(defined $c->param("oauth_nonce"))
|| !(defined $c->param("oauth_timestamp")))
{
- debug("LTIAdvanced returning that it has insufficent data");
+ debug("LTIAdvanced returning that it has insufficient data");
return (0);
} else {
debug("LTIAdvanced returning that it has sufficient data");
@@ -117,10 +117,9 @@ sub get_credentials {
# Determine the WW user_id to use, if possible
if (!$ce->{LTI}{v1p1}{preferred_source_of_username}) {
- warn
- "LTI is not properly configured (no preferred_source_of_username). Please contact your instructor or system administrator.";
- $self->{error} = $c->maketext(
- "There was an error during the login process. Please speak to your instructor or system administrator.");
+ $self->{error} = $c->maketext("There was an error during the login process. "
+ . "Please speak to your instructor or system administrator.");
+ warn "LTI is not properly configured (no preferred_source_of_username).\n" if $ce->{debug_lti_parameters};
debug("No preferred_source_of_username in "
. $c->ce->{'courseName'}
. " so LTIAdvanced::get_credentials is returning a 0\n");
@@ -228,8 +227,8 @@ sub get_credentials {
warn "================================\n";
}
if (!defined($self->{user_id})) {
- croak
- "LTIAdvanced was unable to create a username from the data provided with the current settings. Set \$debug_lti_parameters=1 in authen_LTI.conf to debug";
+ croak "LTIAdvanced was unable to create a username from the data provided with the current settings. "
+ . "Set \$debug_lti_parameters=1 in authen_LTI.conf to debug";
}
$self->{login_type} = "normal";
@@ -237,8 +236,9 @@ sub get_credentials {
debug("LTIAdvanced::get_credentials is returning a 1\n");
return 1;
}
- warn
- "LTI is not properly configured (failed to set user_id from preferred_source_of_username or fallback_source_of_username). Please contact your instructor or system administrator.";
+ warn "LTI is not properly configured (failed to set user_id from preferred_source_of_username or "
+ . "fallback_source_of_username).\n"
+ if $ce->{debug_lti_parameters};
$self->{error} = $c->maketext(
"There was an error during the login process. Please speak to your instructor or system administrator.");
debug("LTIAdvanced::get_credentials is returning a 0\n");
@@ -262,9 +262,9 @@ sub check_user {
return 0;
}
- my $User = $db->getUser($user_id);
+ $self->{user} = $db->getUser($user_id);
- if (!$User) {
+ if (!$self->{user}) {
my %options;
$options{ $ce->{LTI}{v1p1}{preferred_source_of_username} } = 1
if ($ce->{LTI}{v1p1}{preferred_source_of_username});
@@ -285,9 +285,8 @@ sub check_user {
foreach my $key (keys(%options), ($use_lis_person_sourcedid_options ? @lis_person_sourcedid_options : ())) {
if (defined($c->param($key))) {
- debug(
- "User |$user_id| is unknown but may be an new user from an LSM via LTI. Saw a value for $key About to return a 1"
- );
+ debug("User |$user_id| is unknown but may be a new user from an LMS via LTI. "
+ . "Saw a value for $key About to return a 1");
return 1; #This may be a new user coming in from a LMS via LTI.
}
}
@@ -298,14 +297,14 @@ sub check_user {
return 0;
}
- unless ($ce->status_abbrev_has_behavior($User->status, "allow_course_access")) {
- $self->{log_error} .= "LOGIN FAILED $user_id - course access denied";
+ unless ($ce->status_abbrev_has_behavior($self->{user}->status, "allow_course_access")) {
+ $self->{log_error} .= "$user_id - course access denied";
$self->{error} = $c->maketext("Authentication failed. Please speak to your instructor.");
return 0;
}
unless ($authz->hasPermissions($user_id, "login")) {
- $self->{log_error} .= "LOGIN FAILED $user_id - no permission to login";
+ $self->{log_error} .= "$user_id - no permission to login";
$self->{error} = $c->maketext("Authentication failed. Please speak to your instructor.");
return 0;
}
@@ -353,19 +352,15 @@ sub authenticate {
debug("LTIAdvanced::authenticate called for user |$user|");
debug "ref(c) = |" . ref($c) . "|";
- my $ce = $c->ce;
- my $db = $c->db;
- my $courseName = $c->ce->{'courseName'};
+ my $ce = $c->ce;
# Check nonce to see whether request is legitimate
debug("Nonce = |" . $self->{oauth_nonce} . "|");
my $nonce = WeBWorK::Authen::LTIAdvanced::Nonce->new($c, $self->{oauth_nonce}, $self->{oauth_timestamp});
- if (!($nonce->ok)) {
- $self->{error} .= $c->maketext(
- "There was an error during the login process. Please speak to your instructor or system administrator if this recurs."
- );
+ if (!$nonce->ok) {
debug("Failed to verify nonce");
- return 0;
+ return $c->maketext("There was an error during the login process. "
+ . "Please speak to your instructor or system administrator if this recurs.");
}
debug("c->param(oauth_signature) = |" . $c->param("oauth_signature") . "|");
@@ -418,24 +413,21 @@ sub authenticate {
debug("construction of Net::OAuth object failed: $@");
debug("eval failed: ", $@, " ");
- $self->{error} .= $c->maketext(
- "There was an error during the login process. Please speak to your instructor or system administrator.");
$self->{log_error} .= "Construction of OAuth request record failed";
- return 0;
+ return $c->maketext(
+ "There was an error during the login process. Please speak to your instructor or system administrator.");
}
if (!$request->verify && !$altrequest->verify) {
- debug("LTIAdvanced::authenticate request-> verify failed");
- debug("OAuth verification Failed ");
+ debug("LTIAdvanced::authenticate request->verify failed");
+ debug("OAuth verification Failed");
- $self->{error} .= $c->maketext(
- "There was an error during the login process. Please speak to your instructor or system administrator.");
- $self->{log_error} .=
- "OAuth verification failed. Check the Consumer Secret and that the URL in the LMS exactly matches the WeBWorK URL.";
+ $self->{log_error} .= "OAuth verification failed. "
+ . "Check the Consumer Secret and that the URL in the LMS exactly matches the WeBWorK URL.";
if ($ce->{debug_lti_parameters}) {
- warn(
- "OAuth verification failed. Check the Consumer Secret and that the URL in the LMS exactly matches the WeBWorK URL as defined in site.conf. E.G. Check that if you have https in the LMS url then you have https in \$server_root_url in site.conf"
- );
+ warn("OAuth verification failed. Check the Consumer Secret and that the URL in the LMS exactly "
+ . "matches the WeBWorK URL as defined in site.conf. E.G. Check that if you have https in the "
+ . "LMS url then you have https in \$server_root_url in site.conf");
}
return 0;
} else {
@@ -443,44 +435,34 @@ sub authenticate {
my $userID = $self->{user_id};
- # Indentation of the internal blocks below was modified to follow
- # the WW coding standard; however, the leading indentation of the
- # if/elsif/closing '}' was kept as in the original code for now.
- # Thus the apparenly overlarge indentation below.
- if (!$db->existsUser($userID)) { # New User. Create User record
+ if (!$self->{user}) { # New User. Create User record
if ($ce->{block_lti_create_user}) {
-# We don't yet have the next string in the PO/POT files - so the next line is disabled.
-# $c->maketext("Account creation is currently disabled in this course. Please speak to your instructor or system administrator.");
$self->{log_error} .=
"Account creation blocked by block_lti_create_user setting. Did not create user $userID.";
- if ($ce->{debug_lti_parameters}) {
- warn(
- "Account creation is currently disabled in this course. Please speak to your instructor or system administrator."
- );
- }
- return 0;
+ warn "Account creation is currently disabled in this course. "
+ . "Please speak to your instructor or system administrator."
+ if $ce->{debug_lti_parameters};
+ return $c->maketext("Account creation is currently disabled in this course. "
+ . "Please speak to your instructor or system administrator.");
} else {
# Attempt to create the user, and warn if that fails.
- unless ($self->create_user()) {
- $c->maketext(
- "There was an error during the login process. Please speak to your instructor or system administrator."
- );
+ unless ($self->create_user) {
$self->{log_error} .= "Failed to create user $userID.";
- if ($ce->{debug_lti_parameters}) {
- warn("Failed to create user $userID.");
- }
+ warn "Failed to create user $userID.\n" if $ce->{debug_lti_parameters};
+ return $c->maketext('Unable to create a WeBWorK user. '
+ . 'Please speak to your instructor or system administrator.');
}
}
} elsif ($ce->{LMSManageUserData}) {
- $self->{initial_login} = 1
- ; # Set here so login gets logged, even for accounts which maybe_update_user() would not modify or when it fails to update
- # Existing user. Possibly modify demographic information and permission level.
+ # Set here so login gets logged, even for accounts which maybe_update_user()
+ # would not modify or when it fails to update.
+ $self->{initial_login} = 1;
+
+ # Existing user. Possibly modify demographic information and permission level.
unless ($self->maybe_update_user()) {
- # Do not fail the login if data update failed
- # FIXME - In the future we would like the message below (and other warn messages in this file) to be sent via maketext.
- warn(
- "The system failed to update some of your account information. Please speak to your instructor or system administrator."
- );
+ # Do not fail the login if data update failed
+ warn("The system failed to update some of your account information. "
+ . "Please speak to your instructor or system administrator.");
}
} else {
# Set here so login gets logged when $ce->{LMSManageUserData} is false
@@ -501,9 +483,8 @@ sub authenticate {
}
debug("LTIAdvanced is returning a failed authentication");
- $self->{error} = $c->maketext(
+ return $c->maketext(
"There was an error during the login process. Please speak to your instructor or system administrator.");
- return (0);
}
# create a new user trying to log in
@@ -515,32 +496,35 @@ sub create_user {
my $db = $c->db;
my $courseName = $c->ce->{'courseName'};
- ############################################################
# Determine the roles defined for this user by the LTI request
# and assign a permission level on that basis.
- ############################################################
+
my $LTIrolesString = $c->param("roles");
my @LTIroles = split /,/, $LTIrolesString;
- #remove the urn string if its present
+ # Remove the urn string if its present.
s/^urn:lti:.*:ims\/lis\/// for @LTIroles;
if ($ce->{debug_lti_parameters}) {
warn "The adjusted LTI roles defined for this user are: \n--",
- join("\n--", @LTIroles), "\n",
+ join("\n-- ", @LTIroles), "\n",
"Any initial ^urn:lti:.*:ims/lis/ segments have been stripped off.\n",
"The user will be assigned the highest role defined for them\n",
"========================\n";
}
- my $nr = scalar(@LTIroles);
- if (!defined($ce->{userRoles}->{ $ce->{LTI}{v1p1}{LMSrolesToWeBWorKroles}->{ $LTIroles[0] } })) {
- croak("Cannot find a WeBWorK role that corresponds to the LMS role of " . $LTIroles[0] . ".");
+ if (!defined $ce->{LTI}{v1p1}{LMSrolesToWeBWorKroles}{ $LTIroles[0] }
+ || !defined $ce->{userRoles}{ $ce->{LTI}{v1p1}{LMSrolesToWeBWorKroles}{ $LTIroles[0] } })
+ {
+ $self->{log_error} = "Cannot find a WeBWorK role that corresponds to the LMS role of $LTIroles[0].";
+ warn "Cannot find a WeBWorK role that corresponds to the LMS role of $LTIroles[0].\n"
+ if $ce->{debug_lti_parameters};
+ return 0;
}
- my $LTI_webwork_permissionLevel = $ce->{userRoles}->{ $ce->{LTI}{v1p1}{LMSrolesToWeBWorKroles}->{ $LTIroles[0] } };
- if ($nr > 1) {
- for (my $j = 1; $j < $nr; $j++) {
+ my $LTI_webwork_permissionLevel = $ce->{userRoles}{ $ce->{LTI}{v1p1}{LMSrolesToWeBWorKroles}{ $LTIroles[0] } };
+ if (@LTIroles > 1) {
+ for (my $j = 1; $j < @LTIroles; $j++) {
my $wwRole = $ce->{LTI}{v1p1}{LMSrolesToWeBWorKroles}->{ $LTIroles[$j] };
next unless defined $wwRole;
if ($LTI_webwork_permissionLevel < $ce->{userRoles}->{$wwRole}) {
@@ -549,20 +533,26 @@ sub create_user {
}
}
- ####### End defining roles and $LTI_webwork_permissionLevel#######
+ # End defining roles and $LTI_webwork_permissionLevel
warn "New user: $userID -- requested permission level is $LTI_webwork_permissionLevel."
- if ($ce->{debug_lti_parameters});
+ if $ce->{debug_lti_parameters};
+
+ # Don't create a user that does not have permission to login.
+ if ($LTI_webwork_permissionLevel < $ce->{userRoles}{ $ce->{permissionLevels}{login} }) {
+ $self->{log_error} .= "$userID - no permission to login";
+ return 0;
+ }
- # We dont create users with too high of a permission level
- # for security reasons.
+ # We dont create users with too high of a permission level for security reasons.
if ($LTI_webwork_permissionLevel > $ce->{userRoles}->{ $ce->{LTIAccountCreationCutoff} }) {
$self->{log_error} .=
- "userID: $userID -- Unknown instructor attempting to log in via LTI. Instructor accounts must be created manually";
- croak $c->maketext(
- "The instructor account with user id [_1] does not exist. Please create the account manually via WeBWorK.",
- $userID
- );
+ "The instructor account with user id $userID does not exist. "
+ . 'Instructor accounts must be created manually.';
+ warn "The instructor account with user id $userID does not exist. "
+ . "Instructor accounts must be created manually.\n"
+ if $ce->{debug_lti_parameters};
+ return 0;
}
my $newUser = $db->newUser();
@@ -584,6 +574,7 @@ sub create_user {
}
$db->addUser($newUser);
+ $self->{user} = $newUser;
$self->write_log_entry("New user $userID added via LTIAdvanced login");
# Assign permssion level
@@ -649,7 +640,6 @@ sub maybe_update_user {
my $db = $c->db;
my $courseName = $c->ce->{'courseName'};
- my $user = $db->getUser($userID);
my $permissionLevel = $db->getPermissionLevel($userID);
# We don't alter records of users with too high a permission
if (defined($permissionLevel->permission)
@@ -684,10 +674,10 @@ sub maybe_update_user {
my $change_made = 0;
for my $element (@elements) {
- if ($user->$element ne $tempUser->$element) {
+ if ($self->{user}->$element ne $tempUser->$element) {
$change_made = 1;
warn "WeBWorK User has $element: "
- . $user->$element
+ . $self->{user}->$element
. " but LMS user has $element "
. $tempUser->$element . "\n"
if ($ce->{debug_lti_parameters});
diff --git a/lib/WeBWorK/Authen/LTIAdvanced/SubmitGrade.pm b/lib/WeBWorK/Authen/LTIAdvanced/SubmitGrade.pm
index 26aa71071d..5b175d0c74 100644
--- a/lib/WeBWorK/Authen/LTIAdvanced/SubmitGrade.pm
+++ b/lib/WeBWorK/Authen/LTIAdvanced/SubmitGrade.pm
@@ -298,7 +298,7 @@ EOS
}
# Blackboard seems to return this when there is no prior grade.
- # See: https://webwork.maa.org/moodle/mod/forum/discuss.php?d=5002
+ # See: https://forums.openwebwork.org/mod/forum/discuss.php?d=5002
$priorScore = '' if $priorScore eq 'success';
# Do not update the score if there is no significant change. Note that the cases where the webwork score
diff --git a/lib/WeBWorK/Authen/LTIAdvantage.pm b/lib/WeBWorK/Authen/LTIAdvantage.pm
index c2a7351481..8be03a7c7a 100644
--- a/lib/WeBWorK/Authen/LTIAdvantage.pm
+++ b/lib/WeBWorK/Authen/LTIAdvantage.pm
@@ -34,7 +34,7 @@ sub request_has_data_for_this_verification_module ($self) {
return 1;
}
- debug('LTIAdvantage returning that it has insufficent data');
+ debug('LTIAdvantage returning that it has insufficient data');
return 0;
}
@@ -141,7 +141,8 @@ sub get_credentials ($self) {
}
# Fallback if necessary
- if (!defined $self->{user_id}
+ if ($ce->{LTI}{v1p3}{fallback_source_of_username}
+ && !defined $self->{user_id}
&& (my $user_id = $extract_claim->($ce->{LTI}{v1p3}{fallback_source_of_username})))
{
$user_id_source = $ce->{LTI}{v1p3}{fallback_source_of_username};
@@ -235,21 +236,21 @@ sub check_user ($self) {
return 0;
}
- my $User = $db->getUser($user_id);
+ $self->{user} = $db->getUser($user_id);
- if (!$User) {
- debug("User |$user_id| is unknown but may be an new user from an LMS via LTI.");
+ if (!$self->{user}) {
+ debug("User |$user_id| is unknown but may be a new user from an LMS via LTI.");
return 1;
}
- unless ($ce->status_abbrev_has_behavior($User->status, 'allow_course_access')) {
- $self->{log_error} .= "LOGIN FAILED $user_id - course access denied";
+ unless ($ce->status_abbrev_has_behavior($self->{user}->status, 'allow_course_access')) {
+ $self->{log_error} .= "$user_id - course access denied";
$self->{error} = $c->maketext('Authentication failed. Please speak to your instructor.');
return 0;
}
unless ($authz->hasPermissions($user_id, 'login')) {
- $self->{log_error} .= "LOGIN FAILED $user_id - no permission to login";
+ $self->{log_error} .= "$user_id - no permission to login";
$self->{error} = $c->maketext('Authentication failed. Please speak to your instructor.');
return 0;
}
@@ -290,11 +291,9 @@ sub authenticate ($self) {
# The actual authentication for this module has already been done. This just creates and updates users if needed.
- my $ce = $c->ce;
- my $db = $c->db;
- my $courseName = $c->ce->{courseName};
+ my $ce = $c->ce;
- if (!$db->existsUser($self->{user_id})) {
+ if (!$self->{user}) {
# New User. Create User record.
if ($ce->{block_lti_create_user}) {
$self->{log_error} .=
@@ -303,12 +302,15 @@ sub authenticate ($self) {
warn $c->maketext('Account creation is currently disabled in this course. '
. 'Please speak to your instructor or system administrator.') . "\n";
}
- return 0;
+ return $c->maketext("Account creation is currently disabled in this course. "
+ . "Please speak to your instructor or system administrator.");
} else {
# Attempt to create the user, and warn if that fails.
unless ($self->create_user) {
$self->{log_error} .= "Failed to create user $self->{user_id}.";
- warn "Failed to create user $self->{user_id}.\n" if ($ce->{debug_lti_parameters});
+ warn "Failed to create user $self->{user_id}.\n" if $ce->{debug_lti_parameters};
+ return $c->maketext('Unable to create a WeBWorK user. '
+ . 'Please speak to your instructor or system administrator.');
}
}
} elsif ($ce->{LMSManageUserData}) {
@@ -360,7 +362,10 @@ sub create_user ($self) {
}
if (!defined($ce->{userRoles}{ $ce->{LTI}{v1p3}{LMSrolesToWeBWorKroles}{ $LTIroles[0] } })) {
- die "Cannot find a WeBWorK role that corresponds to the LMS role of $LTIroles[0].\n";
+ $self->{log_error} = "Cannot find a WeBWorK role that corresponds to the LMS role of $LTIroles[0].\n";
+ warn "Cannot find a WeBWorK role that corresponds to the LMS role of $LTIroles[0].\n"
+ if $ce->{debug_lti_parameters};
+ return 0;
}
my $LTI_webwork_permissionLevel = $ce->{userRoles}{ $ce->{LTI}{v1p3}{LMSrolesToWeBWorKroles}{ $LTIroles[0] } };
@@ -378,11 +383,19 @@ sub create_user ($self) {
# We dont create users with too high of a permission level for security reasons.
if ($LTI_webwork_permissionLevel > $ce->{userRoles}{ $ce->{LTIAccountCreationCutoff} }) {
- die $c->maketext(
- 'The instructor account with user id [_1] does not exist. '
- . 'Instructor accounts must be created manually.',
- $userID
- ) . "\n";
+ $self->{log_error} =
+ "The instructor account with user id $userID does not exist. "
+ . 'Instructor accounts must be created manually.';
+ warn "The instructor account with user id $userID does not exist. "
+ . "Instructor accounts must be created manually.\n"
+ if $ce->{debug_lti_parameters};
+ return 0;
+ }
+
+ # Don't create a user that does not have permission to login.
+ if ($LTI_webwork_permissionLevel < $ce->{userRoles}{ $ce->{permissionLevels}{login} }) {
+ $self->{log_error} .= "$userID - no permission to login";
+ return 0;
}
my $newUser = $db->newUser;
@@ -401,6 +414,7 @@ sub create_user ($self) {
$ce->{LTI}{v1p3}{modify_user}($self, $newUser) if ref($ce->{LTI}{v1p3}{modify_user}) eq 'CODE';
$db->addUser($newUser);
+ $self->{user} = $newUser;
$self->write_log_entry("New user $userID added via LTIAdvantange login");
# Set permission level.
@@ -466,7 +480,6 @@ sub maybe_update_user ($self) {
my $userID = $self->{user_id};
my $courseName = $ce->{courseName};
- my $user = $db->getUser($userID);
my $permissionLevel = $db->getPermissionLevel($userID);
# We don't alter records of users with too high a permission.
@@ -492,10 +505,10 @@ sub maybe_update_user ($self) {
my $change_made = 0;
for my $element (qw(last_name first_name email_address status section recitation student_id)) {
- if ($user->$element ne $tempUser->$element) {
+ if ($self->{user}->$element ne $tempUser->$element) {
$change_made = 1;
warn "WeBWorK User has $element: "
- . $user->$element
+ . $self->{user}->$element
. " but LMS user has $element "
. $tempUser->$element . "\n"
if ($ce->{debug_lti_parameters});
diff --git a/lib/WeBWorK/Authen/Moodle.pm b/lib/WeBWorK/Authen/Moodle.pm
deleted file mode 100644
index 58eccc42cd..0000000000
--- a/lib/WeBWorK/Authen/Moodle.pm
+++ /dev/null
@@ -1,253 +0,0 @@
-package WeBWorK::Authen::Moodle;
-use base qw/WeBWorK::Authen/;
-
-=head1 NAME
-
-WeBWorK::Authen::Moodle - Allow moodle cookies to be used for WeBWorK authentication.
-
-=cut
-
-=for comment
-
-TODO
-
-* Modules that modify data that's being taken from moodle should check for "alternative URLs" in the
-CE that can point back to the moodle installation. operations include: change password, change user
-data, change permission level, add user, delete user. Run this for a rough estimate:
- pcregrep -r '\$db->(add|put)(User|Password|PermissionLevel)\b' lib
-
-=cut
-
-use strict;
-use warnings;
-use Digest::MD5 qw/md5_hex/;
-use WeBWorK::Debug;
-use Date::Parse; # for moodle 1.7 date parsing
-
-sub new {
- my $self = shift->SUPER::new(@_);
-
- $self->init_mdl_session;
-
- return $self;
-}
-
-# call superclass get_credentials. if no credentials were found, look for a moodle cooke.
-# if a moodle cookie is found, a new webwork session is created and the session key is used.
-# (this is similar to what happens when a guest user is selected.)
-sub get_credentials {
- my $self = shift;
- my $c = $self->{c};
-
- my $super_result = $self->SUPER::get_credentials;
- if ($super_result) {
- debug("Superclass's get_credentials found credentials. Using them.\n");
- return $super_result;
- }
-
- my ($moodle_user_id, $moodle_expiration_time) = $self->fetch_moodle_session;
-#debug("fetch_moodle_session returned: moodle_user_id='$moodle_user_id' moodle_expiration_time='$moodle_expiration_time'.\n"); # causes errors when undefined
-
- if (defined $moodle_user_id and defined $moodle_expiration_time and time <= $moodle_expiration_time) {
- my $newKey = $self->create_session($moodle_user_id);
- debug("Unexpired moodle session found. Created new WeBWorK session with newKey='$newKey'.\n");
-
- $self->{user_id} = $moodle_user_id;
- $self->{session_key} = $newKey;
- $self->{login_type} = "normal";
- $self->{credential_source} = "moodle";
- return 1;
- } else {
- debug("No moodle session found or moodle session expired. No credentials to be had.\n");
- warn(
- "No moodle session found or moodle sessioin expired. If this happens repeatedly and you are constantly being asked
- to log back in ask your moodle admin to check that the Moodle item:
- Server -> Session Handling -> dbsessions (Use database for session information) has been checked."
- );
- }
-
- return 0;
-}
-
-# extend the moodle session if authentication succeeded
-sub site_fixup {
- my $self = shift;
-
- if ($self->was_verified) {
- debug("User was verified, updating moodle session.\n");
- $self->update_moodle_session;
- }
-}
-
-# we assume that the database is set up to use the moodle password table, which uses MD5 passwords.
-# this is overridden to accommodate this.
-sub checkPassword {
- my ($self, $userID, $possibleClearPassword) = @_;
- my $db = $self->{c}->db;
-
- debug("Moodle module is doing the password checking.\n");
-
- my $Password = $db->getPassword($userID); # checked
- if (defined $Password) {
- # check against Moodle password database
- my $possibleMD5Password = md5_hex($possibleClearPassword);
- debug("Hashed password from supplied cleartext: '$possibleMD5Password'.\n");
- debug("Hashed password from Password record: '", $Password->password, "'.\n");
- if ($possibleMD5Password eq $Password->password) {
- $self->write_log_entry("AUTH MDL: password accepted");
- return 1;
- } else {
- if ($self->can("site_checkPassword")) {
- $self->write_log_entry("AUTH MDL: password rejected, deferring to site_checkPassword");
- return $self->site_checkPassword($userID, $possibleClearPassword);
- } else {
- $self->write_log_entry("AUTH MDL: password rejected");
- return 0;
- }
- }
-
- }
-}
-
-sub check_session {
- my ($self, $user_id, $session_key, $update_timestamp) = @_;
-
- my ($sessionExists, $keyMatches, $timestampValid) =
- $self->SUPER::check_session($user_id, $session_key, $update_timestamp);
- debug(
- "SUPER::check_session returned: sessionExists='",
- $sessionExists, "' keyMatches='",
- $keyMatches, "' timestampValid='",
- $timestampValid, "'"
- );
-
- if ($update_timestamp and $sessionExists and $keyMatches and not $timestampValid) {
- debug("special case: webwork key matches an expired session (check for a unexpired moodle session)");
- my ($moodle_user_id, $moodle_expiration_time) = $self->fetch_moodle_session;
- debug(
- "fetch_moodle_session returned: moodle_user_id='$moodle_user_id' moodle_expiration_time='$moodle_expiration_time'.\n"
- );
- if (defined $moodle_user_id
- and $moodle_user_id eq $user_id
- and defined $moodle_expiration_time
- and time <= $moodle_expiration_time)
- {
- $self->{session_key} = $self->create_session($moodle_user_id);
- $timestampValid = 1;
- }
- }
-
- return $sessionExists, $keyMatches, $timestampValid;
-}
-
-################################################################################
-
-use DBI;
-use PHP::Serialization qw/unserialize/;
-
-use constant DEFAULT_EXPIRY => 7200;
-
-sub init_mdl_session {
- my $self = shift;
-
- # version-specific stuff
- $self->{moodle17} = $self->{c}->ce->{authen}{moodle_options}{moodle17};
- $self->{sql_session_table} = $self->{moodle17} ? "sessions2" : "sessions";
- $self->{sql_data_field} = $self->{moodle17} ? "sessdata" : "data";
-
- $self->{mdl_dbh} = DBI->connect_cached(
- $self->{c}->ce->{authen}{moodle_options}{dsn},
- $self->{c}->ce->{authen}{moodle_options}{username},
- $self->{c}->ce->{authen}{moodle_options}{password},
- {
- PrintError => 0,
- RaiseError => 1,
- },
- );
- die $DBI::errstr unless defined $self->{mdl_dbh};
-}
-
-sub fetch_moodle_session {
- # fetches the basic information from the moodle session.
- # returns the user name and expiration time of the moodle session
- # Note that we don't worry about the user being in this course at this point.
- # That is taken care of in Schema::Moodle::User.
- my ($self) = @_;
- my $c = $self->{c};
- my $db = $c->db;
-
- my $cookie = $c->req->cookie('MoodleSession');
- return unless $cookie;
-
- my $session_table = $self->prefix_table($self->{sql_session_table});
- my $data_field = $self->{sql_data_field};
- my $stmt = "SELECT `expiry`,`$data_field` FROM `$session_table` WHERE `sesskey`=?";
- my @bind_vals = $cookie->value;
-
- my $sth = $self->{mdl_dbh}->prepare_cached($stmt, undef, 3); # 3: see DBI docs
- $sth->execute(@bind_vals);
- my $row = $sth->fetchrow_arrayref;
- $sth->finish;
- return unless defined $row;
-
- my ($expires, $data_string) = @$row;
-
- # Moodle 1.7 stores expiry as a DATETIME, but WeBWorK wants a UNIX timestamp.
- $expires = str2time($expires) if $self->{moodle17};
-
- my $data = unserialize_session($data_string);
- my $username = $data->{"USER"}{"username"};
-
- return $username, $expires;
-}
-
-sub update_moodle_session {
- # extend the timeout of the current moodle session, if one exists.
- my ($self) = @_;
- my $c = $self->{c};
- my $db = $c->db;
-
- my $cookie = $c->req->cookie('MoodleSession');
- return unless $cookie;
-
- my $config_table = $self->prefix_table("config");
- my $value = "IFNULL((SELECT `value` FROM `$config_table` WHERE `name`=?),?)+?";
-
- # Moodle 1.7 stores expiry as a DATETIME, but WeBWorK supplies a UNIX timestamp.
- $value = "FROM_UNIXTIME($value)" if $self->{moodle17};
-
- my $session_table = $self->prefix_table($self->{sql_session_table});
- my $stmt = "UPDATE `$session_table` SET `expiry`=$value WHERE `sesskey`=?";
- my @bind_vals = ("sessiontimeout", DEFAULT_EXPIRY, time, $cookie->value);
-
- my $sth = $self->{mdl_dbh}->prepare_cached($stmt, undef, 3); # 3: see DBI docs
- my $result = $sth->execute(@bind_vals);
- $sth->finish;
-
- return defined $result;
-}
-
-sub prefix_table {
- my ($self, $base) = @_;
- if (defined $self->{c}->ce->{authen}{moodle_options}{table_prefix}) {
- return $self->{c}->ce->{authen}{moodle_options}{table_prefix} . $base;
- } else {
- return $base;
- }
-}
-
-sub unserialize_session {
- my $serialData = shift;
- # first, url decode:
- $serialData =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
- # then, split it up by |, it's some ADODB sillyness
- my @serialArray = split(/(\w+)\|/, $serialData);
- my %variables;
- # finally, actually deserialize it:
- for (my $i = 1; $i < $#serialArray; $i += 2) {
- $variables{ $serialArray[$i] } = unserialize($serialArray[ $i + 1 ]);
- }
- return \%variables;
-}
-
-1;
diff --git a/lib/WeBWorK/Authen/Shibboleth.pm b/lib/WeBWorK/Authen/Shibboleth.pm
index 498220f598..61e6d7f027 100644
--- a/lib/WeBWorK/Authen/Shibboleth.pm
+++ b/lib/WeBWorK/Authen/Shibboleth.pm
@@ -11,7 +11,7 @@ To use this module copy C to
C, and uncomment the line in C
that reads C.
-Refer to the L
+Refer to the L
documentation on the WeBWorK wiki and the instructions in the comments of the
C file.
diff --git a/lib/WeBWorK/Authz.pm b/lib/WeBWorK/Authz.pm
index a238f3fb67..784d97c8fb 100644
--- a/lib/WeBWorK/Authz.pm
+++ b/lib/WeBWorK/Authz.pm
@@ -46,7 +46,7 @@ use warnings;
use Carp qw/croak/;
use WeBWorK::Utils::DateTime qw(before);
-use WeBWorK::Utils::Sets qw(is_restricted);
+use WeBWorK::Utils::Sets qw(restricted_set_message);
use WeBWorK::Authen::Proctor;
use Net::IP;
use Scalar::Util qw(weaken);
@@ -414,34 +414,35 @@ sub checkSet {
# Cache the set for future use as needed. This should probably be more sophisticated than this.
$self->{merged_set} = $set;
+ # Save restricted set messages to show to instructors if they exist.
+ my $canViewUnopened = $self->hasPermissions($userName, "view_unopened_sets");
+ my @restrictedSetMessages;
+
# Now we know that the set is assigned to the appropriate user.
- # Check to see if the user is trying to access a set that is not open.
- if (
- before($set->open_date)
- && !$self->hasPermissions($userName, "view_unopened_sets")
- && !(
- defined $set->assignment_type
- && $set->assignment_type =~ /gateway/
- && $node_name eq 'problem_list'
- && $db->countSetVersions($effectiveUserName, $set->set_id)
- )
- )
- {
- return $c->maketext("Requested set '[_1]' is not yet open.", $setName);
- }
+ # $c->{viewSetCheck} is used to configure what is shown on ProblemSet page.
# Check to make sure that the set is visible, and that the user is allowed to view hidden sets.
my $visible = $set && $set->visible ne '0' && $set->visible ne '1' ? 1 : $set->visible;
if (!$visible && !$self->hasPermissions($userName, "view_hidden_sets")) {
+ $c->{viewSetCheck} = 'hidden';
+ return $c->maketext("Requested set '[_1]' is not available.", $setName);
+ }
+
+ # Check to see if the user is trying to access a set that is not open.
+ if (before($set->open_date) && !$canViewUnopened) {
+ $c->{viewSetCheck} = 'not-open';
return $c->maketext("Requested set '[_1]' is not available yet.", $setName);
}
# Check to see if conditional release conditions have been met.
- if ($ce->{options}{enableConditionalRelease}
- && is_restricted($db, $set, $effectiveUserName)
- && !$self->hasPermissions($userName, "view_unopened_sets"))
- {
- return $c->maketext("The prerequisite conditions have not been met for set '[_1]'.", $setName);
+ my $conditional_msg = restricted_set_message($c, $set, 'conditional');
+ if ($conditional_msg) {
+ if ($canViewUnopened) {
+ push(@restrictedSetMessages, $conditional_msg);
+ } else {
+ $c->{viewSetCheck} = 'restricted';
+ return $conditional_msg;
+ }
}
# Check to be sure that gateways are being sent to the correct content generator.
@@ -474,25 +475,27 @@ sub checkSet {
# Check for ip restrictions.
my $badIP = $self->invalidIPAddress($set);
- return $badIP if $badIP;
-
- # If LTI grade passback is enabled and set to 'homework' mode then we need to make sure that there is a sourcedid
- # for this set before students access it.
- my $LTIGradeMode = $ce->{LTIGradeMode} // '';
-
- if ($LTIGradeMode eq 'homework' && !$self->hasPermissions($userName, "view_unopened_sets")) {
- my $LMS =
- $ce->{LTI}{ $ce->{LTIVersion} }{LMS_url}
- ? $c->link_to($ce->{LTI}{ $ce->{LTIVersion} }{LMS_name} => $ce->{LTI}{ $ce->{LTIVersion} }{LMS_url})
- : $ce->{LTI}{ $ce->{LTIVersion} }{LMS_name};
- return $c->b($c->maketext(
- 'You must use your Learning Management System ([_1]) to access this set. '
- . 'Try logging in to the Learning Management System and visiting the set from there.',
- $LMS
- ))
- unless $set->lis_source_did || ($ce->{LTIVersion} eq 'v1p3' && $ce->{LTI}{v1p3}{ignoreMissingSourcedID});
+ if ($badIP) {
+ if ($self->hasPermissions($userName, 'view_ip_restricted_sets')) {
+ push(@restrictedSetMessages, $badIP);
+ } else {
+ $c->{viewSetCheck} = 'restricted';
+ return $badIP;
+ }
+ }
+
+ # Check for lis_source_did if LTI grade passback is 'homework'.
+ my $lti_msg = restricted_set_message($c, $set, 'lti');
+ if ($lti_msg) {
+ if ($canViewUnopened) {
+ push(@restrictedSetMessages, $lti_msg);
+ } else {
+ $c->{viewSetCheck} = 'restricted';
+ return $lti_msg;
+ }
}
+ $c->{restrictedSetMessages} = \@restrictedSetMessages if @restrictedSetMessages;
return 0;
}
@@ -514,8 +517,7 @@ sub invalidIPAddress {
return 0
if (!defined($set->restrict_ip)
|| $set->restrict_ip eq ''
- || $set->restrict_ip eq 'No'
- || $self->hasPermissions($userName, 'view_ip_restricted_sets'));
+ || $set->restrict_ip eq 'No');
my $clientIP = new Net::IP($c->tx->remote_address);
@@ -530,7 +532,9 @@ sub invalidIPAddress {
# if there are no addresses in the locations, return an error that
# says this
return $c->maketext(
- "Client ip address [_1] is not allowed to work this assignment, because the assignment has ip address restrictions and there are no allowed locations associated with the restriction. Contact your professor to have this problem resolved.",
+ 'Client ip address [_1] is not allowed to work this assignment, because the assignment has ip address '
+ . 'restrictions and there are no allowed locations associated with the restriction. Contact your '
+ . 'professor to have this problem resolved.',
$clientIP->ip()
) if (!@restrictAddresses);
@@ -552,17 +556,13 @@ sub invalidIPAddress {
# this is slightly complicated by having to check relax_restrict_ip
my $badIP = '';
if ($restrictType eq 'RestrictTo' && !$inRestrict) {
- $badIP =
- "Client ip address "
- . $clientIP->ip()
- . " is not in the list of addresses from "
- . "which this assignment may be worked.";
+ $badIP = $c->maketext(
+ 'Client ip address [_1] is not in the list of addresses from which this assignment may be worked.',
+ $clientIP->ip());
} elsif ($restrictType eq 'DenyFrom' && $inRestrict) {
- $badIP =
- "Client ip address "
- . $clientIP->ip()
- . " is in the list of addresses from "
- . "which this assignment may not be worked.";
+ $badIP = $c->maketext(
+ 'Client ip address [_1] is in the list of addresses from which this assignment may not be worked.',
+ $clientIP->ip());
} else {
return 0;
}
diff --git a/lib/WeBWorK/ConfigValues.pm b/lib/WeBWorK/ConfigValues.pm
index 411be4426c..8b4423f437 100644
--- a/lib/WeBWorK/ConfigValues.pm
+++ b/lib/WeBWorK/ConfigValues.pm
@@ -213,7 +213,7 @@ sub getConfigValues ($ce) {
{
var => 'hardcopyThemePGEditor',
doc => x('Hardcopy Theme for Problem Editor'),
- doc2 => x('Choose a layout/styling theme for PDF hardcopy production from the Prooblem Editor.'),
+ doc2 => x('Choose a layout/styling theme for PDF hardcopy production from the Problem Editor.'),
values => [qw(empty.xml)],
type => 'popuplist',
hashVar => '{hardcopyThemePGEditor}'
@@ -278,8 +278,8 @@ sub getConfigValues ($ce) {
"Achievements are a way to gamify WeBWorK. In parallel to a student's regular scores on "
. 'assignments, they earn "achievement points" for (a) answering an exercise correctly, and '
. '(b) earning badges. Badges can be for tasks like earning 100% on three assignments, '
- . 'answering five questions correclty on the first attempt, etc. As students earn achivement '
- . 'points, they can "level up" as well. An instructor can manage Achievents using the '
+ . 'answering five questions correctly on the first attempt, etc. As students earn achievement '
+ . 'points, they can "level up" as well. An instructor can manage Achievements using the '
. 'Achievements Manager tool.'
),
type => 'boolean'
@@ -321,6 +321,21 @@ sub getConfigValues ($ce) {
),
type => 'boolean'
},
+ {
+ var => 'achievementExtensionFactor',
+ doc => x('Multiplicative time factor for extension achievement items'),
+ doc2 => x(
+ 'This sets the time factor which affects the extension time for the extension achievement items. '
+ . 'This factor is multiplied by the base extension time of 24 hours to determine the '
+ . 'extension time. In effect, this is the number of days of the extension. If this factor is '
+ . 'not a whole number, then the resulting time is rounded to the nearest full hour, and '
+ . 'cannot be less than 1 hour. This affects the extension achievement items "ExtendDueDate", '
+ . '"ExtendDueDateGW", "ExtendReducedDate", "ReducedCred", "ResurrectGW", and "ResurrectHW". '
+ . 'The two super extensions, "SuperExtendDueDate" and "SuperExtendedReducedDate", will have '
+ . 'double the time.'
+ ),
+ type => 'number'
+ },
{
var => 'achievementExcludeSet',
doc => x('List of sets excluded from achievements'),
@@ -545,8 +560,8 @@ sub getConfigValues ($ce) {
var => 'permissionLevels{report_bugs}',
doc => x('Can report bugs'),
doc2 => x(
- 'Users with at least this permission level get a link in the left panel for reporting bugs to the '
- . 'bug tracking system at bugs.webwork.maa.org.'
+ 'Users with at least this permission level get a link in the left panel for reporting issues at '
+ . 'github.com/openwebwork/webwork2.'
),
type => 'permission'
},
@@ -776,12 +791,12 @@ sub getConfigValues ($ce) {
},
{
var => 'problemGraderScore',
- doc => x('Method to enter problem scores in the single problem manual grader'),
+ doc => x('Method to enter problem scores in the manual problem graders'),
doc2 => x(
- 'This configures if the single problem manual grader has inputs to enter problem scores as '
- . 'a percent, a point value, or both. Note, the problem score is always saved as a '
- . 'percent, so when using a point value, the problem score will be rounded to the '
- . 'nearest whole percent.'
+ 'This configures if the manual problem grader or single problem grader has inputs to enter '
+ . 'problem scores as a percent, a point value, or both. Note, the problem score is always '
+ . 'saved as a percent, so when using a point value, the problem score will be rounded to '
+ . 'the nearest whole percent.'
),
values => [qw(Percent Point Both)],
type => 'popuplist'
@@ -796,7 +811,7 @@ sub getConfigValues ($ce) {
. 'the "Check Answers" button. Or if that button is also not present, it will activate '
. 'the "Preview My Answers" button. A third option is "conservative". In this case, the '
. 'enter key behaves like "preview" when the "Submit" button is available and there are '
- . 'only finitely many attempts allowed. Otherise the enter key behaves like "submit". '
+ . 'only finitely many attempts allowed. Otherwise the enter key behaves like "submit". '
. 'Note that this is only affects homework problem pages, not test/quiz pages, and not '
. 'instructor pages like the PG Editor and the Library Browser.'
),
@@ -820,7 +835,8 @@ sub getConfigValues ($ce) {
doc2 => x(
'A "Reveal" button must be clicked to make a correct answer visible any time that correct '
. 'answers for a problem are shown. Note that this is always the case for instructors '
- . 'before answers are available to students, and in "Show Me Another" problems.'
+ . 'before answers are available to students (except when the problem grader is open), and '
+ . 'in "Show Me Another" problems.'
),
type => 'boolean'
}
@@ -829,13 +845,15 @@ sub getConfigValues ($ce) {
x('E-Mail'),
{
var => 'mail{feedbackSubjectFormat}',
- doc => x('Format for the subject line in feedback emails'),
+ doc => x('Format for the subject of feedback emails'),
doc2 => x(
- 'When students click the Email Instructor button to send feedback, WeBWorK fills in the '
- . 'subject line. Here you can set the subject line. In it, you can have various bits of '
- . 'information filled in with the following escape sequences.
%c = course ID '
+ 'When students click the Email Instructor button to send feedback, WeBWorK fills in '
+ . 'the subject line. Here you can set the subject line. In it, you can have various bits of '
+ . 'information filled in with the following escape sequences.
%c = course ID '
. '%u = user ID %s = set ID %p = problem ID %x = section '
- . '%r = recitation %% = literal percent sign '
+ . '%r = recitation %% = literal percent sign If content is between '
+ . "a brace pair, like '{ rec:%r}', then it will only be included in the subject line if all "
+ . 'substitutions within the double brace pair are defined and nonempty.'
),
width => 45,
type => 'text'
@@ -1152,13 +1170,15 @@ sub getConfigValues ($ce) {
};
# Get the list of theme folders in the theme directory.
- my $themes = eval { path($ce->{webworkDirs}{themes})->list({ dir => 1 })->map('basename')->sort; };
+ my $themes = eval {
+ path($ce->{webworkDirs}{themes})->list({ dir => 1 })->grep(sub {-d})->map('basename')->sort;
+ };
die "can't opendir $ce->{webworkDirs}{themes}: $@" if $@;
# Get the list of all site hardcopy theme files.
my $hardcopyThemesSite =
eval { path($ce->{webworkDirs}{hardcopyThemes})->list->grep(qr/\.xml$/)->map('basename')->sort };
- die "Unabled to list files in $ce->{webworkDirs}{hardcopyThemes}: $@" if $@;
+ die "Unable to list files in $ce->{webworkDirs}{hardcopyThemes}: $@" if $@;
my $hardcopyThemesCourse = eval {
path($ce->{courseDirs}{hardcopyThemes})->list->grep(sub {
diff --git a/lib/WeBWorK/ContentGenerator.pm b/lib/WeBWorK/ContentGenerator.pm
index 3e069f2a03..2bd7756686 100644
--- a/lib/WeBWorK/ContentGenerator.pm
+++ b/lib/WeBWorK/ContentGenerator.pm
@@ -32,6 +32,7 @@ use MIME::Base64;
use Scalar::Util qw(weaken);
use HTML::Entities;
use Encode;
+use Mojo::JSON qw(encode_json true);
use WeBWorK::File::Scoring qw(parse_scoring_file);
use WeBWorK::Localize;
@@ -94,7 +95,7 @@ The method content() is called to send the page content to client.
async sub go ($c) {
my $ce = $c->ce;
- # If grades are being passed back to the lti, then peroidically update all of the
+ # If grades are being passed back to the lti, then periodically update all of the
# grades because things can get out of sync if instructors add or modify sets.
massUpdate($c) if $c->stash('courseID') && ref($c->db) && $ce->{LTIGradeMode};
@@ -109,8 +110,6 @@ async sub go ($c) {
my $tx = $c->render_later->tx;
- $c->stash->{footerWidthClass} = $c->can('info') ? 'col-md-8' : 'col-12';
-
if ($c->can('pre_header_initialize')) {
my $pre_header_initialize = $c->pre_header_initialize;
await $pre_header_initialize
@@ -132,6 +131,8 @@ async sub go ($c) {
await $initialize if ref $initialize eq 'Future' || ref $initialize eq 'Mojo::Promise';
}
+ $c->stash->{footerWidthClass} //= $c->can('info') ? 'col-md-8' : 'col-12';
+
$c->content;
# All content generator modules must have rendered at this point unless there was an error in which case an error
@@ -646,7 +647,7 @@ sub timestamp ($c) {
Defined in this package.
Print any messages (error or non-error) resulting from the last form submission.
-This could be used to give Sucess and Failure messages after an action is performed by a module.
+This could be used to give Success and Failure messages after an action is performed by a module.
The implementation in this package outputs the value of the field
$c->{status_message}, if it is present.
@@ -682,20 +683,19 @@ sub page_title ($c) {
return route_title($c, $c->current_route, 1);
}
-=item webwork_url
-
-Defined in this package.
-
-Outputs the $webwork_url defined in site.conf, unless $webwork_url is equal to
-"/", in which case this outputs the empty string.
+=item webwork_js_config
-This is used to set a value in a global webworkConfig javascript variable,
-that can be accessed in javascript files.
+Outputs the webwork2 JavaScript configuration. This configuration can be
+accessed by JavaScript files to obtain various webwork2 settings.
=cut
-sub webwork_url ($c) {
- return $c->location;
+sub webwork_js_config ($c, $showMathJaxErrors = 0) {
+ return encode_json({
+ webwork_url => $c->location,
+ mathJaxBSColorSchemeUrl => getAssetURL($c->ce, 'js/MathJaxConfig/bs-color-scheme.js'),
+ $showMathJaxErrors ? (showMathJaxErrors => true) : ()
+ });
}
=item warnings()
@@ -813,7 +813,7 @@ there are pg errors.
=cut
sub have_warnings ($c) {
- return $c->stash('warnings') || $c->{pgerrors};
+ return $c->stash('warnings');
}
=item exists_theme_file
@@ -1122,7 +1122,7 @@ object from which the base path will be taken. %options can consist of:
Can be either a reference to an array or a reference to a hash.
-If it is a reference to a hash, it maps parmaeter names to values. These
+If it is a reference to a hash, it maps parameter names to values. These
parameters will be included in the generated link. If a value is an arrayref,
the values of the array referenced will be used. If a value is undefined, the
value from the current request will be used.
@@ -1220,8 +1220,8 @@ Used to display a generic warning message at the top of the page
=cut
sub warningMessage ($c) {
- return $c->maketext('Warning : There may be something wrong with this question. '
- . 'Please inform your instructor including the warning messages below.');
+ return $c->maketext('Warning : WeBWorK has encountered warnings while processing your request. '
+ . 'See the warning messages below for details.');
}
=item $string = formatDateTime($date_time, $format_string, $timezone, $locale)
@@ -1238,7 +1238,7 @@ If C<$locale> is provided, the string returned will be in the format of that
locale. If C<$locale> is not provided, Perl defaults to using C.
Note that the defaults for C<$timezone> and C<$locale> should almost never be
-overriden when this method is used.
+overridden when this method is used.
=cut
diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm
index 02f1467544..ff67697c4b 100644
--- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm
+++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm
@@ -294,8 +294,6 @@ sub do_add_course ($c) {
my $ce2 = WeBWorK::CourseEnvironment->new({ courseName => $add_courseID });
- my %courseOptions;
-
my @users;
# copy users from current (admin) course if desired
@@ -376,9 +374,6 @@ sub do_add_course ($c) {
}
}
- push @{ $courseOptions{PRINT_FILE_NAMES_FOR} },
- map { $_->[0]->user_id } grep { $_->[2]->permission >= $ce->{userRoles}{professor} } @users;
-
# Include any optional arguments, including a template course and the course title and course institution.
my %optional_arguments;
if ($copy_from_course ne '') {
@@ -395,15 +390,7 @@ sub do_add_course ($c) {
my $output = $c->c;
- eval {
- addCourse(
- courseID => $add_courseID,
- ce => $ce2,
- courseOptions => \%courseOptions,
- users => \@users,
- %optional_arguments,
- );
- };
+ eval { addCourse(courseID => $add_courseID, ce => $ce2, users => \@users, %optional_arguments,); };
if ($@) {
my $error = $@;
push(
@@ -707,9 +694,10 @@ sub do_rename_course ($c) {
eval {
renameCourse(
- courseID => $rename_oldCourseID,
- ce => WeBWorK::CourseEnvironment->new({ courseName => $rename_oldCourseID }),
- newCourseID => $rename_newCourseID,
+ courseID => $rename_oldCourseID,
+ ce => WeBWorK::CourseEnvironment->new({ courseName => $rename_oldCourseID }),
+ newCourseID => $rename_newCourseID,
+ updateLTICourseMap => 1,
%optional_arguments
);
};
diff --git a/lib/WeBWorK/ContentGenerator/Feedback.pm b/lib/WeBWorK/ContentGenerator/Feedback.pm
index 336e5fc127..335caff765 100644
--- a/lib/WeBWorK/ContentGenerator/Feedback.pm
+++ b/lib/WeBWorK/ContentGenerator/Feedback.pm
@@ -12,7 +12,7 @@ use Email::Stuffer;
use Try::Tiny;
use WeBWorK::Upload;
-use WeBWorK::Utils qw(createEmailSenderTransportSMTP fetchEmailRecipients);
+use WeBWorK::Utils qw(createEmailSenderTransportSMTP fetchEmailRecipients formatEmailSubject);
# request paramaters used
#
@@ -108,18 +108,15 @@ sub initialize ($c) {
}
}
- my %subject_map = (
- 'c' => $courseID,
- 'u' => $user ? $user->user_id : undef,
- 's' => $set ? $set->set_id : undef,
- 'p' => $problem ? $problem->problem_id : undef,
- 'x' => $user ? $user->section : undef,
- 'r' => $user ? $user->recitation : undef,
- '%' => '%',
+ my $subject = formatEmailSubject(
+ $ce->{mail}{feedbackSubjectFormat},
+ $courseID,
+ $user ? $user->user_id : '',
+ $set ? $set->set_id : '',
+ $problem ? $problem->problem_id : '',
+ $user ? $user->section : '',
+ $user ? $user->recitation : ''
);
- my $chars = join('', keys %subject_map);
- my $subject = $ce->{mail}{feedbackSubjectFormat} || 'WeBWorK question from %c: %u set %s/prob %p';
- $subject =~ s/%([$chars])/defined $subject_map{$1} ? $subject_map{$1} : ''/eg;
my %data = (
user => $user,
diff --git a/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm b/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm
index 7cec7a5b6d..a939a494f6 100644
--- a/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm
+++ b/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm
@@ -282,14 +282,15 @@ sub get_instructor_comment ($c, $problem) {
async sub pre_header_initialize ($c) {
# Make sure these are defined for the templates.
- $c->stash->{problems} = [];
- $c->stash->{pg_results} = [];
- $c->stash->{startProb} = 0;
- $c->stash->{endProb} = 0;
- $c->stash->{numPages} = 0;
- $c->stash->{pageNumber} = 0;
- $c->stash->{problem_numbers} = [];
- $c->stash->{probOrder} = [];
+ $c->stash->{problems} = [];
+ $c->stash->{pg_results} = [];
+ $c->stash->{startProb} = 0;
+ $c->stash->{endProb} = 0;
+ $c->stash->{numPages} = 0;
+ $c->stash->{pageNumber} = 0;
+ $c->stash->{problem_numbers} = [];
+ $c->stash->{probOrder} = [];
+ $c->stash->{haveProblemWarnings} = 0;
# If authz->checkSet has failed, then this set is invalid. No need to proceeded.
return if $c->{invalidSet};
@@ -510,7 +511,6 @@ async sub pre_header_initialize ($c) {
my $maxAttemptsPerVersion = $tmplSet->attempts_per_version || 0;
my $timeInterval = $tmplSet->time_interval || 0;
my $versionsPerInterval = $tmplSet->versions_per_interval || 0;
- my $timeLimit = $tmplSet->version_time_limit || 0;
# What happens if someone didn't set one of these? Perhaps this can happen if we're handed a malformed set, where
# the values in the database are null.
@@ -596,7 +596,8 @@ async sub pre_header_initialize ($c) {
$set = $db->getMergedSetVersion($effectiveUserID, $setID, $setVersionNumber);
$set->visible(1);
- # If there is a cap on problems per page, make sure that is respected in case something higher snuck in.
+ # If there is a cap on problems per page, make sure that is respected
+ # in case something higher snuck in.
if (
$ce->{test}{maxProblemsPerPage}
&& ($tmplSet->problems_per_page == 0
@@ -611,6 +612,8 @@ async sub pre_header_initialize ($c) {
# Convert the floating point value from Time::HiRes to an integer for use below. Truncate toward 0.
my $timeNowInt = int($c->submitTime);
+ my $timeLimit = ($tmplSet->version_time_limit || 0) * $effectiveUser->accommodation_time_factor;
+
# Set up creation time, and open and due dates.
my $ansOffset = $set->answer_date - $set->due_date;
$set->version_creation_time($timeNowInt);
@@ -633,7 +636,7 @@ async sub pre_header_initialize ($c) {
$cleanSet->due_date($set->due_date);
$cleanSet->answer_date($set->answer_date);
$cleanSet->version_last_attempt_time($set->version_last_attempt_time);
- $cleanSet->version_time_limit($set->version_time_limit);
+ $cleanSet->version_time_limit($set->version_time_limit * $effectiveUser->accommodation_time_factor);
$cleanSet->attempts_per_version($set->attempts_per_version);
$cleanSet->assignment_type($set->assignment_type);
$db->putSetVersion($cleanSet);
@@ -788,14 +791,11 @@ async sub pre_header_initialize ($c) {
return;
}
- # Unset the showProblemGrader parameter if the "Hide Problem Grader" button was clicked.
- $c->param(showProblemGrader => undef) if $c->param('hideProblemGrader');
-
# What does the user want to do?
my %want = (
showOldAnswers => $user->showOldAnswers ne '' ? $user->showOldAnswers : $ce->{pg}{options}{showOldAnswers},
showCorrectAnswers => 1,
- showProblemGrader => $c->param('showProblemGrader') || 0,
+ showProblemGrader => $userID ne $effectiveUserID,
showHints => 0, # Hints are not yet implemented in gateway quzzes.
showSolutions => 1,
recordAnswers => $c->{submitAnswers} && !$authz->hasPermissions($userID, 'avoid_recording_answers'),
@@ -895,9 +895,6 @@ async sub pre_header_initialize ($c) {
my @problems;
my @pg_results;
- # pg errors are stored here.
- $c->{errors} = [];
-
# Process the problems as needed.
my @mergedProblems;
if ($setID eq 'Undefined_Set') {
@@ -1321,7 +1318,8 @@ async sub pre_header_initialize ($c) {
} elsif ($endTime > $set->due_date) {
$c->{exceededAllowedTime} = 1;
}
- $c->{elapsedTime} = int(($endTime - $set->open_date) / 0.6 + 0.5) / 100;
+ $c->{elapsedTime} = int(($endTime - $set->open_date) / 0.6 + 0.5) / 100;
+ $c->{completedTime} = $c->formatDateTime($endTime, $ce->{studentDateDisplayFormat});
# Get the number of attempts and number of remaining attempts.
$c->{attemptNumber} =
@@ -1354,7 +1352,7 @@ sub path ($c, $args) {
$courseName => $navigation_allowed ? $c->url_for('set_list') : '',
$setID eq 'Undefined_Set'
|| $c->{invalidSet} || $c->{actingCreationError} || $c->stash->{actingConfirmation}
- ? ($setID => '')
+ ? ($setID =~ /^(.+),(v\d+)$/ ? ($1 => $c->url_for('problem_list', setID => $1), $2 => '') : ($setID => ''))
: (
$c->{set}->set_id => $c->url_for('problem_list', setID => $c->{set}->set_id),
'v' . $c->{set}->version_id => ''
@@ -1370,7 +1368,7 @@ sub nav ($c, $args) {
return '' if $c->{invalidSet} || $c->{actingCreationError} || $c->stash->{actingConfirmation};
# Set up and display a student navigation for those that have permission to act as a student.
- if ($c->authz->hasPermissions($userID, 'become_student') && $effectiveUserID ne $userID) {
+ if ($c->authz->hasPermissions($userID, 'become_student')) {
my $setID = $c->{set}->set_id;
return '' if $setID eq 'Undefined_Set';
@@ -1378,82 +1376,90 @@ sub nav ($c, $args) {
my $setVersion = $c->{set}->version_id;
# Find all versions of this set that have been taken (excluding those taken by the current user).
- my @users =
- $db->listSetVersionsWhere({ user_id => { not_like => $userID }, set_id => { like => "$setID,v\%" } });
- my @allUserRecords = $db->getUsers(map { $_->[0] } @users);
-
- my $filter = $c->param('studentNavFilter');
-
- # Format the student names for display, and associate the users with the test versions.
- my %filters;
- my @userRecords;
- for (0 .. $#allUserRecords) {
- # Add to the sections and recitations if defined. Also store the first user found in that section or
- # recitation. This user will be switched to when the filter is selected.
- my $section = $allUserRecords[$_]->section;
- $filters{"section:$section"} =
- [ $c->maketext('Filter by section [_1]', $section), $allUserRecords[$_]->user_id, $users[$_][2] ]
- if $section && !$filters{"section:$section"};
- my $recitation = $allUserRecords[$_]->recitation;
- $filters{"recitation:$recitation"} =
- [ $c->maketext('Filter by recitation [_1]', $recitation), $allUserRecords[$_]->user_id, $users[$_][2] ]
- if $recitation && !$filters{"recitation:$recitation"};
-
- # Only keep this user if it satisfies the selected filter if a filter was selected.
- next
- unless !$filter
- || ($filter =~ /^section:(.*)$/ && $allUserRecords[$_]->section eq $1)
- || ($filter =~ /^recitation:(.*)$/ && $allUserRecords[$_]->recitation eq $1);
-
- my $addRecord = $allUserRecords[$_];
- push @userRecords, $addRecord;
-
- $addRecord->{displayName} =
- ($addRecord->last_name || $addRecord->first_name
- ? $addRecord->last_name . ', ' . $addRecord->first_name
- : $addRecord->user_id);
- $addRecord->{setVersion} = $users[$_][2];
- }
+ my @userVersions =
+ $db->listSetVersionsWhere({ user_id => { '!=' => $userID }, set_id => { like => "$setID,v\%" } });
+ my %users = map { $_->[0] => 1 } @userVersions;
+ my @allUserRecords =
+ grep { $users{ $_->{user_id} } }
+ $c->db->getUsersWhere({ -and => { user_id => { not_like => 'set_id:%' } }, user_id => { '!=' => $userID } },
+ [qw/last_name first_name user_id/]);
+
+ if (@allUserRecords) {
+ my $filter = $c->param('studentNavFilter');
+
+ # Format the student names for display, and associate the users with the test versions.
+ my %filters;
+ my @userRecords;
+ for (0 .. $#allUserRecords) {
+ # Add to the sections and recitations if defined. Also store the first user found in that section or
+ # recitation. This user will be switched to when the filter is selected.
+ my $section = $allUserRecords[$_]->section;
+ $filters{"section:$section"} = [
+ $c->maketext('Filter by section [_1]', $section), $allUserRecords[$_]->user_id,
+ $userVersions[$_][2]
+ ]
+ if $section && !$filters{"section:$section"};
+ my $recitation = $allUserRecords[$_]->recitation;
+ $filters{"recitation:$recitation"} = [
+ $c->maketext('Filter by recitation [_1]', $recitation), $allUserRecords[$_]->user_id,
+ $userVersions[$_][2]
+ ]
+ if $recitation && !$filters{"recitation:$recitation"};
+
+ # Only keep this user if it satisfies the selected filter if a filter was selected.
+ next
+ unless !$filter
+ || ($filter =~ /^section:(.*)$/ && $allUserRecords[$_]->section eq $1)
+ || ($filter =~ /^recitation:(.*)$/ && $allUserRecords[$_]->recitation eq $1);
+
+ my $addRecord = $allUserRecords[$_];
+ push @userRecords, $addRecord;
+
+ $addRecord->{displayName} =
+ ($addRecord->last_name || $addRecord->first_name
+ ? $addRecord->last_name . ', ' . $addRecord->first_name
+ : $addRecord->user_id);
+ $addRecord->{setVersion} = $userVersions[$_][2];
+ }
- # Sort by last name, then first name, then user_id, then set version.
- @userRecords = sort {
- lc($a->last_name) cmp lc($b->last_name)
- || lc($a->first_name) cmp lc($b->first_name)
- || lc($a->user_id) cmp lc($b->user_id)
- || lc($a->{setVersion}) <=> lc($b->{setVersion})
- } @userRecords;
-
- # Find the previous, current, and next test.
- my $currentTestIndex = 0;
- for (0 .. $#userRecords) {
- if ($userRecords[$_]->user_id eq $effectiveUserID && $userRecords[$_]->{setVersion} == $setVersion) {
- $currentTestIndex = $_;
- last;
+ # Sort by last name, then first name, then user_id, then set version.
+ @userRecords = sort {
+ lc($a->last_name) cmp lc($b->last_name)
+ || lc($a->first_name) cmp lc($b->first_name)
+ || lc($a->user_id) cmp lc($b->user_id)
+ || lc($a->{setVersion}) <=> lc($b->{setVersion})
+ } @userRecords;
+
+ # Find the previous, current, and next test.
+ my $currentTestIndex = 0;
+ for (0 .. $#userRecords) {
+ if ($userRecords[$_]->user_id eq $effectiveUserID && $userRecords[$_]->{setVersion} == $setVersion) {
+ $currentTestIndex = $_;
+ last;
+ }
}
+ my $prevTest = $currentTestIndex > 0 ? $userRecords[ $currentTestIndex - 1 ] : 0;
+ my $nextTest = $currentTestIndex < $#userRecords ? $userRecords[ $currentTestIndex + 1 ] : 0;
+
+ # Mark the current test.
+ $userRecords[$currentTestIndex]{currentTest} = 1;
+
+ # Show the student nav.
+ return $c->include(
+ 'ContentGenerator/GatewayQuiz/nav',
+ userID => $userID,
+ eUserID => $effectiveUserID,
+ userRecords => \@userRecords,
+ setVersion => $setVersion,
+ prevTest => $prevTest,
+ nextTest => $nextTest,
+ currentTestIndex => $currentTestIndex,
+ filters => \%filters,
+ filter => $filter
+ );
}
- my $prevTest = $currentTestIndex > 0 ? $userRecords[ $currentTestIndex - 1 ] : 0;
- my $nextTest = $currentTestIndex < $#userRecords ? $userRecords[ $currentTestIndex + 1 ] : 0;
-
- # Mark the current test.
- $userRecords[$currentTestIndex]{currentTest} = 1;
-
- # Show the student nav.
- return $c->include(
- 'ContentGenerator/GatewayQuiz/nav',
- userRecords => \@userRecords,
- setVersion => $setVersion,
- prevTest => $prevTest,
- nextTest => $nextTest,
- currentTestIndex => $currentTestIndex,
- filters => \%filters,
- filter => $filter
- );
}
-}
-
-sub warningMessage ($c) {
- return $c->maketext('Warning : There may be something wrong with a question in this test. '
- . 'Please inform your instructor including the warning messages below.');
+ return '';
}
# Evaluation utility
@@ -1502,10 +1508,9 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem)
&& $c->can_showCorrectAnswersForAll($set, $c->{problem}, $c->{tmplSet})),
showMessages => !$showOnlyCorrectAnswers,
showCorrectAnswers => (
- $c->{will}{showProblemGrader} ? 2
- : !$c->{previewAnswers} && $c->can_showCorrectAnswersForAll($set, $c->{problem}, $c->{tmplSet})
+ !$c->{previewAnswers} && $c->can_showCorrectAnswersForAll($set, $c->{problem}, $c->{tmplSet})
? ($c->ce->{pg}{options}{correctRevealBtnAlways} ? 1 : 2)
- : !$c->{previewAnswers} && $c->{will}{showCorrectAnswers} ? 1
+ : $c->{will}{showProblemGrader} || (!$c->{previewAnswers} && $c->{will}{showCorrectAnswers}) ? 1
: 0
),
debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID}),
@@ -1515,27 +1520,14 @@ async sub getProblemHTML ($c, $effectiveUser, $set, $formFields, $mergedProblem)
},
);
- # Warnings in the renderPG subprocess will not be caught by the global warning handler of this process.
- # So rewarn them and let the global warning handler take care of it.
- warn $pg->{warnings} if $pg->{warnings};
-
- if ($pg->{flags}{error_flag}) {
- push @{ $c->{errors} },
- {
- set => $set->set_id . ',v' . $set->version_id,
- problem => $mergedProblem->problem_id,
- message => $pg->{errors},
- context => $pg->{body_text},
- };
- $pg->{body_text} = undef;
- }
-
# If the user can check answers and either this is not an answer submission or the problem_data form
# parameter was previously set, then set or update the problem_data form parameter.
$c->param('problem_data_' . $mergedProblem->problem_id => encode_json($pg->{PERSISTENCE_HASH} || '{}'))
if $c->{can}{checkAnswers}
&& (!$c->{submitAnswers} || defined $c->param('problem_data_' . $mergedProblem->problem_id));
+ $c->stash->{haveProblemWarnings} = 1 if $pg->{warnings} || @{ $pg->{warning_messages} // [] };
+
return $pg;
}
diff --git a/lib/WeBWorK/ContentGenerator/Hardcopy.pm b/lib/WeBWorK/ContentGenerator/Hardcopy.pm
index fdd35ed8ca..8182737946 100644
--- a/lib/WeBWorK/ContentGenerator/Hardcopy.pm
+++ b/lib/WeBWorK/ContentGenerator/Hardcopy.pm
@@ -10,6 +10,7 @@ problem sets.
use File::Temp qw/tempdir/;
use Mojo::File;
+use Mojo::Util qw(xml_escape);
use String::ShellQuote;
use Archive::Zip qw(:ERROR_CODES);
use XML::LibXML;
@@ -117,10 +118,7 @@ async sub pre_header_initialize ($c) {
die 'Parameter "user" not defined -- this should never happen' unless defined $userID;
# Check to see if the user is authorized to view source file paths.
- $c->{can_show_source_file} =
- ($db->getPermissionLevel($userID)->permission >=
- $ce->{pg}{specialPGEnvironmentVars}{PRINT_FILE_NAMES_PERMISSION_LEVEL})
- || (grep { $_ eq $userID } @{ $ce->{pg}{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} });
+ $c->{can_show_source_file} = $authz->hasPermissions($userID, 'print_path_to_problem');
if ($generate_hardcopy) {
my $validation_failed = 0;
@@ -130,14 +128,16 @@ async sub pre_header_initialize ($c) {
# Make sure the format is valid.
unless (grep { $_ eq $hardcopy_format } keys %HC_FORMATS) {
- $c->addbadmessage(qq{"$hardcopy_format" is not a valid hardcopy format.});
+ $c->addbadmessage($c->maketext('"[_1]" is not a valid hardcopy format.', xml_escape($hardcopy_format)));
$validation_failed = 1;
}
# Make sure we are allowed to generate hardcopy in this format.
unless ($authz->hasPermissions($userID, "download_hardcopy_format_$hardcopy_format")) {
- $c->addbadmessage(
- $c->maketext('You do not have permission to generate hardcopy in [_1] format.', $hardcopy_format));
+ $c->addbadmessage($c->maketext(
+ 'You do not have permission to generate hardcopy in [_1] format.',
+ xml_escape($hardcopy_format)
+ ));
$validation_failed = 1;
}
@@ -284,13 +284,14 @@ async sub pre_header_initialize ($c) {
my $fullFilePath = "$ce->{webworkDirs}{tmp}/$courseID/hardcopy/$userID/$tempFile";
unless (-e $fullFilePath) {
- $c->addbadmessage($c->maketext('The requested file "[_1]" does not exist on the server.', $tempFile));
+ $c->addbadmessage(
+ $c->maketext('The requested file "[_1]" does not exist on the server.', xml_escape($tempFile)));
return;
}
unless ($baseName =~ /\.$userID\./ || $authz->hasPermissions($userID, 'download_hardcopy_multiuser')) {
$c->addbadmessage($c->maketext('You do not have permission to access the requested file "[_1]".'),
- $tempFile);
+ xml_escape($tempFile));
return;
}
@@ -670,7 +671,7 @@ sub generate_hardcopy_tex ($c, $temp_dir_path, $final_file_basename) {
);
}
}
- for (qw{pg.sty PGML.tex CAPA.tex}) {
+ for (qw{pg.sty PGML.tex}) {
eval { Mojo::File->new("$ce->{pg}{directories}{assetsTex}/$_")->copy_to($bundle_path) };
if ($@) {
$c->add_error(
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/AchievementNotificationEditor.pm b/lib/WeBWorK/ContentGenerator/Instructor/AchievementNotificationEditor.pm
index 906845efa4..353e3f06ea 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/AchievementNotificationEditor.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/AchievementNotificationEditor.pm
@@ -212,7 +212,7 @@ sub save_as_handler ($c) {
));
} else {
$c->addbadmessage($c->maketext(
- 'Unable to change the achievement notification template for achivement [_1]. Unknown error.',
+ 'Unable to change the achievement notification template for achievement [_1]. Unknown error.',
$achievementName
));
}
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/Index.pm b/lib/WeBWorK/ContentGenerator/Instructor/Index.pm
index 2ac5e5e94e..ac0f00f2be 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/Index.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/Index.pm
@@ -80,13 +80,6 @@ sub pre_header_initialize ($c) {
} else {
push @error, E_ONE_SET;
}
- } elsif (defined $c->param('user_stats')) {
- if ($nusers == 1) {
- $route = 'instructor_user_statistics';
- $args{userID} = $firstUserID;
- } else {
- push @error, E_ONE_USER;
- }
} elsif (defined $c->param('set_stats')) {
if ($nsets == 1) {
$route = 'instructor_set_statistics';
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm b/lib/WeBWorK/ContentGenerator/Instructor/PGProblemEditor.pm
index c7736812de..54aa863de6 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
@@ -108,25 +108,25 @@ not exist. The path to the actual file being edited is stored in inputFilePath.
use Mojo::File;
use XML::LibXML;
-use WeBWorK::Utils qw(not_blank x max);
-use WeBWorK::Utils::Files qw(surePathToFile readFile path_is_subdir);
-use WeBWorK::Utils::Instructor qw(assignProblemToAllSetUsers addProblemToSet);
-use WeBWorK::Utils::JITAR qw(seq_to_jitar_id jitar_id_to_seq);
-use WeBWorK::Utils::Sets qw(format_set_name_display);
-use SampleProblemParser qw(getSampleProblemCode generateMetadata);
+use WeBWorK::Utils qw(not_blank x max);
+use WeBWorK::Utils::Files qw(surePathToFile readFile path_is_subdir);
+use WeBWorK::Utils::Instructor qw(assignProblemToAllSetUsers addProblemToSet);
+use WeBWorK::Utils::JITAR qw(seq_to_jitar_id jitar_id_to_seq);
+use WeBWorK::Utils::Sets qw(format_set_name_display);
+use WeBWorK::PG::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';
@@ -704,14 +704,15 @@ sub saveFileChanges ($c, $outputFilePath, $backup = 0) {
}
# If the file is being saved as a new file in a new location, and the file is accompanied by auxiliary files
- # transfer them as well. If the file is a pg file, then assume there are auxiliary files. Copy all files not
- # ending in .pg from the original directory to the new one.
- if ($c->{action} eq 'save_as' && $outputFilePath =~ /\.pg/) {
+ # transfer them as well. If the option 'copyAuxFiles' is set and the file is a pg file, then assume there are
+ # auxiliary files. Copy all files not ending in .pg from the original directory to the new one.
+ if ($c->{action} eq 'save_as' && $c->param('copyAuxFiles') && $outputFilePath =~ /\.pg/) {
my $sourceDirectory = Mojo::File->new(($c->{sourceFilePath} || '') =~ s|/[^/]+\.pg$||r);
my $outputDirectory = Mojo::File->new($outputFilePath =~ s|/[^/]+\.pg$||r);
# Only perform the copy if the output directory is an actual new location.
if ($sourceDirectory ne $outputDirectory && -d $sourceDirectory) {
+ my $filesCopied = 0;
for my $file (@{ $sourceDirectory->list }) {
# The .pg file being edited has already been transferred. Ignore any others in the directory.
next if $file =~ /\.pg$/;
@@ -719,13 +720,18 @@ sub saveFileChanges ($c, $outputFilePath, $backup = 0) {
# Only copy regular files that are readable and that have not already been copied.
if (-f $file && -r $file && !-e $toPath) {
eval { $file->copy_to($toPath) };
- $c->addbadmessage($c->maketext('Error copying [_1] to [_2].', $file, $toPath)) if $@;
+ if ($@) {
+ $c->addbadmessage($c->maketext('Error copying [_1] to [_2].', $file, $toPath));
+ } else {
+ $filesCopied = 1;
+ }
}
}
$c->addgoodmessage($c->maketext(
'Copied auxiliary files from [_1] to new location at [_2].',
$sourceDirectory, $outputDirectory
- ));
+ ))
+ if $filesCopied;
}
}
@@ -847,9 +853,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.
@@ -1278,7 +1284,7 @@ sub save_as_handler ($c) {
$new_file_type = $file_type;
} else {
$c->addbadmessage($c->maketext(
- 'Please use radio buttons to choose the method for saving this file. Uknown saveMode: [_1].', $saveMode
+ 'Please use radio buttons to choose the method for saving this file. Unknown saveMode: [_1].', $saveMode
));
return;
}
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/ProblemGrader.pm b/lib/WeBWorK/ContentGenerator/Instructor/ProblemGrader.pm
index 7bf2c9ed78..dae595e356 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/ProblemGrader.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/ProblemGrader.pm
@@ -13,6 +13,7 @@ use HTML::Entities;
use WeBWorK::Utils::JITAR qw(jitar_id_to_seq);
use WeBWorK::Utils::Rendering qw(renderPG);
use WeBWorK::Utils::Sets qw(get_test_problem_position format_set_name_display);
+use WeBWorK::Utils::DateTime qw(before);
async sub initialize ($c) {
my $authz = $c->authz;
@@ -96,6 +97,17 @@ async sub initialize ($c) {
if ($c->param('assignGrades')) {
$c->addgoodmessage($c->maketext('Grades have been saved for all current users.'));
+ # Get all of the merged user sets for this set. These are needed to determine if the problem sub_status also
+ # needs to be set. The sub_status must be set if reduced scoring is not enabled for the course or set or if it
+ # is before the reduced scoring date.
+ my %mergedSets;
+ if ($c->stash->{set}->assignment_type =~ /gateway/) {
+ $mergedSets{ $_->user_id }{ $_->version_id } = $_
+ for $db->getMergedSetVersionsWhere({ set_id => { like => "$setID,v\%" } });
+ } else {
+ %mergedSets = map { $_->user_id => { 0 => $_ } } $db->getMergedSetsWhere({ set_id => $setID });
+ }
+
for my $user (@{ $c->stash->{users} }) {
my $userID = $user->user_id;
for (@{ $user->{data} }) {
@@ -115,9 +127,16 @@ async sub initialize ($c) {
$_->{problem}{flags} =~ s/:needs_grading$//;
if ($c->param("$userID.$versionID.mark_correct")) {
$_->{problem}->status(1);
+ $_->{problem}->sub_status(1);
} elsif (defined $c->param("$userID.$versionID.score")) {
my $newscore = $c->param("$userID.$versionID.score") / 100;
- if ($newscore != $_->{problem}->status) { $_->{problem}->status($newscore); }
+ if ($newscore != $_->{problem}->status) {
+ $_->{problem}->status($newscore);
+ $_->{problem}->sub_status($newscore)
+ if !$ce->{pg}{ansEvalDefaults}{enableReducedScoring}
+ || !$mergedSets{$userID}{$versionID}->enable_reduced_scoring
+ || before($mergedSets{$userID}{$versionID}->reduced_scoring_date);
+ }
}
if ($versionID) { $db->putProblemVersion($_->{problem}); }
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm
index eba2d2aa82..19c2c557aa 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm
@@ -29,20 +29,15 @@ use constant SET_FIELDS => [
];
use constant PROBLEM_FIELDS =>
[qw(source_file value max_attempts showMeAnother showHintsAfter prPeriod att_to_open_children counts_parent_grade)];
-use constant USER_PROBLEM_FIELDS => [qw(problem_seed status num_correct num_incorrect)];
+use constant USER_PROBLEM_FIELDS => [qw(problem_seed status)];
# These constants determine what order those fields should be displayed in.
-use constant HEADER_ORDER => [qw(set_header hardcopy_header)];
-use constant PROBLEM_FIELD_ORDER => [
- qw(problem_seed status value max_attempts showMeAnother showHintsAfter prPeriod attempted last_answer num_correct
- num_incorrect)
-];
-# For gateway sets, don't allow changing max_attempts on a per problem basis.
-use constant GATEWAY_PROBLEM_FIELD_ORDER =>
- [qw(problem_seed status value attempted last_answer num_correct num_incorrect)];
+use constant HEADER_ORDER => [qw(set_header hardcopy_header)];
+use constant PROBLEM_FIELD_ORDER => [qw(problem_seed status value max_attempts showMeAnother showHintsAfter prPeriod)];
+use constant GATEWAY_PROBLEM_FIELD_ORDER => [qw(problem_seed status value)];
use constant JITAR_PROBLEM_FIELD_ORDER => [
qw(problem_seed status value max_attempts showMeAnother showHintsAfter prPeriod att_to_open_children
- counts_parent_grade attempted last_answer num_correct num_incorrect)
+ counts_parent_grade)
];
# Exclude the gateway set fields from the set field order, because they are only displayed for sets that are gateways.
@@ -70,14 +65,28 @@ use constant JITAR_SET_FIELD_ORDER => [qw(restrict_prob_progression email_instru
# [min, max, step] will introduce validation, so should not be used on just any
# input where we expect numbers
# size => "50", # size of the edit box (if any)
-# override => "none", # none, one, any, all - defines for whom this data can/must be overidden
+# override => "all", # none, one, any, all - defines for whom this data can be overidden
# module => "problem_list", # WeBWorK module
# default => 0 # if a field cannot default to undefined/empty what should it default to
-# labels => { # special values can be hashed to display labels
-# 1 => x('Yes'),
-# 0 => x('No'),
+# labels => { # Display labels for type "choose" or type "[min, max, step]".
+# 1 => x('Yes'), # This is required for type "choose", is optional for type "[min, max, step]",
+# 0 => x('No'), # and should never be used for any other type.
# },
+# choices => [ qw(0 1) ] # Order of the labels above. This must be given if labels is.
# convertby => 60, # divide incoming database field values by this, and multiply when saving
+#
+# Note that if "type" is "[min, max, step]" and "labels" is defined, then a select will be shown before the numeric
+# input with the labels as options. The label values must not overlap with the numeric values (i.e., min must be
+# greater than all defined label values), and must be numeric. The labels must also include a "numeric" label. This
+# label will be shown when the number input value is used. It is impomrtant that the choices should not include the
+# special "numeric" value, and that all other choices have numeric values.
+
+# FIXME: The override "none" case needs to be revisited if it is ever used again. It is definitely not implemented
+# correctly, or it is pointless. If override "none" is to mean it can't be changed at all, then why have it? But then
+# again, with the current implementation fields set to override "none" are not shown when editing for more than one
+# user, but are shown when editing for one user (although they still can't be edited). That doesn't make sense. The
+# previous fields that used it were also of type "hidden", and it turns out that type "hidden" and override "none"
+# really means don't use at all, and so listing them was pointless.
use constant BLANKPROBLEM => 'newProblem.pg';
@@ -281,7 +290,9 @@ use constant FIELD_PROPERTIES => {
'This sets a number of minutes for each version of a test, once it is started. Use "0" to indicate no '
. 'time limit. If there is a time limit, then there will be an indication that this is a timed '
. 'test on the main "Assignments" page. Additionally the student will be sent to a confirmation '
- . 'page beefore they can begin.'
+ . 'page before they can begin. Note that the actual time a student will have to complete a timed test '
+ . 'is the product of this time limit and the accommodation time factor set for the student in the '
+ . 'accounts manager.'
)
},
time_limit_cap => {
@@ -479,65 +490,57 @@ use constant FIELD_PROPERTIES => {
)
},
max_attempts => {
- name => x('Max Attempts'),
- type => 'edit',
- size => 6,
- override => 'any',
- default => '-1',
- labels => {
- '-1' => x('Unlimited'),
- },
+ name => x('Max Attempts'),
+ type => [ 0, undef, 1 ],
+ size => 6,
+ override => 'any',
+ default => '-1',
+ choices => [qw(-1)],
+ labels => { '-1' => x('Unlimited'), numeric => x('Limit to') },
help_text => x(
- 'You may cap the number of attempts a student can use for the problem. Use -1 to indicate unlimited attempts.'
+ 'You may cap the number of attempts a student can use for the problem. '
+ . 'Select "Unlimited" to allow an unlimited number of attempts.'
)
},
showMeAnother => {
- name => x('Show Me Another'),
- type => 'edit',
- size => '6',
- override => 'any',
- default => '-2',
- labels => {
- '-1' => x('Never'),
- '-2' => x('Course Default'),
- },
+ name => x('Show Me Another'),
+ type => [ 0, undef, 1 ],
+ size => '6',
+ override => 'any',
+ default => '-2',
+ choices => [qw(-2 -1)],
+ labels => { '-2' => x('Course Default'), '-1' => x('Never'), numeric => x('After number of attempts is') },
help_text => x(
'When a student has more attempts than is specified here they will be able to view another '
- . 'version of this problem. If set to -1 the feature is disabled and if set to -2 '
- . 'the course default is used.'
+ . 'version of this problem. The "Show Me Another" feature is is disabled if "Never" is selected.'
)
},
showHintsAfter => {
- name => x('Show Hints After'),
- type => 'edit',
- size => '6',
- override => 'any',
- default => '-2',
- labels => {
- '-2' => x('Course Default'),
- '-1' => x('Never'),
- },
+ name => x('Show Hints'),
+ type => [ 0, undef, 1 ],
+ size => '6',
+ override => 'any',
+ default => '-2',
+ choices => [qw(-2 -1)],
+ labels => { '-2' => x('Course Default'), '-1' => x('Never'), numeric => x('After number of attempts is') },
help_text => x(
'This specifies the number of attempts before hints are shown to students. '
- . 'The value of -2 uses the default from course configuration. '
- . 'The value of -1 disables hints. '
+ . 'If "Never" is selected, then hints are disabled. '
. 'Note that this will only have an effect if the problem has a hint.'
),
},
prPeriod => {
- name => x('Rerandomize After'),
- type => 'edit',
- size => '6',
- override => 'any',
- default => '-1',
- labels => {
- '-1' => x('Course Default'),
- '0' => x('Never'),
- },
+ name => x('Rerandomize'),
+ type => [ 1, undef, 1 ],
+ size => '6',
+ override => 'any',
+ default => '-1',
+ choices => [qw(-1 0)],
+ labels => { '-1' => x('Course Default'), '0' => x('Never'), numeric => x('After number of attempts is') },
help_text => x(
'This specifies the rerandomization period: the number of attempts before a new version of '
- . 'the problem is generated by changing the Seed value. The value of -1 uses the '
- . 'default from course configuration. The value of 0 disables rerandomization.'
+ . 'the problem is generated by changing the Seed value. '
+ . 'Randomization is disabled if "Never" is selected.'
),
},
problem_seed => {
@@ -561,34 +564,6 @@ use constant FIELD_PROPERTIES => {
. 'to 1 to manually award full credit on this problem.'
)
},
- attempted => {
- name => x('Attempted'),
- type => 'hidden',
- override => 'none',
- choices => [qw(0 1)],
- labels => {
- 1 => x('Yes'),
- 0 => x('No'),
- },
- default => '0',
- },
- last_answer => {
- name => x('Last Answer'),
- type => 'hidden',
- override => 'none',
- },
- num_correct => {
- name => x('Correct'),
- type => 'hidden',
- override => 'none',
- default => '0',
- },
- num_incorrect => {
- name => x('Incorrect'),
- type => 'hidden',
- override => 'none',
- default => '0',
- },
hide_hint => {
name => x('Hide Hints from Students'),
type => 'choose',
@@ -605,19 +580,18 @@ use constant FIELD_PROPERTIES => {
)
},
att_to_open_children => {
- name => x('Attempt Threshold for Children'),
- type => 'edit',
- size => 6,
- override => 'any',
- default => '0',
- labels => {
- '-1' => x('max'),
- },
+ name => x('Attempt Threshold for Children'),
+ type => [ 0, undef, 1 ],
+ size => 6,
+ override => 'any',
+ default => '0',
+ choices => [qw(-1)],
+ labels => { '-1' => x('No Attempts Remaining'), numeric => x('Number incorrect is') },
help_text => x(
'The child problems for this problem will become visible to the student when they either have more '
. 'incorrect attempts than is specified here, or when they run out of attempts, whichever comes '
- . 'first. Use -1 to indicate that child problems should only be available after a student '
- . 'runs out of attempts.'
+ . 'first. Select "No Attempts Remaining" to indicate that child problems should only be available '
+ . 'after a student runs out of attempts.'
),
},
counts_parent_grade => {
@@ -638,13 +612,6 @@ use constant FIELD_PROPERTIES => {
},
};
-use constant FIELD_PROPERTIES_GWQUIZ => {
- max_attempts => {
- type => 'hidden',
- override => 'any',
- }
-};
-
# Create a table of fields for the given parameters, one row for each db field.
# If only the setID is included, it creates a table of set information.
# If the problemID is included, it creates a table of problem information.
@@ -724,13 +691,7 @@ sub fieldTable ($c, $userID, $setID, $problemID, $globalRecord, $userRecord = un
}
for my $field (@fieldOrder) {
- my %properties;
-
- if ($isGWset && defined(FIELD_PROPERTIES_GWQUIZ->{$field})) {
- %properties = %{ FIELD_PROPERTIES_GWQUIZ->{$field} };
- } else {
- %properties = %{ FIELD_PROPERTIES()->{$field} };
- }
+ my %properties = %{ FIELD_PROPERTIES()->{$field} };
# Don't show fields if that option isn't enabled.
if (!$ce->{options}{enableConditionalRelease}
@@ -822,9 +783,8 @@ sub fieldHTML ($c, $userID, $setID, $problemID, $globalRecord, $userRecord, $fie
my %properties = %{ FIELD_PROPERTIES()->{$field} };
if ($field eq 'problems_per_page') {
- if ($c->ce->{test}{maxProblemsPerPage} == 1) {
- $properties{override} = 'none';
- } elsif ($c->ce->{test}{maxProblemsPerPage} > 1) {
+ return '' if $c->ce->{test}{maxProblemsPerPage} == 1;
+ if ($c->ce->{test}{maxProblemsPerPage} > 1) {
my $max = $c->ce->{test}{maxProblemsPerPage};
$properties{type} = [ 1, $max, 1 ];
$properties{help_text} =
@@ -841,10 +801,6 @@ sub fieldHTML ($c, $userID, $setID, $problemID, $globalRecord, $userRecord, $fie
return '' if $properties{override} eq 'none' && !$forOneUser;
return '' if $properties{override} eq 'all' && $forUsers;
- my $edit = $properties{type} eq 'edit' && $properties{override} ne 'none';
- my $number = ref($properties{type}) eq 'ARRAY' && $properties{override} ne 'none';
- my $choose = $properties{type} eq 'choose' && $properties{override} ne 'none';
-
my ($globalValue, $userValue, $blankField) = (undef, undef, '');
if ($field =~ /:/) {
# This allows one "select" to set multiple database fields.
@@ -882,106 +838,141 @@ sub fieldHTML ($c, $userID, $setID, $problemID, $globalRecord, $userRecord, $fie
# Determine if this is a set record or problem record.
my ($recordType, $recordID) = defined $problemID ? ('problem', $problemID) : ('set', $setID);
- my %labels = (map { $_ => $c->maketext($properties{labels}{$_}) } keys %{ $properties{labels} });
+ my %labels = map { $_ => $c->maketext($properties{labels}{$_}) } keys %{ $properties{labels} // {} };
# This contains either a text input or a select for changing a given database field.
my $input = '';
- if ($edit || $number) {
- if ($field =~ /_date/) {
- $input = $c->tag(
- 'div',
- class => 'input-group input-group-sm flatpickr',
- $c->c(
- $c->text_field(
- "$recordType.$recordID.$field",
- $forUsers ? $userValue : $globalValue,
- id => "$recordType.$recordID.${field}_id",
- class => 'form-control form-control-sm'
- . ($field eq 'open_date' ? ' datepicker-group' : ''),
- placeholder => (
- $forUsers && $canOverride ? $c->maketext('Set Default') : $c->maketext('None Specified')
- ),
- data => {
- input => undef,
- done_text => $c->maketext('Done'),
- today_text => $c->maketext('Today'),
- now_text => $c->maketext('Now'),
- locale => $c->ce->{language},
- timezone => $c->ce->{siteDefaults}{timezone}
- }
- ),
- $c->tag(
- 'a',
- class => 'btn btn-secondary btn-sm',
- data => { toggle => undef },
- role => 'button',
- tabindex => 0,
- 'aria-label' => $c->maketext('Pick date and time'),
- $c->tag('i', class => 'fas fa-calendar-alt', 'aria-hidden' => 'true', '')
- )
- )->join('')
- );
- } else {
- my $value = $forUsers ? ($labels{$userValue} || $userValue) : ($labels{$globalValue} || $globalValue);
- $value = $c->ce->{test}{maxProblemsPerPage}
- if ($field eq 'problems_per_page'
- && $c->ce->{test}{maxProblemsPerPage}
- && ($value == 0 || $value > $c->ce->{test}{maxProblemsPerPage}));
- $value = format_set_name_display($value =~ s/\s*,\s*/,/gr) if $field eq 'restricted_release';
-
- my @field_args = (
- "$recordType.$recordID.$field", $value,
- id => "$recordType.$recordID.${field}_id",
- class => 'form-control form-control-sm',
- $field eq 'restricted_release' || $field eq 'source_file' ? (dir => 'ltr') : ()
- );
- if ($field eq 'problem_seed') {
- # Insert a randomization button
+ if ($properties{override} ne 'none') {
+ if ($properties{type} eq 'edit' || ref($properties{type}) eq 'ARRAY') {
+ if ($field =~ /_date/) {
$input = $c->tag(
'div',
- class => 'input-group input-group-sm',
- style => 'min-width: 7rem',
+ class => 'input-group input-group-sm flatpickr',
$c->c(
- $c->number_field(@field_args, min => 0),
+ $c->text_field(
+ "$recordType.$recordID.$field",
+ $forUsers ? $userValue : $globalValue,
+ id => "$recordType.$recordID.${field}_id",
+ class => 'form-control form-control-sm'
+ . ($field eq 'open_date' ? ' datepicker-group' : ''),
+ placeholder => (
+ $forUsers
+ && $canOverride ? $c->maketext('Set Default') : $c->maketext('None Specified')
+ ),
+ data => {
+ input => undef,
+ done_text => $c->maketext('Done'),
+ today_text => $c->maketext('Today'),
+ now_text => $c->maketext('Now'),
+ locale => $c->ce->{language},
+ timezone => $c->ce->{siteDefaults}{timezone}
+ }
+ ),
$c->tag(
- 'button',
- type => 'button',
- class => 'randomize-seed-btn btn btn-sm btn-secondary',
- title => 'randomize',
- data => {
- seed_input => "$recordType.$recordID.problem_seed_id",
- status_input => "$recordType.$recordID.status_id"
- },
- $c->tag('i', class => 'fa-solid fa-shuffle')
+ 'a',
+ class => 'btn btn-secondary btn-sm',
+ data => { toggle => undef },
+ role => 'button',
+ tabindex => 0,
+ 'aria-label' => $c->maketext('Pick date and time'),
+ $c->tag('i', class => 'fas fa-calendar-alt', 'aria-hidden' => 'true', '')
)
)->join('')
);
- } elsif ($number) {
- $input = $c->number_field(
- @field_args,
- min => ($properties{type}[0] || 0),
- max => ($properties{type}[1] || undef),
- step => ($properties{type}[2] || 1),
- $forUsers && $canOverride ? (placeholder => $c->maketext('Set Default')) : ()
- );
} else {
- $input = $c->text_field(@field_args,
- $forUsers && $canOverride ? (placeholder => $c->maketext('Set Default')) : ());
+ my $value = $forUsers ? $userValue : $globalValue;
+ $value = $c->ce->{test}{maxProblemsPerPage}
+ if ($field eq 'problems_per_page'
+ && $c->ce->{test}{maxProblemsPerPage}
+ && ($value == 0 || $value > $c->ce->{test}{maxProblemsPerPage}));
+ $value = format_set_name_display($value =~ s/\s*,\s*/,/gr) if $field eq 'restricted_release';
+
+ my @field_args = (
+ id => "$recordType.$recordID.${field}_id",
+ class => 'form-control form-control-sm',
+ $field eq 'restricted_release' || $field eq 'source_file' ? (dir => 'ltr') : ()
+ );
+ if ($field eq 'problem_seed') {
+ # Insert a randomization button
+ $input = $c->tag(
+ 'div',
+ class => 'input-group input-group-sm',
+ style => 'min-width: 7rem',
+ $c->c(
+ $c->number_field("$recordType.$recordID.$field", $value, @field_args, min => 0),
+ $c->tag(
+ 'button',
+ type => 'button',
+ class => 'randomize-seed-btn btn btn-sm btn-secondary',
+ title => 'randomize',
+ data => {
+ seed_input => "$recordType.$recordID.problem_seed_id",
+ status_input => "$recordType.$recordID.status_id"
+ },
+ $c->tag('i', class => 'fa-solid fa-shuffle')
+ )
+ )->join('')
+ );
+ } elsif (ref($properties{type}) eq 'ARRAY') {
+ if (ref $properties{labels} eq 'HASH') {
+ $input = $c->tag(
+ 'div',
+ class => 'input-group input-group-sm mixed-numeric-select',
+ style => 'min-width: 7rem',
+ $c->c(
+ $c->select_field(
+ "$recordType.$recordID.$field",
+ [
+ $forUsers && $canOverride ? [ $c->maketext('Set Default') => '' ] : (),
+ [
+ $labels{numeric} => 'numeric',
+ $value ne '' && !defined $labels{$value} ? (selected => undef) : ()
+ ],
+ map { [ $labels{$_} => $_, $_ eq $value ? (selected => undef) : () ] }
+ @{ $properties{choices} }
+ ],
+ class => 'form-select form-select-sm'
+ ),
+ $c->number_field(
+ "$recordType.$recordID.$field", defined $labels{$value} ? '' : $value,
+ @field_args,
+ min => $properties{type}[0],
+ $properties{type}[1] ? (max => $properties{type}[1]) : (),
+ step => $properties{type}[2] || 1,
+ style => 'max-width: 4rem'
+ )
+ )->join('')
+ );
+ } else {
+ $input = $c->number_field(
+ "$recordType.$recordID.$field", $value,
+ @field_args,
+ min => $properties{type}[0],
+ $properties{type}[1] ? (max => $properties{type}[1]) : (),
+ step => $properties{type}[2] || 1,
+ $forUsers && $canOverride ? (placeholder => $c->maketext('Set Default')) : (),
+ );
+ }
+ } else {
+ $input = $c->text_field("$recordType.$recordID.$field",
+ $value, @field_args,
+ $forUsers && $canOverride ? (placeholder => $c->maketext('Set Default')) : ());
+ }
}
+ } elsif ($properties{type} eq 'choose') {
+ my $value = $forUsers ? $userValue : $globalValue;
+
+ $input = $c->select_field(
+ "$recordType.$recordID.$field",
+ [
+ $forUsers && $userRecord ? [ $c->maketext('Set Default') => '' ] : (),
+ map { [ $labels{$_} => $_, $_ eq $value ? (selected => undef) : () ] } @{ $properties{choices} }
+ ],
+ id => "$recordType.$recordID.${field}_id",
+ class => 'form-select form-select-sm'
+ );
}
- } elsif ($choose) {
- my $value = $forUsers ? $userValue : $globalValue;
-
- $input = $c->select_field(
- "$recordType.$recordID.$field",
- [
- $forUsers && $userRecord ? [ $c->maketext('Set Default') => '' ] : (),
- map { [ $labels{$_} => $_, $_ eq $value ? (selected => undef) : () ] } @{ $properties{choices} }
- ],
- id => "$recordType.$recordID.${field}_id",
- class => 'form-select form-select-sm'
- );
}
my $globalDisplayValue =
@@ -1342,18 +1333,11 @@ sub initialize ($c) {
my $forOneUser = $forUsers == 1;
$c->stash->{forOneUser} = $forOneUser;
- # If editing a versioned set, it only makes sense edit it for one user.
+ # If editing a versioned set, it only makes sense to edit it for one user.
return if ($editingSetVersion && !$forOneUser);
my %properties = %{ FIELD_PROPERTIES() };
- # Invert the labels hashes.
- my %undoLabels;
- for my $key (keys %properties) {
- %{ $undoLabels{$key} } =
- map { $c->maketext($properties{$key}{labels}{$_}) => $_ } keys %{ $properties{$key}{labels} };
- }
-
my $error = 0;
if ($c->param('submit_changes')) {
my @names = ('open_date', 'due_date', 'answer_date', 'reduced_scoring_date');
@@ -1405,7 +1389,6 @@ sub initialize ($c) {
$c->addbadmessage($c->maketext('No changes were saved!')) if $error;
if ($c->param('submit_changes') && !$error) {
-
my $oldAssignmentType = $setRecord->assignment_type();
# Save general set information (including headers)
@@ -1428,9 +1411,9 @@ sub initialize ($c) {
for my $field (@{ SET_FIELDS() }) {
next unless canChange($forUsers, $field);
- my $param = $c->param("set.$setID.$field");
+ my @paramValues = $c->param("set.$setID.$field");
+ my $param = @paramValues > 1 && $paramValues[0] eq 'numeric' ? $paramValues[1] : $paramValues[0];
if ($param && $param ne '') {
- $param = $undoLabels{$field}{$param} if defined $undoLabels{$field}{$param};
$param = $param * $properties{$field}->{convertby} if $properties{$field}{convertby};
# Special case: Does field fill in multiple values?
@@ -1502,11 +1485,10 @@ sub initialize ($c) {
foreach my $field (@{ SET_FIELDS() }) {
next unless canChange($forUsers, $field);
- my $param = $c->param("set.$setID.$field");
- $param = defined $properties{$field}->{default} ? $properties{$field}->{default} : ""
- unless defined $param && $param ne "";
- my $unlabel = $undoLabels{$field}->{$param};
- $param = $unlabel if defined $unlabel;
+ my @paramValues = $c->param("set.$setID.$field");
+ my $param = @paramValues > 1 && $paramValues[0] eq 'numeric' ? $paramValues[1] : $paramValues[0];
+ $param = defined $properties{$field}{default} ? $properties{$field}{default} : ''
+ unless defined $param && $param ne '';
if ($field =~ /restricted_release/ && $param) {
$param = format_set_name_internal($param =~ s/\s*,\s*/,/gr);
$c->check_sets($db, $param);
@@ -1655,10 +1637,10 @@ sub initialize ($c) {
for my $field (@{ PROBLEM_FIELDS() }) {
next unless canChange($forUsers, $field);
- my $param = $c->param("problem.$problemID.$field");
+ my @paramValues = $c->param("problem.$problemID.$field");
+ my $param =
+ @paramValues > 1 && $paramValues[0] eq 'numeric' ? $paramValues[1] : $paramValues[0];
if (defined $param && $param ne '') {
- $param = $undoLabels{$field}{$param} if defined $undoLabels{$field}{$param};
-
# Protect exploits with source_file
if ($field eq 'source_file') {
if ($param =~ /\.\./ || $param =~ /^\//) {
@@ -1682,11 +1664,12 @@ sub initialize ($c) {
for my $field (@{ USER_PROBLEM_FIELDS() }) {
next unless canChange($forUsers, $field);
- my $param = $c->param("problem.$problemID.$field");
- $param = defined $properties{$field}->{default} ? $properties{$field}->{default} : ""
- unless defined $param && $param ne "";
- my $unlabel = $undoLabels{$field}->{$param};
- $param = $unlabel if defined $unlabel;
+ my @paramValues = $c->param("problem.$problemID.$field");
+ my $param =
+ @paramValues > 1 && $paramValues[0] eq 'numeric' ? $paramValues[1] : $paramValues[0];
+ $param = defined $properties{$field}{default} ? $properties{$field}{default} : ''
+ unless defined $param && $param ne '';
+
# Protect exploits with source_file
if ($field eq 'source_file') {
if ($param =~ /\.\./ || $param =~ /^\//) {
@@ -1719,11 +1702,10 @@ sub initialize ($c) {
foreach my $field (@{ PROBLEM_FIELDS() }) {
next unless canChange($forUsers, $field);
- my $param = $c->param("problem.$problemID.$field");
- $param = defined $properties{$field}->{default} ? $properties{$field}->{default} : ""
- unless defined $param && $param ne "";
- my $unlabel = $undoLabels{$field}->{$param};
- $param = $unlabel if defined $unlabel;
+ my @paramValues = $c->param("problem.$problemID.$field");
+ my $param = @paramValues > 1 && $paramValues[0] eq 'numeric' ? $paramValues[1] : $paramValues[0];
+ $param = defined $properties{$field}{default} ? $properties{$field}{default} : ''
+ unless defined $param && $param ne '';
# Protect exploits with source_file
if ($field eq 'source_file') {
@@ -1759,11 +1741,11 @@ sub initialize ($c) {
foreach my $field (keys %useful) {
next unless canChange($forUsers, $field);
- my $param = $c->param("problem.$problemID.$field");
- $param = defined $properties{$field}->{default} ? $properties{$field}->{default} : ""
- unless defined $param && $param ne "";
- my $unlabel = $undoLabels{$field}->{$param};
- $param = $unlabel if defined $unlabel;
+ my @paramValues = $c->param("problem.$problemID.$field");
+ my $param =
+ @paramValues > 1 && $paramValues[0] eq 'numeric' ? $paramValues[1] : $paramValues[0];
+ $param = defined $properties{$field}{default} ? $properties{$field}{default} : ''
+ unless defined $param && $param ne '';
$changed ||= changed($record->$field, $param);
$record->$field($param);
}
@@ -2048,13 +2030,11 @@ sub initialize ($c) {
}
# Helper method for checking if two values are different.
-# The return values will usually be thrown away, but they could be useful for debugging.
sub changed ($first, $second) {
- return "def/undef" if defined $first && !defined $second;
- return "undef/def" if !defined $first && defined $second;
- return "" if !defined $first && !defined $second;
- return "ne" if $first ne $second;
- return "";
+ return 0 if !defined $first && !defined $second;
+ return 1 if !defined $first || !defined $second;
+ return 1 if $first ne $second;
+ return 0;
}
# Helper method that determines for how many users at a time a field can be changed.
@@ -2071,7 +2051,7 @@ sub canChange ($forUsers, $field) {
return 1 if $howManyCan eq "any";
return 1 if $howManyCan eq "one" && $forOneUser;
return 1 if $howManyCan eq "all" && !$forUsers;
- return 0; # FIXME: maybe it should default to 1?
+ return 0;
}
# Helper method that determines if a file is valid and returns a pretty error message.
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm
index 6a65b99ce0..5e33ee8369 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetList.pm
@@ -643,17 +643,6 @@ sub save_edit_handler ($c) {
}
}
- # make sure the dates are not more than 10 years in the future
- my $curr_time = time;
- my $seconds_per_year = 31_556_926;
- my $cutoff = $curr_time + $seconds_per_year * 10;
- return (0, $c->maketext('Error: Open date cannot be more than 10 years from now in set [_1].', $setID))
- if $Set->open_date > $cutoff;
- return (0, $c->maketext('Error: Close date cannot be more than 10 years from now in set [_1].', $setID))
- if $Set->due_date > $cutoff;
- return (0, $c->maketext('Error: Answer date cannot be more than 10 years from now in set [_1].', $setID))
- if $Set->answer_date > $cutoff;
-
# Check that the open, due and answer dates are in increasing order.
# Bail if this is not correct.
if ($Set->open_date > $Set->due_date) {
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm b/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm
index 63ef67a7bb..33b86fb40b 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm
@@ -22,30 +22,7 @@ sub initialize ($c) {
# Check permissions
return unless $c->authz->hasPermissions($user, 'access_instructor_tools');
- # Cache a list of all users except set level proctors and practice users, and restrict to the sections or
- # recitations that are allowed for the user if such restrictions are defined. This list is sorted by last_name,
- # then first_name, then user_id. This is used in multiple places in this module, and is guaranteed to be used at
- # least once. So it is done here to prevent extra database access.
- $c->{student_records} = [
- $db->getUsersWhere(
- {
- user_id => [ -and => { not_like => 'set_id:%' }, { not_like => "$ce->{practiceUserPrefix}\%" } ],
- $ce->{viewable_sections}{$user} || $ce->{viewable_recitations}{$user}
- ? (
- -or => [
- $ce->{viewable_sections}{$user} ? (section => $ce->{viewable_sections}{$user}) : (),
- $ce->{viewable_recitations}{$user} ? (recitation => $ce->{viewable_recitations}{$user}) : ()
- ]
- )
- : ()
- },
- [qw/last_name first_name user_id/]
- )
- ];
-
- if ($c->current_route eq 'instructor_user_statistics') {
- $c->{studentID} = $c->stash('userID');
- } elsif ($c->current_route =~ /^instructor_(set|problem)_statistics$/) {
+ if ($c->current_route =~ /^instructor_(set|problem)_statistics$/) {
my $setRecord = $db->getGlobalSet($c->stash('setID'));
return unless $setRecord;
$c->{setRecord} = $setRecord;
@@ -57,6 +34,30 @@ sub initialize ($c) {
return unless $problemRecord;
$c->{problemRecord} = $problemRecord;
}
+
+ # Cache a list of all users except set level proctors and practice users, and restrict to the sections
+ # or recitations that are allowed for the user if such restrictions are defined. This list is sorted by
+ # last_name, then first_name, then user_id. This is used in multiple places in this module, and is used
+ # on every page except the main page, so it is done here to prevent extra database access.
+ $c->{student_records} = [
+ $db->getUsersWhere(
+ {
+ user_id =>
+ [ -and => { not_like => 'set_id:%' }, { not_like => "$ce->{practiceUserPrefix}\%" } ],
+ $ce->{viewable_sections}{$user} || $ce->{viewable_recitations}{$user}
+ ? (
+ -or => [
+ $ce->{viewable_sections}{$user} ? (section => $ce->{viewable_sections}{$user}) : (),
+ $ce->{viewable_recitations}{$user}
+ ? (recitation => $ce->{viewable_recitations}{$user})
+ : ()
+ ]
+ )
+ : ()
+ },
+ [qw/last_name first_name user_id/]
+ )
+ ];
}
return;
@@ -67,9 +68,7 @@ sub page_title ($c) {
my $setID = $c->stash('setID') || '';
- if ($c->current_route eq 'instructor_user_statistics') {
- return $c->maketext('Statistics for student [_1]', $c->{studentID});
- } elsif ($c->current_route eq 'instructor_set_statistics') {
+ if ($c->current_route eq 'instructor_set_statistics') {
return $c->maketext('Statistics for [_1]', $c->tag('span', dir => 'ltr', format_set_name_display($setID)));
} elsif ($c->current_route eq 'instructor_problem_statistics') {
return $c->maketext(
@@ -79,12 +78,11 @@ sub page_title ($c) {
);
}
- return $c->maketext('Statistics');
+ return $c->maketext('Set Statistics');
}
sub siblings ($c) {
- # Stats and StudentProgress share this template.
- return $c->include('ContentGenerator/Instructor/Stats/siblings', header => $c->maketext('Statistics'));
+ return $c->include('ContentGenerator/Instructor/Stats/siblings');
}
# Apply the currently selected filter to the student records, and return a reference to the
@@ -567,6 +565,7 @@ sub build_bar_chart ($c, $data, %options) {
viewbox => '-2 -2 ' . ($imageWidth + 3) . ' ' . ($imageHeight + 3),
'aria-labelledby' => "bar_graph_title_$id",
role => 'img',
+ class => 'stats-image',
-nocredits => 1
);
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm b/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm
index 6acc9cc579..0871948c44 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm
@@ -68,8 +68,7 @@ sub page_title ($c) {
}
sub siblings ($c) {
- # Stats and StudentProgress share this template.
- return $c->include('ContentGenerator/Instructor/Stats/siblings', header => $c->maketext('Student Progress'));
+ return $c->include('ContentGenerator/Instructor/StudentProgress/siblings');
}
# Display student progress table
diff --git a/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm b/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm
index a3eff9d7b6..ca6db7ed6b 100644
--- a/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm
+++ b/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm
@@ -93,24 +93,47 @@ use constant SORT_SUBS => {
};
use constant FIELDS => [
- 'user_id', 'first_name', 'last_name', 'email_address', 'student_id', 'status',
- 'section', 'recitation', 'comment', 'permission', 'password'
+ 'user_id', 'first_name', 'last_name', 'email_address',
+ 'student_id', 'status', 'accommodation_time_factor', 'section',
+ 'recitation', 'comment', 'permission', 'password'
];
-# Note that only the editable fields need a type (i.e. all but user_id),
-# and only the text fields need a size.
+# Note that only the editable fields need a type (i.e. all but user_id).
+# The fields of type text or number may also include optional attributes for the HTML input.
+# Any field may also contain a perlValidate method that will be called to validate user input. If provided, it should be
+# a subroutine that takes the parameter value as its only argument, and returns a translatable error string if the
+# parameter value is not valid for the field, and 0 otherwise.
use constant FIELD_PROPERTIES => {
- user_id => { name => x('Login Name') },
- first_name => { name => x('First Name'), type => 'text', size => 10 },
- last_name => { name => x('Last Name'), type => 'text', size => 10 },
- email_address => { name => x('Email Address'), type => 'text', size => 20 },
- student_id => { name => x('Student ID'), type => 'text', size => 11 },
- status => { name => x('Enrollment Status'), type => 'status' },
- section => { name => x('Section'), type => 'text', size => 3 },
- recitation => { name => x('Recitation'), type => 'text', size => 3 },
- comment => { name => x('Comment'), type => 'text', size => 20 },
- permission => { name => x('Permission Level'), type => 'permission' },
- password => { name => x('Password'), type => 'password' },
+ user_id => { name => x('Login Name') },
+ first_name => { name => x('First Name'), type => 'text', attributes => { size => 10 } },
+ last_name => { name => x('Last Name'), type => 'text', attributes => { size => 10 } },
+ email_address => { name => x('Email Address'), type => 'text', attributes => { size => 20 } },
+ student_id => { name => x('Student ID'), type => 'text', attributes => { size => 11 } },
+ status => { name => x('Enrollment Status'), type => 'status' },
+ accommodation_time_factor => {
+ name => x('Accommodation Time Factor'),
+ type => 'number',
+ attributes => {
+ size => 5,
+ min => 1,
+ step => 'any',
+ title => 'Enter a decimal number that is greater than or equal to 1.'
+ },
+ perlValidate => sub {
+ my $value = shift;
+ return $value !~ /^(\d+(\.\d*)?|\.\d+)$/ || $value <= 0
+ ? (x(
+ 'Accomodation time factor for [_1] unchanged. '
+ . 'A value was given that is not a decimal number or is not greater than or equal to 1.'
+ ))[0]
+ : 0;
+ }
+ },
+ section => { name => x('Section'), type => 'text', attributes => { size => 3 } },
+ recitation => { name => x('Recitation'), type => 'text', attributes => { size => 3 } },
+ comment => { name => x('Comment'), type => 'text', attributes => { size => 20 } },
+ permission => { name => x('Permission Level'), type => 'permission' },
+ password => { name => x('Password'), type => 'password' },
};
sub pre_header_initialize ($c) {
@@ -517,7 +540,14 @@ sub save_edit_handler ($c) {
for my $field ($User->NONKEYFIELDS()) {
my $newValue = $c->param("user.$userID.$field");
- $User->$field($newValue) if defined $newValue;
+ next unless defined $newValue;
+ if (ref(FIELD_PROPERTIES()->{$field}{perlValidate}) eq 'CODE'
+ && (my $error = FIELD_PROPERTIES()->{$field}{perlValidate}->($newValue)))
+ {
+ $c->addbadmessage($c->maketext($error, $userID));
+ next;
+ }
+ $User->$field($newValue);
}
$db->putUser($User);
diff --git a/lib/WeBWorK/ContentGenerator/LTIAdvantage.pm b/lib/WeBWorK/ContentGenerator/LTIAdvantage.pm
index 78139db160..75b58e5d6c 100644
--- a/lib/WeBWorK/ContentGenerator/LTIAdvantage.pm
+++ b/lib/WeBWorK/ContentGenerator/LTIAdvantage.pm
@@ -1,11 +1,13 @@
package WeBWorK::ContentGenerator::LTIAdvantage;
-use Mojo::Base 'WeBWorK::ContentGenerator', -signatures;
+use Mojo::Base 'WeBWorK::ContentGenerator', -signatures, -async_await;
use Mojo::UserAgent;
+use Mojo::URL;
use Mojo::JSON qw(decode_json);
use Crypt::JWT qw(decode_jwt encode_jwt);
use Math::Random::Secure qw(irand);
use Digest::SHA qw(sha256_hex);
+use Mojo::File qw(tempfile);
use WeBWorK::Debug qw(debug);
use WeBWorK::Authen::LTIAdvantage::SubmitGrade;
@@ -175,9 +177,11 @@ sub launch ($c) {
return $c->redirect_to($c->systemLink(
$c->url_for($c->stash->{LTILaunchRedirect}),
- $c->stash->{isContentSelection}
- ? (
- params => {
+ params => {
+ %{ Mojo::URL->new($c->stash->{LTILaunchRedirect})->query->to_hash },
+ $c->stash->{isContentSelection}
+ ? (
+
courseID => $c->stash->{courseID},
initial_request => 1,
accept_multiple =>
@@ -191,9 +195,9 @@ sub launch ($c) {
? (data => $c->stash->{lti_jwt_claims}
{'https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'}{data})
: ()
- }
- )
- : ()
+ )
+ : ()
+ }
));
}
@@ -425,4 +429,120 @@ sub purge_expired_lti_data ($c, $ce, $db) {
return;
}
+async sub registration ($c) {
+ return $c->render(json => { error => 'invalid configuration request' }, status => 400)
+ unless defined $c->req->param('openid_configuration') && defined $c->req->param('registration_token');
+
+ # If we want to allow options in the configuration such as whether grade passback is enabled or to allow the LMS
+ # administrator to choose a tool name, then this should render a form that the LMS will be presented in an iframe
+ # allowing the LMS administrator to select the options. When that form is submitted, then the code below should be
+ # executed taking those options into consideration. However, at this point this is a simplistic approach that will
+ # work in most cases.
+
+ $c->render_later;
+
+ my $configurationResult = (await Mojo::UserAgent->new->get_p($c->req->param('openid_configuration')))->result;
+ return $c->render(json => { error => 'unabled to obtain openid configuration' }, status => 400)
+ unless $configurationResult->is_success;
+ my $lmsConfiguration = $configurationResult->json;
+
+ return $c->render(json => { error => 'invalid openid configuration received' }, status => 400)
+ unless defined $lmsConfiguration->{registration_endpoint}
+ && defined $lmsConfiguration->{issuer}
+ && defined $lmsConfiguration->{jwks_uri}
+ && defined $lmsConfiguration->{token_endpoint}
+ && defined $lmsConfiguration->{authorization_endpoint}
+ && defined $lmsConfiguration->{'https://purl.imsglobal.org/spec/lti-platform-configuration'}
+ {product_family_code};
+
+ # FIXME: This should also probably check that the token_endpoint_auth_method is private_key_jwt, the
+ # id_token_signing_alg_values_supported is RS256, and that the scopes_supported is an array and contains all of the
+ # scopes listed below. There are perhaps some other configuration values that should be checked as well. However,
+ # most of the time these are all going to be fine.
+
+ my $rootURL = $c->url_for('root')->to_abs;
+
+ my $registrationResult = (await Mojo::UserAgent->new->post_p(
+ $lmsConfiguration->{registration_endpoint},
+ {
+ Authorization => 'Bearer ' . $c->req->param('registration_token'),
+ 'Content-Type' => 'application/json'
+ },
+ json => {
+ application_type => 'web',
+ response_types => ['id_token'],
+ grant_types => [ 'implicit', 'client_credentials' ],
+ client_name => 'WeBWorK at ' . $rootURL->host_port,
+ client_uri => $rootURL->to_string,
+ initiate_login_uri => $c->url_for('ltiadvantage_login')->to_abs->to_string,
+ redirect_uris => [ $c->url_for('ltiadvantage_launch')->to_abs->to_string ],
+ jwks_uri => $c->url_for('ltiadvantage_keys')->to_abs->to_string,
+ token_endpoint_auth_method => 'private_key_jwt',
+ scope => join(' ',
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem',
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly',
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly',
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/score'),
+ 'https://purl.imsglobal.org/spec/lti-tool-configuration' => {
+ domain => $rootURL->host_port,
+ target_link_uri => $rootURL->to_string,
+ claims => [ 'iss', 'sub', 'name', 'given_name', 'family_name', 'email' ],
+ messages => [ {
+ type => 'LtiDeepLinkingRequest',
+ target_link_uri => $c->url_for('ltiadvantage_content_selection')->to_abs->to_string,
+ # Placements are specific to the LMS. The following placements are needed for Canvas, and Moodle
+ # completely ignores this parameter. Does D2L need any? What about Blackboard?
+ placements => [ 'assignment_selection', 'course_assignments_menu' ]
+ } ]
+ }
+ }
+ ))->result;
+ unless ($registrationResult->is_success) {
+ $c->log->error('Invalid regististration response: ' . $registrationResult->message);
+ return $c->render(json => { error => 'invalid registration response' }, status => 400);
+ }
+ return $c->render(json => { error => 'invalid registration received' }, status => 400)
+ unless defined $registrationResult->json->{client_id};
+
+ my $configuration = <<~ "END_CONFIG";
+ \$LTI{v1p3}{PlatformID} = '$lmsConfiguration->{issuer}';
+ \$LTI{v1p3}{ClientID} = '${\($registrationResult->json->{client_id})}';
+ \$LTI{v1p3}{DeploymentID} = '${
+ \($registrationResult->json->{'https://purl.imsglobal.org/spec/lti-tool-configuration'}{deployment_id}
+ // 'obtain from LMS administrator')
+ }';
+ \$LTI{v1p3}{PublicKeysetURL} = '$lmsConfiguration->{jwks_uri}';
+ \$LTI{v1p3}{AccessTokenURL} = '$lmsConfiguration->{token_endpoint}';
+ \$LTI{v1p3}{AccessTokenAUD} = '${
+ \($lmsConfiguration->{authorization_server}
+ // $lmsConfiguration->{token_endpoint})
+ }';
+ \$LTI{v1p3}{AuthReqURL} = '$lmsConfiguration->{authorization_endpoint}';
+ END_CONFIG
+
+ my $registrationDir = Mojo::File->new($c->ce->{webworkDirs}{DATA})->child('LTIRegistrationRequests');
+ if (!-d $registrationDir) {
+ eval { $registrationDir->make_path };
+ if ($@) {
+ $c->log->error("Failed to create directory for saving LTI registrations: $@");
+ return $c->render(json => { error => 'internal server error' }, status => 400);
+ }
+ }
+
+ my $registrationFile = tempfile(
+ TEMPLATE =>
+ $lmsConfiguration->{'https://purl.imsglobal.org/spec/lti-platform-configuration'}{product_family_code}
+ . '-XXXX',
+ DIR => $registrationDir,
+ SUFFIX => '.conf',
+ UNLINK => 0
+ );
+ $registrationFile->spew($configuration, 'UTF-8');
+
+ # This tells the LMS that registration is complete and it can close its dialog.
+ return $c->render(data => '');
+}
+
1;
diff --git a/lib/WeBWorK/ContentGenerator/Problem.pm b/lib/WeBWorK/ContentGenerator/Problem.pm
index e2ef7bd20a..5bda78c4fc 100644
--- a/lib/WeBWorK/ContentGenerator/Problem.pm
+++ b/lib/WeBWorK/ContentGenerator/Problem.pm
@@ -7,7 +7,6 @@ WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem.
=cut
-use WeBWorK::HTML::SingleProblemGrader;
use WeBWorK::Debug;
use WeBWorK::Utils qw(decodeAnswers wwRound);
use WeBWorK::Utils::DateTime qw(before between after);
@@ -23,6 +22,8 @@ use WeBWorK::AchievementEvaluator qw(checkForAchievements);
use WeBWorK::DB::Utils qw(global2user fake_set fake_problem);
use WeBWorK::Localize;
use WeBWorK::AchievementEvaluator;
+use WeBWorK::HTML::SingleProblemGrader;
+use WeBWorK::HTML::StudentNav qw(studentNav);
# GET/POST Parameters for this module
#
@@ -431,20 +432,17 @@ async sub pre_header_initialize ($c) {
Count => $problem->{showMeAnotherCount},
};
- # Unset the showProblemGrader parameter if the "Hide Problem Grader" button was clicked.
- $c->param(showProblemGrader => undef) if $c->param('hideProblemGrader');
-
# Permissions
# What does the user want to do?
my %want = (
showOldAnswers => $user->showOldAnswers ne '' ? $user->showOldAnswers : $ce->{pg}{options}{showOldAnswers},
showCorrectAnswers => 1,
- showProblemGrader => $c->param('showProblemGrader') || 0,
- showAnsGroupInfo => $c->param('showAnsGroupInfo') || $ce->{pg}{options}{showAnsGroupInfo},
- showAnsHashInfo => $c->param('showAnsHashInfo') || $ce->{pg}{options}{showAnsHashInfo},
- showPGInfo => $c->param('showPGInfo') || $ce->{pg}{options}{showPGInfo},
- showResourceInfo => $c->param('showResourceInfo') || $ce->{pg}{options}{showResourceInfo},
+ showProblemGrader => $userID ne $effectiveUserID,
+ showAnsGroupInfo => $c->param('showAnsGroupInfo') || $ce->{pg}{options}{showAnsGroupInfo},
+ showAnsHashInfo => $c->param('showAnsHashInfo') || $ce->{pg}{options}{showAnsHashInfo},
+ showPGInfo => $c->param('showPGInfo') || $ce->{pg}{options}{showPGInfo},
+ showResourceInfo => $c->param('showResourceInfo') || $ce->{pg}{options}{showResourceInfo},
showHints => 1,
showSolutions => 1,
useMathView => $user->useMathView ne '' ? $user->useMathView : $ce->{pg}{options}{useMathView},
@@ -531,18 +529,16 @@ async sub pre_header_initialize ($c) {
# requiring another answer submission.
my $showReturningFeedback = 0;
- # Sticky answers
- if (!($c->{submitAnswers} || $previewAnswers || $checkAnswers) && $will{showOldAnswers}) {
+ # Reinsert sticky answers. Do this only if new answers are NOT being submitted,
+ # and a new problem version is NOT being opened.
+ if (!($prEnabled && !$problem->{prCount})
+ && !($c->{submitAnswers} || $previewAnswers || $checkAnswers)
+ && $will{showOldAnswers})
+ {
my %oldAnswers = decodeAnswers($problem->last_answer);
- # Do this only if new answers are NOT being submitted
- if ($prEnabled && !$problem->{prCount}) {
- # Clear answers if this is a new problem version
- delete $formFields->{$_} for keys %oldAnswers;
- } else {
- $formFields->{$_} = $oldAnswers{$_} for (keys %oldAnswers);
- $showReturningFeedback = 1
- if $ce->{pg}{options}{automaticAnswerFeedback} && $problem->num_correct + $problem->num_incorrect > 0;
- }
+ $formFields->{$_} = $oldAnswers{$_} for (keys %oldAnswers);
+ $showReturningFeedback = 1
+ if $ce->{pg}{options}{automaticAnswerFeedback} && $problem->num_correct + $problem->num_incorrect > 0;
}
my $showOnlyCorrectAnswers = $c->param('showCorrectAnswers') && $will{showCorrectAnswers};
@@ -555,7 +551,9 @@ async sub pre_header_initialize ($c) {
$c->{set},
$problem,
$c->{set}->psvn,
- $formFields,
+ $prEnabled
+ && !$problem->{prCount}
+ && !($c->{submitAnswers} || $previewAnswers || $checkAnswers || $showOnlyCorrectAnswers) ? {} : $formFields,
{
displayMode => $displayMode,
showHints => $will{showHints},
@@ -581,22 +579,21 @@ async sub pre_header_initialize ($c) {
&& after($c->{set}->answer_date, $c->submitTime)),
showMessages => !$showOnlyCorrectAnswers,
showCorrectAnswers => (
- $will{showProblemGrader} || ($c->{submitAnswers} && $c->{showCorrectOnRandomize}) ? 2
+ $c->{submitAnswers} && $c->{showCorrectOnRandomize} ? 2
: !$c->{previewAnswers} && after($c->{set}->answer_date, $c->submitTime)
? ($ce->{pg}{options}{correctRevealBtnAlways} ? 1 : 2)
- : !$c->{previewAnswers} && $will{showCorrectAnswers} ? 1
+ : $will{showProblemGrader} || (!$c->{previewAnswers} && $will{showCorrectAnswers}) ? 1
: 0
),
debuggingOptions => getTranslatorDebuggingOptions($authz, $userID),
- $can{checkAnswers}
- && defined $formFields->{problem_data} ? (problemData => $formFields->{problem_data}) : ()
+ $prEnabled && !$problem->{prCount}
+ ? (problemData => '{}')
+ : ($can{checkAnswers} && defined $formFields->{problem_data})
+ ? (problemData => $formFields->{problem_data})
+ : ()
}
);
- # Warnings in the renderPG subprocess will not be caught by the global warning handler of this process.
- # So rewarn them and let the global warning handler take care of it.
- warn $pg->{warnings} if $pg->{warnings};
-
debug('end pg processing');
$pg->{body_text} .= $c->hidden_field(
@@ -608,20 +605,6 @@ async sub pre_header_initialize ($c) {
$can{showHints} &&= $pg->{flags}{hintExists};
$can{showSolutions} &&= $pg->{flags}{solutionExists};
- # Record errors
- $c->{pgdebug} = $pg->{debug_messages} if ref $pg->{debug_messages} eq 'ARRAY';
- $c->{pgwarning} = $pg->{warning_messages} if ref $pg->{warning_messages} eq 'ARRAY';
- $c->{pginternalerrors} = $pg->{internal_debug_messages} if ref $pg->{internal_debug_messages} eq 'ARRAY';
- # $c->{pgerrors} is defined if any of the above are defined, and is nonzero if any are non-empty.
- $c->{pgerrors} = @{ $c->{pgdebug} // [] } || @{ $c->{pgwarning} // [] } || @{ $c->{pginternalerrors} // [] }
- if defined $c->{pgdebug} || defined $c->{pgwarning} || defined $c->{pginternalerrors};
-
- # If $c->{pgerrors} is not defined, then the PG messages arrays were not defined,
- # which means $pg->{pgcore} was not defined and the translator died.
- warn 'Processing of this PG problem was not completed. Probably because of a syntax error. '
- . 'The translator died prematurely and no PG warning messages were transmitted.'
- unless defined $c->{pgerrors};
-
# Store fields
$c->{want} = \%want;
$c->{can} = \%can;
@@ -634,53 +617,6 @@ async sub pre_header_initialize ($c) {
return;
}
-sub warnings ($c) {
- my $output = $c->c;
-
- # Display warning messages
- if (!defined $c->{pgerrors}) {
- push(
- @$output,
- $c->tag(
- 'div',
- $c->c(
- $c->tag('h3', style => 'color:red;', $c->maketext('PG question failed to render')),
- $c->tag('p', $c->maketext('Unable to obtain error messages from within the PG question.'))
- )->join('')
- )
- );
- } elsif ($c->{pgerrors} > 0) {
- my @pgdebug = @{ $c->{pgdebug} // [] };
- my @pgwarning = @{ $c->{pgwarning} // [] };
- my @pginternalerrors = @{ $c->{pginternalerrors} // [] };
- push(
- @$output,
- $c->tag(
- 'div',
- $c->c(
- $c->tag('h2', $c->maketext('PG question processing error messages')),
- @pgdebug ? $c->c(
- $c->tag('h3', $c->maketext('PG debug messages')),
- $c->tag('p', $c->c(@pgdebug)->join($c->tag('br')))
- )->join('') : '',
- @pgwarning ? $c->c(
- $c->tag('h3', $c->maketext('PG warning messages')),
- $c->tag('p', $c->c(@pgwarning)->join($c->tag('br')))
- )->join('') : '',
- @pginternalerrors ? $c->c(
- $c->tag('h3', $c->maketext('PG internal errors')),
- $c->tag('p', $c->c(@pginternalerrors)->join($c->tag('br')))
- )->join('') : ''
- )->join('')
- )
- );
- }
-
- push(@$output, $c->SUPER::warnings());
-
- return $output->join('');
-}
-
sub head ($c) {
return '' if ($c->{invalidSet});
return $c->{pg}{head_text} if $c->{pg}{head_text};
@@ -722,9 +658,6 @@ sub siblings ($c) {
my @items;
- # Keep the grader open when linking to problems if it is already open.
- my %problemGraderLink = $c->{will}{showProblemGrader} ? (params => { showProblemGrader => 1 }) : ();
-
for my $problemID (@problemIDs) {
if ($isJitarSet
&& !$authz->hasPermissions($eUserID, 'view_unopened_sets')
@@ -795,7 +728,7 @@ sub siblings ($c) {
@items,
$c->tag(
'a',
- $active ? () : (href => $c->systemLink($problemPage, %problemGraderLink)),
+ $active ? () : (href => $c->systemLink($problemPage)),
class => $class,
$c->b($c->maketext('Problem [_1]', join('.', @seq)) . $status_symbol)
)
@@ -806,7 +739,7 @@ sub siblings ($c) {
@items,
$c->tag(
'a',
- $active ? () : (href => $c->systemLink($problemPage, %problemGraderLink)),
+ $active ? () : (href => $c->systemLink($problemPage)),
class => 'nav-link' . ($active ? ' active' : ''),
$c->b($c->maketext('Problem [_1]', $problemID) . $status_symbol)
)
@@ -842,74 +775,6 @@ sub nav ($c, $args) {
my $mergedSet = $db->getMergedSet($eUserID, $setID);
return '' if !$mergedSet;
- # Set up a student navigation for those that have permission to act as a student.
- my $userNav = '';
- if ($authz->hasPermissions($userID, 'become_student') && $eUserID ne $userID) {
- # Find all users for this set (except the current user) sorted by last_name, then first_name, then user_id.
- my @allUserRecords = $db->getUsersWhere(
- {
- user_id => [
- map { $_->[0] } $db->listUserSetsWhere({ set_id => $setID, user_id => { not_like => $userID } })
- ]
- },
- [qw/last_name first_name user_id/]
- );
-
- my $filter = $c->param('studentNavFilter');
-
- # Find the previous, current, and next users, and format the student names for display.
- # Also create a hash of sections and recitations if there are any for the course.
- my @userRecords;
- my $currentUserIndex = 0;
- my %filters;
- for (@allUserRecords) {
- # Add to the sections and recitations if defined. Also store the first user found in that section or
- # recitation. This user will be switched to when the filter is selected.
- my $section = $_->section;
- $filters{"section:$section"} = [ $c->maketext('Filter by section [_1]', $section), $_->user_id ]
- if $section && !$filters{"section:$section"};
- my $recitation = $_->recitation;
- $filters{"recitation:$recitation"} = [ $c->maketext('Filter by recitation [_1]', $recitation), $_->user_id ]
- if $recitation && !$filters{"recitation:$recitation"};
-
- # Only keep this user if it satisfies the selected filter if a filter was selected.
- next
- unless !$filter
- || ($filter =~ /^section:(.*)$/ && $_->section eq $1)
- || ($filter =~ /^recitation:(.*)$/ && $_->recitation eq $1);
-
- my $addRecord = $_;
- $currentUserIndex = @userRecords if $addRecord->user_id eq $eUserID;
- push @userRecords, $addRecord;
-
- # Construct a display name.
- $addRecord->{displayName} =
- ($addRecord->last_name || $addRecord->first_name
- ? $addRecord->last_name . ', ' . $addRecord->first_name
- : $addRecord->user_id);
- }
- my $prevUser = $currentUserIndex > 0 ? $userRecords[ $currentUserIndex - 1 ] : 0;
- my $nextUser = $currentUserIndex < $#userRecords ? $userRecords[ $currentUserIndex + 1 ] : 0;
-
- # Mark the current user.
- $userRecords[$currentUserIndex]{currentUser} = 1;
-
- my $problemPage = $c->url_for('problem_detail', setID => $setID, problemID => $problemID);
-
- # Set up the student nav.
- $userNav = $c->include(
- 'ContentGenerator/Problem/student_nav',
- eUserID => $eUserID,
- problemPage => $problemPage,
- userRecords => \@userRecords,
- currentUserIndex => $currentUserIndex,
- prevUser => $prevUser,
- nextUser => $nextUser,
- filter => $filter,
- filters => \%filters
- );
- }
-
my $isJitarSet = $mergedSet->assignment_type eq 'jitar';
my ($prevID, $nextID);
@@ -970,10 +835,9 @@ sub nav ($c, $args) {
}
my %tail;
- $tail{displayMode} = $c->{displayMode} if defined $c->{displayMode};
- $tail{showOldAnswers} = 1 if $c->{will}{showOldAnswers};
- $tail{showProblemGrader} = 1 if $c->{will}{showProblemGrader};
- $tail{studentNavFilter} = $c->param('studentNavFilter') if $c->param('studentNavFilter');
+ $tail{displayMode} = $c->{displayMode} if defined $c->{displayMode};
+ $tail{showOldAnswers} = 1 if $c->{will}{showOldAnswers};
+ $tail{studentNavFilter} = $c->param('studentNavFilter') if $c->param('studentNavFilter');
return $c->tag(
'div',
@@ -981,7 +845,7 @@ sub nav ($c, $args) {
role => 'navigation',
'aria-label' => 'problem navigation',
$c->c($c->tag('div', class => 'd-flex submit-buttons-container', $c->navMacro($args, \%tail, @links)),
- $userNav)->join('')
+ studentNav($c, $setID))->join('')
);
}
@@ -1042,10 +906,7 @@ sub page_title ($c) {
# This uses the permission level and user id of the user assigned to the problem.
my $problemUser = $problem->user_id;
- my $inList = grep { $_ eq $problemUser } @{ $ce->{pg}{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} };
- if ($db->getPermissionLevel($problemUser)->permission >=
- $ce->{pg}{specialPGEnvironmentVars}{PRINT_FILE_NAMES_PERMISSION_LEVEL} || $inList)
- {
+ if ($c->authz->hasPermissions($problemUser, 'print_path_to_problem')) {
$subheader .= ' ' . $problem->source_file;
}
@@ -1113,7 +974,7 @@ sub output_problem_body ($c) {
} else {
# For students render the body text of the problem with a message about error details.
return $c->c(
- $c->tag('div', id => 'output_problem_body', $c->b($c->{pg}{body_text})),
+ $c->tag('div', $c->b($c->{pg}{body_text})),
$c->include(
'ContentGenerator/Base/error_output',
error => $c->{pg}{errors},
@@ -1123,7 +984,14 @@ sub output_problem_body ($c) {
}
}
- return $c->tag('div', id => 'output_problem_body', $c->b($c->{pg}{body_text}));
+ return $c->tag(
+ 'div',
+ id => 'output_problem_body',
+ class => 'text-dark',
+ style => 'color-scheme: light',
+ data => { bs_theme => 'light' },
+ $c->b($c->{pg}{body_text})
+ );
}
# Output messages about the problem
@@ -1133,10 +1001,8 @@ sub output_message ($c) {
# Output the problem grader if the user has permissions to grade problems
sub output_grader ($c) {
- if ($c->{will}{showProblemGrader}) {
- return WeBWorK::HTML::SingleProblemGrader->new($c, $c->{pg}, $c->{problem})->insertGrader;
- }
-
+ return WeBWorK::HTML::SingleProblemGrader->new($c, $c->{pg}, $c->{problem}, $c->{set})->insertGrader
+ if $c->{will}{showProblemGrader};
return '';
}
@@ -1444,7 +1310,7 @@ sub output_summary ($c) {
# Attempt summary
if ($c->{submitAnswers}) {
push(@$output, $c->attemptResults($pg));
- } elsif ($will{checkAnswers} || $c->{will}{showProblemGrader}) {
+ } elsif ($will{checkAnswers}) {
push(
@$output,
$c->tag(
@@ -1480,7 +1346,7 @@ sub output_summary ($c) {
'div',
class => 'alert alert-danger d-inline-block mb-2 p-1',
$c->maketext(
- 'ATTEMPT NOT ACCEPTED -- Please submit answers again (or request new version if neccessary).')
+ 'ATTEMPT NOT ACCEPTED -- Please submit answers again (or request new version if necessary).')
)
) if $c->{resubmitDetected};
@@ -1549,7 +1415,7 @@ sub output_achievement_message ($c) {
&& $c->{submitAnswers}
&& $c->{problem}->set_id ne 'Undefined_Set')
{
- return checkForAchievements($c->{problem}, $c->{pg}, $c);
+ return checkForAchievements($c->{problem}, $c);
}
return '';
@@ -1573,13 +1439,13 @@ sub output_custom_edit_message ($c) {
# Output the "Show Past Answers" button
sub output_past_answer_button ($c) {
- my $problemID = $c->{problem}->problem_id;
- my $setRecord = $c->db->getGlobalSet($c->{problem}->set_id);
- if (defined $setRecord && $setRecord->assignment_type eq 'jitar') {
- $problemID = join('.', jitar_id_to_seq($problemID));
- }
-
if ($c->authz->hasPermissions($c->param('user'), 'view_answers')) {
+ my $problemID = $c->{problem}->problem_id;
+ my $setRecord = $c->db->getGlobalSet($c->{problem}->set_id);
+ if (defined $setRecord && $setRecord->assignment_type eq 'jitar') {
+ $problemID = join('.', jitar_id_to_seq($problemID));
+ }
+
my $hiddenFields = $c->hidden_authen_fields;
$hiddenFields =~ s/\"hidden_/\"pastans-hidden_/g;
return $c->form_for(
@@ -1593,7 +1459,8 @@ sub output_past_answer_button ($c) {
$c->hidden_field(selected_sets => $c->{problem}->set_id),
$c->hidden_field(selected_users => $c->{problem}->user_id),
$c->tag(
- 'p',
+ 'div',
+ class => 'mb-3',
$c->submit_button(
$c->maketext('Show Past Answers'),
name => 'action',
diff --git a/lib/WeBWorK/ContentGenerator/ProblemSet.pm b/lib/WeBWorK/ContentGenerator/ProblemSet.pm
index ba77939ec5..d20438d94d 100644
--- a/lib/WeBWorK/ContentGenerator/ProblemSet.pm
+++ b/lib/WeBWorK/ContentGenerator/ProblemSet.pm
@@ -17,19 +17,25 @@ use WeBWorK::Utils::Sets qw(is_restricted grade_set format_set_name_display
use WeBWorK::DB::Utils qw(grok_versionID_from_vsetID_sql);
use WeBWorK::Localize;
use WeBWorK::AchievementItems;
+use WeBWorK::HTML::StudentNav qw(studentNav);
+
+sub can ($c, $arg) {
+ if ($arg eq 'info') {
+ return $c->{pg} ? 1 : 0;
+ }
+ return $c->SUPER::can($arg);
+}
async sub initialize ($c) {
my $db = $c->db;
my $ce = $c->ce;
my $authz = $c->authz;
- # $c->{invalidSet} is set in checkSet which is called by ContentGenerator.pm
- return
- if $c->{invalidSet}
- && ($c->{invalidSet} !~ /^Client ip address .* is not in the list of addresses/
- || $authz->{merged_set}->assignment_type !~ /gateway/);
+ # $c->{invalidSet} is set in checkSet which is called by ContentGenerator.pm.
+ # If $c->{viewSetCheck} is also set, we want to view some information unless the set is hidden.
+ return if $c->{invalidSet} && (!$c->{viewSetCheck} || $c->{viewSetCheck} eq 'hidden');
- # This will all be valid if checkSet did not set $c->{invalidSet}.
+ # This will all be valid if the above check passes.
my $userID = $c->param('user');
my $eUserID = $c->param('effectiveUser');
@@ -105,6 +111,7 @@ async sub initialize ($c) {
$c->{pg} =
await renderPG($c, $effectiveUser, $c->{set}, $problem, $c->{set}->psvn, {}, { displayMode => $displayMode });
+ $c->{pg} = '' unless $c->{pg}{body_text} =~ /\S/;
return;
}
@@ -113,17 +120,24 @@ sub nav ($c, $args) {
# Don't show the nav if the user does not have unrestricted navigation permissions.
return '' unless $c->authz->hasPermissions($c->param('user'), 'navigation_allowed');
- my @links = (
- $c->maketext('Assignments'),
- $c->url_for($c->app->routes->lookup($c->current_route)->parent->name),
- $c->maketext('Assignments')
- );
return $c->tag(
'div',
class => 'row sticky-nav',
role => 'navigation',
- 'aria-label' => 'problem navigation',
- $c->tag('div', $c->navMacro($args, {}, @links))
+ 'aria-label' => 'set navigation',
+ $c->c(
+ $c->tag(
+ 'div',
+ class => 'd-flex submit-buttons-container',
+ $c->navMacro(
+ $args, {},
+ $c->maketext('Assignments'),
+ $c->url_for($c->app->routes->lookup($c->current_route)->parent->name),
+ $c->maketext('Assignments')
+ )
+ ),
+ $c->{set} ? studentNav($c, $c->{set}->set_id) : ''
+ )->join('')
);
}
@@ -161,10 +175,8 @@ sub siblings ($c) {
return $c->include('ContentGenerator/ProblemSet/siblings', setIDs => \@setIDs);
}
-sub info {
- my ($c) = @_;
- return '' unless $c->{pg};
- return $c->include('ContentGenerator/ProblemSet/info');
+sub info ($c) {
+ return $c->{pg} ? $c->include('ContentGenerator/ProblemSet/info') : '';
}
# This is called by the ContentGenerator/ProblemSet/body template for a regular homework set.
@@ -180,12 +192,14 @@ sub gateway_body ($c) {
my $ce = $c->ce;
my $db = $c->db;
- my $set = $c->{set};
- my $effectiveUser = $c->param('effectiveUser');
- my $user = $c->param('user');
+ my $set = $c->{set};
+ my $effectiveUserID = $c->param('effectiveUser');
+ my $userID = $c->param('user');
+
+ my $effectiveUser = $db->getUser($effectiveUserID);
my $timeNow = time;
- my $timeLimit = $set->version_time_limit || 0;
+ my $timeLimit = ($set->version_time_limit || 0) * $effectiveUser->accommodation_time_factor;
# Compute how many versions have been launched within timeInterval to determine if a new version can be created,
# if a version can be continued, and the date a next version can be started. If there is an open version that
@@ -206,8 +220,9 @@ sub gateway_body ($c) {
}
# Get a problem to determine how many submits have been made.
- my @ProblemNums = $db->listUserProblems($effectiveUser, $set->set_id);
- my $Problem = $db->getMergedProblemVersion($effectiveUser, $set->set_id, $verSet->version_id, $ProblemNums[0]);
+ my @ProblemNums = $db->listUserProblems($effectiveUserID, $set->set_id);
+ my $Problem =
+ $db->getMergedProblemVersion($effectiveUserID, $set->set_id, $verSet->version_id, $ProblemNums[0]);
my $verSubmits = defined $Problem ? $Problem->num_correct + $Problem->num_incorrect : 0;
my $maxSubmits = $verSet->attempts_per_version || 0;
@@ -292,11 +307,11 @@ sub gateway_body ($c) {
$data->{score} = '';
# Only show score if user has permission and assignment has at least one submit.
- if ($authz->hasPermissions($user, 'view_hidden_work')
+ if ($authz->hasPermissions($userID, 'view_hidden_work')
|| ($verSet->hide_score eq 'N' && $verSubmits >= 1)
|| ($verSet->hide_score eq 'BeforeAnswerDate' && $timeNow > $set->answer_date))
{
- my ($total, $possible) = grade_set($db, $verSet, $effectiveUser, 1);
+ my ($total, $possible) = grade_set($db, $verSet, $effectiveUserID, 1);
$total = wwRound(2, $total);
$data->{score} = "$total/$possible";
}
diff --git a/lib/WeBWorK/ContentGenerator/ProblemSets.pm b/lib/WeBWorK/ContentGenerator/ProblemSets.pm
index 3eeb4fb4c3..2c02a51ceb 100644
--- a/lib/WeBWorK/ContentGenerator/ProblemSets.pm
+++ b/lib/WeBWorK/ContentGenerator/ProblemSets.pm
@@ -11,7 +11,7 @@ use WeBWorK::Debug;
use WeBWorK::Utils qw(sortByName);
use WeBWorK::Utils::DateTime qw(after);
use WeBWorK::Utils::Files qw(readFile path_is_subdir);
-use WeBWorK::Utils::Sets qw(is_restricted format_set_name_display);
+use WeBWorK::Utils::Sets qw(restricted_set_message);
use WeBWorK::Localize;
# The "default" data in the course_info.txt file.
@@ -114,15 +114,11 @@ sub info ($c) {
}
sub getSetStatus ($c, $set) {
- my $ce = $c->ce;
- my $db = $c->db;
- my $authz = $c->authz;
- my $effectiveUser = $c->param('effectiveUser') || $c->param('user');
- my $canViewUnopened = $authz->hasPermissions($c->param('user'), 'view_unopened_sets');
-
- my @restricted = $ce->{options}{enableConditionalRelease} ? is_restricted($db, $set, $effectiveUser) : ();
-
- my $link_is_active = 1;
+ my $ce = $c->ce;
+ my $db = $c->db;
+ my $authz = $c->authz;
+ my $effectiveUser = $c->param('effectiveUser') || $c->param('user');
+ my $restricted_msg = restricted_set_message($c, $set, 'conditional') || restricted_set_message($c, $set, 'lti');
# Determine set status.
my $status_msg;
@@ -132,11 +128,7 @@ sub getSetStatus ($c, $set) {
$status = 'not-open';
$status_msg =
$c->maketext('Will open on [_1].', $c->formatDateTime($set->open_date, $ce->{studentDateDisplayFormat}));
- push(@$other_messages, $c->restricted_progression_msg(1, $set->restricted_status * 100, @restricted))
- if @restricted;
- $link_is_active = 0
- unless $canViewUnopened
- || ($set->assignment_type =~ /gateway/ && $db->countSetVersions($effectiveUser, $set->set_id));
+ push(@$other_messages, $restricted_msg) if $restricted_msg;
} elsif ($c->submitTime < $set->due_date) {
$status = 'open';
@@ -172,31 +164,8 @@ sub getSetStatus ($c, $set) {
$c->maketext('Open. Due [_1].', $c->formatDateTime($set->due_date, $ce->{studentDateDisplayFormat}));
}
- if (@restricted) {
- $link_is_active = 0 unless $canViewUnopened;
- push(@$other_messages, $c->restricted_progression_msg(0, $set->restricted_status * 100, @restricted));
- } elsif (!$canViewUnopened
- && $ce->{LTIVersion}
- && ($ce->{LTIVersion} ne 'v1p3' || !$ce->{LTI}{v1p3}{ignoreMissingSourcedID})
- && defined $ce->{LTIGradeMode}
- && $ce->{LTIGradeMode} eq 'homework'
- && !$set->lis_source_did)
- {
- # The set shouldn't be shown if LTI grade mode is set to homework and a
- # sourced_id is not available to use to send back grades
- # (unless we are using LTI 1.3 and $LTI{v1p3}{ignoreMissingSourcedID} is set)
- push(
- @$other_messages,
- $c->maketext(
- 'You must log into this set via your Learning Management System ([_1]).',
- $ce->{LTI}{ $ce->{LTIVersion} }{LMS_url}
- ? $c->link_to(
- $ce->{LTI}{ $ce->{LTIVersion} }{LMS_name} => $ce->{LTI}{ $ce->{LTIVersion} }{LMS_url}
- )
- : $ce->{LTI}{ $ce->{LTIVersion} }{LMS_name}
- )
- );
- $link_is_active = 0;
+ if ($restricted_msg) {
+ push(@$other_messages, $restricted_msg);
}
} elsif ($c->submitTime < $set->answer_date) {
$status_msg = $c->maketext('Answers available for review on [_1].',
@@ -209,8 +178,7 @@ sub getSetStatus ($c, $set) {
status => $status,
status_msg => $status_msg,
other_messages => $other_messages,
- link_is_active => $link_is_active,
- is_restricted => scalar(@restricted)
+ is_restricted => $restricted_msg ? 1 : 0
);
}
@@ -232,20 +200,4 @@ sub byUrgency {
return $a->set_id cmp $b->set_id;
}
-sub restricted_progression_msg ($c, $open, $restriction, @restricted) {
- if (@restricted == 1) {
- return $c->maketext(
- 'To access this set you must score at least [_1]% on set [_2].',
- sprintf('%.0f', $restriction),
- $c->tag('span', dir => 'ltr', format_set_name_display($restricted[0]))
- );
- } else {
- return $c->maketext(
- 'To access this set you must score at least [_1]% on the following sets: [_2].',
- sprintf('%.0f', $restriction),
- join(', ', map { $c->tag('span', dir => 'ltr', format_set_name_display($_)) } @restricted)
- );
- }
-}
-
1;
diff --git a/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm b/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm
index 09e719d91b..f5d95d9e6e 100644
--- a/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm
+++ b/lib/WeBWorK/ContentGenerator/RenderViaRPC.pm
@@ -21,14 +21,34 @@ use WebworkWebservice;
sub initializeRoute ($c, $routeCaptures) {
$c->{rpc} = 1;
+ my $allow_unsecured_rpc = $c->config('allow_unsecured_rpc');
+ my $disable_cookies = 0;
+
+ if ($allow_unsecured_rpc) {
+ if (ref($allow_unsecured_rpc) eq 'HASH') {
+ my $courseID = $c->param('courseID');
+ if ($courseID && $allow_unsecured_rpc->{$courseID}) {
+ if (ref($allow_unsecured_rpc->{$courseID}) eq 'HASH') {
+ my $userID = $c->param('user');
+ if ($userID && $allow_unsecured_rpc->{$courseID}{$userID}) {
+ $disable_cookies = 1;
+ }
+ } else {
+ $disable_cookies = 1;
+ }
+ }
+ } else {
+ $disable_cookies = 1;
+ }
+ }
$c->stash(disable_cookies => 1)
- if $c->current_route eq 'render_rpc' && $c->param('disableCookies') && $c->config('allow_unsecured_rpc');
+ if $c->current_route eq 'render_rpc' && $c->param('disableCookies') && $disable_cookies;
# This provides compatibility for legacy html2xml parameters.
# This should be deleted when the html2xml endpoint is removed.
if ($c->current_route eq 'html2xml') {
- $c->stash(disable_cookies => 1) if $c->config('allow_unsecured_rpc');
+ $c->stash(disable_cookies => 1) if $disable_cookies;
for ([ 'userID', 'user' ], [ 'course_password', 'passwd' ], [ 'session_key', 'key' ]) {
$c->param($_->[1], $c->param($_->[0])) if defined $c->param($_->[0]) && !defined $c->param($_->[1]);
}
diff --git a/lib/WeBWorK/ContentGenerator/SampleProblemViewer.pm b/lib/WeBWorK/ContentGenerator/SampleProblemViewer.pm
index 3ee248c2b0..d4d4ef44a0 100644
--- a/lib/WeBWorK/ContentGenerator/SampleProblemViewer.pm
+++ b/lib/WeBWorK/ContentGenerator/SampleProblemViewer.pm
@@ -2,14 +2,10 @@ package WeBWorK::ContentGenerator::SampleProblemViewer;
use Mojo::Base 'WeBWorK::ContentGenerator', -signatures;
use File::Basename qw(basename);
-use Mojo::File;
-use Mojo::JSON qw(decode_json encode_json);
-use File::Find;
use Pod::Simple::Search;
-use Pod::Simple::SimpleTree;
-use WeBWorK::Utils::Files qw(path_is_subdir);
-use SampleProblemParser qw(parseSampleProblem generateMetadata getSampleProblemCode);
+use WeBWorK::Utils::Files qw(path_is_subdir);
+use WeBWorK::PG::SampleProblemParser qw(parseSampleProblem generateMetadata getSampleProblemCode getSearchData);
=head1 NAME
@@ -92,10 +88,10 @@ sub renderSampleProblem ($c) {
%{
parseSampleProblem(
$problemFile,
- metadata => $metadata,
- pod_root => $c->url_for('pod_viewer', filePath => 'macros'),
- pg_doc_home => $c->url_for('sample_problem_index'),
- macro_locations => \%macro_locations,
+ metadata => $metadata,
+ pod_base_url => $c->url_for('pod_viewer', filePath => 'macros'),
+ sample_problem_base_url => $c->url_for('sample_problem_index'),
+ macro_locations => \%macro_locations,
)
},
metadata => $metadata,
@@ -106,195 +102,7 @@ sub renderSampleProblem ($c) {
}
sub searchData ($c) {
- my $sampleProblemDir = $c->ce->{pg_dir} . '/tutorial/sample-problems';
-
- my $searchDataFile = Mojo::File->new($c->ce->{webworkDirs}{DATA})->child('sample-problem-search-data.json');
- my %files = map { $_->{filename} => $_ } @{ (eval { decode_json($searchDataFile->slurp('UTF-8')) } // []) };
- my @updatedFiles;
-
- # Process the sample problems in the sample problem directory.
- find(
- {
- wanted => sub {
- return unless $_ =~ /\.pg$/;
-
- my $file = Mojo::File->new($File::Find::name);
- my $lastModified = $file->stat->mtime;
-
- if ($files{$_}) {
- push(@updatedFiles, $files{$_});
- return if $files{$_}{lastModified} >= $lastModified;
- }
-
- my @fileContents = eval { split("\n", $file->slurp('UTF-8')) };
- return if $@;
-
- if (!$files{$_}) {
- $files{$_} = {
- type => 'sample problem',
- filename => $_,
- dir => $file->dirname->basename
- };
- push(@updatedFiles, $files{$_});
- }
- $files{$_}{lastModified} = $lastModified;
-
- my (%words, @kw, @macros, @subjects, $description);
-
- while (@fileContents) {
- my $line = shift @fileContents;
- if ($line =~ /^#:%\s*(\w+)\s*=\s*(.*)\s*$/) {
- # Store the name and subjects.
- $files{$_}{name} = $2 if $1 eq 'name';
- if ($1 eq 'subject') {
- @subjects = split(',\s*', $2 =~ s/\[(.*)\]/$1/r);
- }
- } elsif ($line =~ /^#:\s*(.*)?/) {
- my @newWords = $c->processLine($1);
- @words{@newWords} = (1) x @newWords if @newWords;
- } elsif ($line =~ /loadMacros\(/) {
- my $macros = $line;
- while ($line && $line !~ /\);\s*$/) {
- $line = shift @fileContents;
- $macros .= $line;
- }
- my @usedMacros =
- map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r);
-
- # Get the macros other than PGML.pl, PGstandard.pl, and PGcourse.pl.
- for my $m (@usedMacros) {
- push(@macros, $m) unless $m =~ /^(PGML|PGstandard|PGcourse)\.pl$/;
- }
- } elsif ($line =~ /##\s*KEYWORDS\((.*)\)/) {
- @kw = map {s/^'(.*)'$/$1/r} split(/,\s*/, $1);
- } elsif ($line =~ /^##\s*DESCRIPTION/) {
- $line = shift(@fileContents);
- while ($line && $line !~ /^##\s*ENDDESCRIPTION/) {
- $description .= ($line =~ s/^##\s+//r) . ' ';
- $line = shift(@fileContents);
- }
- $description =~ s/\s+$//;
- }
- }
-
- $files{$_}{description} = $description;
- $files{$_}{subjects} = \@subjects;
- $files{$_}{terms} = [ keys %words ];
- $files{$_}{keywords} = \@kw;
- $files{$_}{macros} = \@macros;
-
- return;
- }
- },
- $sampleProblemDir
- );
-
- # Process the POD in macros in the macros dir.
- (undef, my $macro_files) = Pod::Simple::Search->new->inc(0)->survey($c->ce->{pg_dir} . "/macros");
- for my $macroFile (sort keys %$macro_files) {
- next if $macroFile =~ /deprecated/;
-
- my $file = Mojo::File->new($macroFile);
- my $fileName = $file->basename;
- my $lastModified = $file->stat->mtime;
-
- if ($files{$fileName}) {
- push(@updatedFiles, $files{$fileName});
- next if $files{$fileName}{lastModified} >= $lastModified;
- }
-
- if (!$files{$fileName}) {
- $files{$fileName} = {
- type => 'macro',
- id => scalar(keys %files) + 1,
- filename => $fileName,
- dir => $file->dirname->to_rel($c->ce->{pg_dir})->to_string
- };
- push(@updatedFiles, $files{$fileName});
- }
- $files{$fileName}{lastModified} = $lastModified;
-
- my $root = Pod::Simple::SimpleTree->new->parse_file($file->to_string)->root;
-
- $files{$fileName}{terms} = $c->extractHeaders($root);
-
- if (my $nameDescription = extractHeadText($root, 'NAME')) {
- (undef, my $description) = split(/\s*-\s*/, $nameDescription, 2);
- $files{$fileName}{description} = $description if $description;
- }
- }
-
- # Redindex in case files were added or removed.
- my $count = 0;
- $_->{id} = ++$count for @updatedFiles;
-
- $searchDataFile->spew(encode_json(\@updatedFiles), 'UTF-8');
-
- return $c->render(json => \@updatedFiles);
-}
-
-# Get the stop words. The stop words file is loaded the first time this method is called,
-# and is stashed and returned in later calls.
-sub stopWords ($c) {
- return $c->stash->{stopWords} if $c->stash->{stopWords};
- $c->stash->{stopWords} = {};
-
- my $contents = eval { $c->app->home->child('assets', 'stop-words-en.txt')->slurp('UTF-8') };
- return $c->stash->{stopWords} if $@;
-
- for my $line (split("\n", $contents)) {
- chomp $line;
- next if $line =~ /^#/ || !$line;
- $c->stash->{stopWords}{$line} = 1;
- }
-
- return $c->stash->{stopWords};
-}
-
-sub processLine ($c, $line) {
- my %words;
-
- # Extract linked macros and problems.
- my @linkedFiles = $line =~ /(?:PODLINK|PROBLINK)\('([\w.]+)'\)/g;
- $words{$_} = 1 for @linkedFiles;
-
- # Replace any non-word characters with spaces.
- $line =~ s/\W/ /g;
-
- for my $word (split(/\s+/, $line)) {
- next if $word =~ /^\d*$/;
- $word = lc($word);
- $words{$word} = 1 if !$c->stopWords->{$word};
- }
- return keys %words;
-}
-
-# Extract the text for a section from the given POD with a section header title.
-sub extractHeadText ($root, $title) {
- my @index = grep { ref($root->[$_]) eq 'ARRAY' && $root->[$_][2] eq $title } 0 .. $#$root;
- return unless @index == 1;
-
- my $node = $root->[ $index[0] + 1 ];
- my $str = '';
- for (2 .. $#$node) {
- $str .= ref($node->[$_]) eq 'ARRAY' ? $node->[$_][2] : $node->[$_];
- }
- return $str;
-}
-
-# Extract terms form POD headers.
-sub extractHeaders ($c, $root) {
- my %terms =
- map { $_ => 1 }
- grep { $_ && !$c->stopWords->{$_} }
- map { split(/\s+/, $_) }
- map { lc($_) =~ s/\W/ /gr }
- map {
- grep { !ref($_) }
- @$_[ 2 .. $#$_ ]
- }
- grep { ref($_) eq 'ARRAY' && $_->[0] =~ /^head\d+$/ } @$root;
- return [ keys %terms ];
+ return $c->render(json => getSearchData($c->ce->{webworkDirs}{DATA} . '/sample-problem-search-data.json'));
}
1;
diff --git a/lib/WeBWorK/ContentGenerator/ShowMeAnother.pm b/lib/WeBWorK/ContentGenerator/ShowMeAnother.pm
index 39b164d4ab..6878ae8369 100644
--- a/lib/WeBWorK/ContentGenerator/ShowMeAnother.pm
+++ b/lib/WeBWorK/ContentGenerator/ShowMeAnother.pm
@@ -78,6 +78,7 @@ async sub pre_header_initialize ($c) {
effectivePermissionLevel => $db->getPermissionLevel($c->{effectiveUserID})->permission,
useMathQuill => $c->{will}{useMathQuill},
useMathView => $c->{will}{useMathView},
+ problemData => '{}'
},
);
@@ -116,6 +117,7 @@ async sub pre_header_initialize ($c) {
effectivePermissionLevel => $db->getPermissionLevel($c->{effectiveUserID})->permission,
useMathQuill => $c->{will}{useMathQuill},
useMathView => $c->{will}{useMathView},
+ problemData => '{}'
},
);
@@ -155,10 +157,9 @@ async sub pre_header_initialize ($c) {
}
# Disable options that are not applicable for showMeAnother.
- $c->{can}{recordAnswers} = 0;
- $c->{can}{checkAnswers} = 0; # This is turned on if the showMeAnother conditions are met below.
- $c->{can}{getSubmitButton} = 0;
- $c->{can}{showProblemGrader} = 0;
+ $c->{can}{recordAnswers} = 0;
+ $c->{can}{checkAnswers} = 0; # This is turned on if the showMeAnother conditions are met below.
+ $c->{can}{getSubmitButton} = 0;
if ($c->stash->{isPossible}) {
$c->{can}{showCorrectAnswers} =
@@ -206,34 +207,17 @@ async sub pre_header_initialize ($c) {
showMessages => !$showOnlyCorrectAnswers,
showCorrectAnswers => $showOnlyCorrectAnswers
|| ($c->{will}{checkAnswers} && $c->{will}{showCorrectAnswers}) ? 1 : 0,
- debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID})
+ debuggingOptions => getTranslatorDebuggingOptions($c->authz, $c->{userID}),
+ problemData => $c->{formFields}{problem_data} || '{}'
}
);
- # Warnings in the renderPG subprocess will not be caught by the global warning handler of this process.
- # So rewarn them and let the global warning handler take care of it.
- warn $pg->{warnings} if $pg->{warnings};
-
debug('end pg processing');
# Update and fix hint/solution options after PG processing
$c->{can}{showHints} &&= $pg->{flags}{hintExists};
$c->{can}{showSolutions} &&= $pg->{flags}{solutionExists};
- # Record errors
- $c->{pgdebug} = $pg->{debug_messages} if ref $pg->{debug_messages} eq 'ARRAY';
- $c->{pgwarning} = $pg->{warning_messages} if ref $pg->{warning_messages} eq 'ARRAY';
- $c->{pginternalerrors} = $pg->{internal_debug_messages} if ref $pg->{internal_debug_messages} eq 'ARRAY';
- # $c->{pgerrors} is defined if any of the above are defined, and is nonzero if any are non-empty.
- $c->{pgerrors} = @{ $c->{pgdebug} // [] } || @{ $c->{pgwarning} // [] } || @{ $c->{pginternalerrors} // [] }
- if defined $c->{pgdebug} || defined $c->{pgwarning} || defined $c->{pginternalerrors};
-
- # If $c->{pgerrors} is not defined, then the PG messages arrays were not defined,
- # which means $pg->{pgcore} was not defined and the translator died.
- warn 'Processing of this PG problem was not completed. Probably because of a syntax error. '
- . 'The translator died prematurely and no PG warning messages were transmitted.'
- unless defined $c->{pgerrors};
-
$c->{pg} = $pg;
return;
@@ -268,10 +252,7 @@ sub page_title ($c) {
# This uses the permission level and user id of the user assigned to the problem.
my $problemUser = $problem->user_id;
- if ($c->db->getPermissionLevel($problemUser)->permission >=
- $ce->{pg}{specialPGEnvironmentVars}{PRINT_FILE_NAMES_PERMISSION_LEVEL}
- || grep { $_ eq $problemUser } @{ $ce->{pg}{specialPGEnvironmentVars}{PRINT_FILE_NAMES_FOR} })
- {
+ if ($c->authz->hasPermissions($problemUser, 'print_path_to_problem')) {
$subheader .= ' ' . $problem->source_file;
}
diff --git a/lib/WeBWorK/DB.pm b/lib/WeBWorK/DB.pm
index 5ccf5e0eba..fc4e1b214a 100644
--- a/lib/WeBWorK/DB.pm
+++ b/lib/WeBWorK/DB.pm
@@ -436,13 +436,19 @@ sub abort_transaction {
BEGIN {
*User = gen_schema_accessor("user");
- *newUser = gen_new("user");
*countUsersWhere = gen_count_where("user");
*existsUserWhere = gen_exists_where("user");
*listUsersWhere = gen_list_where("user");
*getUsersWhere = gen_get_records_where("user");
}
+sub newUser {
+ my ($self, @data) = @_;
+ my $user = $self->{user}{record}->new(@data);
+ $user->accommodation_time_factor(1) unless defined $user->accommodation_time_factor;
+ return $user;
+}
+
sub countUsers { return scalar shift->listUsers(@_) }
# Note: This returns a list of user_ids for all users except set level proctors.
@@ -2227,11 +2233,8 @@ sub checkArgs {
if (defined $table) {
my $class = $self->{$table}{record};
- #print "arg=$arg class=$class\n";
croak "argument $pos must be of type $class"
- unless defined $arg
- and ref $arg
- and $arg->isa($class);
+ unless blessed $arg && $arg->isa($class);
eval { checkKeyfields($arg, $versioned) };
croak "argument $pos contains $@" if $@;
} else {
diff --git a/lib/WeBWorK/DB/Record/User.pm b/lib/WeBWorK/DB/Record/User.pm
index b712f3473d..5eeea5780e 100644
--- a/lib/WeBWorK/DB/Record/User.pm
+++ b/lib/WeBWorK/DB/Record/User.pm
@@ -12,20 +12,21 @@ use warnings;
BEGIN {
__PACKAGE__->_fields(
- user_id => { type => "VARCHAR(100) NOT NULL", key => 1 },
- first_name => { type => "TEXT" },
- last_name => { type => "TEXT" },
- email_address => { type => "TEXT" },
- student_id => { type => "TEXT" },
- status => { type => "TEXT" },
- section => { type => "TEXT" },
- recitation => { type => "TEXT" },
- comment => { type => "TEXT" },
- displayMode => { type => "TEXT" },
- showOldAnswers => { type => "INT" },
- useMathView => { type => "INT" },
- useMathQuill => { type => "INT" },
- lis_source_did => { type => "TEXT" },
+ user_id => { type => "VARCHAR(100) NOT NULL", key => 1 },
+ first_name => { type => "TEXT" },
+ last_name => { type => "TEXT" },
+ email_address => { type => "TEXT" },
+ student_id => { type => "TEXT" },
+ status => { type => "TEXT" },
+ accommodation_time_factor => { type => "FLOAT NOT NULL DEFAULT 1" },
+ section => { type => "TEXT" },
+ recitation => { type => "TEXT" },
+ comment => { type => "TEXT" },
+ displayMode => { type => "TEXT" },
+ showOldAnswers => { type => "INT" },
+ useMathView => { type => "INT" },
+ useMathQuill => { type => "INT" },
+ lis_source_did => { type => "TEXT" },
);
}
diff --git a/lib/WeBWorK/File/SetDef.pm b/lib/WeBWorK/File/SetDef.pm
index e137ddf544..1599b25848 100644
--- a/lib/WeBWorK/File/SetDef.pm
+++ b/lib/WeBWorK/File/SetDef.pm
@@ -197,8 +197,8 @@ sub importSetsFromDef ($ce, $db, $setDefFiles, $existingSets = undef, $assign =
showMeAnother => $rh_problem->{showMeAnother},
showHintsAfter => $rh_problem->{showHintsAfter},
prPeriod => $rh_problem->{prPeriod},
- attToOpenChildren => $rh_problem->{attToOpenChildren},
- countsParentGrade => $rh_problem->{countsParentGrade}
+ attToOpenChildren => $rh_problem->{att_to_open_children},
+ countsParentGrade => $rh_problem->{counts_parent_grade}
);
}
diff --git a/lib/WeBWorK/HTML/SingleProblemGrader.pm b/lib/WeBWorK/HTML/SingleProblemGrader.pm
index d10de75633..fa7bd24304 100644
--- a/lib/WeBWorK/HTML/SingleProblemGrader.pm
+++ b/lib/WeBWorK/HTML/SingleProblemGrader.pm
@@ -11,8 +11,9 @@ as a student.
use WeBWorK::Localize;
use WeBWorK::Utils 'wwRound';
+use WeBWorK::Utils::DateTime qw(before);
-sub new ($class, $c, $pg, $userProblem) {
+sub new ($class, $c, $pg, $userProblem, $mergedSet) {
$class = ref($class) || $class;
my $db = $c->db;
@@ -43,7 +44,12 @@ sub new ($class, $c, $pg, $userProblem) {
recorded_score => $recordedScore,
past_answer_id => $userPastAnswerID // 0,
comment_string => $comment,
- c => $c
+ c => $c,
+ # The grader needs to also save the sub_status if reduced scoring is not enabled,
+ # or if it is but it is before the reduced scoring date.
+ save_sub_status => !$c->ce->{pg}{ansEvalDefaults}{enableReducedScoring}
+ || !$mergedSet->enable_reduced_scoring
+ || before($mergedSet->reduced_scoring_date)
};
bless $self, $class;
diff --git a/lib/WeBWorK/HTML/StudentNav.pm b/lib/WeBWorK/HTML/StudentNav.pm
new file mode 100644
index 0000000000..afa7f3c058
--- /dev/null
+++ b/lib/WeBWorK/HTML/StudentNav.pm
@@ -0,0 +1,77 @@
+package WeBWorK::HTML::StudentNav;
+use Mojo::Base 'Exporter', -signatures;
+
+=head1 NAME
+
+WeBWorK::HTML::StudentNav - student navigation for all users assigned to a set.
+
+=cut
+
+our @EXPORT_OK = qw(studentNav);
+
+sub studentNav ($c, $setID) {
+ my $userID = $c->param('user');
+
+ return '' unless $c->authz->hasPermissions($userID, 'become_student');
+
+ # Find all users for the given set (except the current user) sorted by last_name, then first_name, then user_id.
+ my %users = map { $_->[0] => 1 } $c->db->listUserSetsWhere({ set_id => $setID, user_id => { '!=' => $userID } });
+ my @allUserRecords =
+ grep { $users{ $_->{user_id} } }
+ $c->db->getUsersWhere({ -and => { user_id => { not_like => 'set_id:%' } }, user_id => { '!=' => $userID } },
+ [qw/last_name first_name user_id/]);
+
+ return '' unless @allUserRecords;
+
+ my $eUserID = $c->param('effectiveUser');
+
+ my $filter = $c->param('studentNavFilter');
+
+ # Find the previous, current, and next users, and format the student names for display.
+ # Also create a hash of sections and recitations if there are any for the course.
+ my @userRecords;
+ my $currentUserIndex = 0;
+ my %filters;
+ for (@allUserRecords) {
+ # Add to the sections and recitations if defined. Also store the first user found in that section or
+ # recitation. This user will be switched to when the filter is selected.
+ my $section = $_->section;
+ $filters{"section:$section"} = [ $c->maketext('Filter by section [_1]', $section), $_->user_id ]
+ if $section && !$filters{"section:$section"};
+ my $recitation = $_->recitation;
+ $filters{"recitation:$recitation"} = [ $c->maketext('Filter by recitation [_1]', $recitation), $_->user_id ]
+ if $recitation && !$filters{"recitation:$recitation"};
+
+ # Only keep this user if it satisfies the selected filter if a filter was selected.
+ next
+ unless !$filter
+ || ($filter =~ /^section:(.*)$/ && $_->section eq $1)
+ || ($filter =~ /^recitation:(.*)$/ && $_->recitation eq $1);
+
+ $currentUserIndex = @userRecords if $_->user_id eq $eUserID;
+ push @userRecords, $_;
+
+ # Construct a display name.
+ $_->{displayName} = ($_->last_name || $_->first_name ? $_->last_name . ', ' . $_->first_name : $_->user_id);
+ }
+ my $prevUser = $currentUserIndex > 0 ? $userRecords[ $currentUserIndex - 1 ] : 0;
+ my $nextUser = $currentUserIndex < $#userRecords ? $userRecords[ $currentUserIndex + 1 ] : 0;
+
+ # Mark the current user.
+ $userRecords[$currentUserIndex]{currentUser} = 1;
+
+ # Set up the student nav.
+ return $c->include(
+ 'HTML/StudentNav/student_nav',
+ userID => $userID,
+ eUserID => $eUserID,
+ userRecords => \@userRecords,
+ currentUserIndex => $currentUserIndex,
+ prevUser => $prevUser,
+ nextUser => $nextUser,
+ filter => $filter,
+ filters => \%filters
+ );
+}
+
+1;
diff --git a/lib/WeBWorK/Localize/webwork2.pot b/lib/WeBWorK/Localize/webwork2.pot
index 0d3860417c..79054b475e 100644
--- a/lib/WeBWorK/Localize/webwork2.pot
+++ b/lib/WeBWorK/Localize/webwork2.pot
@@ -19,7 +19,7 @@ msgstr ""
msgid " (version %1)"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/ProblemSet.pm:263 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/ProblemSet.pm:282
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/ProblemSet.pm:278 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/ProblemSet.pm:297
msgid " Answers Available."
msgstr ""
@@ -38,6 +38,11 @@ msgstr ""
msgid "\"%1\" contains invalid characters."
msgstr ""
+#. (xml_escape($hardcopy_format)
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:134
+msgid "\"%1\" is not a valid hardcopy format."
+msgstr ""
+
#: /opt/webwork/webwork2/templates/HelpFiles/InstructorUserList.html.ep:127
msgid "\"Act as\" a student"
msgstr ""
@@ -74,7 +79,7 @@ msgstr ""
msgid "% Score with Review"
msgstr ""
-#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:501
+#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:506
msgid "% Score:"
msgstr ""
@@ -83,13 +88,28 @@ msgstr ""
msgid "%1 (%2 remaining)"
msgstr ""
+#. ($user->full_name, $user->user_id)
+#: /opt/webwork/webwork2/templates/ContentGenerator/Feedback/feedback_email.txt.ep:21
+msgid "%1 (%2) wrote:"
+msgstr ""
+
+#. ($user->status)
+#: /opt/webwork/webwork2/templates/ContentGenerator/Feedback/feedback_email.html.ep:165 /opt/webwork/webwork2/templates/ContentGenerator/Feedback/feedback_email.txt.ep:97
+msgid "%1 (unknown status abbreviation)"
+msgstr ""
+
#. ($c->maketext($self->name)
#: /opt/webwork/webwork2/lib/WeBWorK/AchievementItems.pm:121
msgid "%1 (unlimited reusability)"
msgstr ""
+#. ($_->{displayName}, $_->{setVersion})
+#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz/nav.html.ep:50
+msgid "%1 (version %2)"
+msgstr ""
+
#. ($properties{name})
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:1018
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:1008
msgid "%1 Help"
msgstr ""
@@ -100,12 +120,12 @@ msgid "%1 Icon"
msgstr ""
#. ($total)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm:2552
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm:2553
msgid "%1 OTP secrets copied."
msgstr ""
#. ($total)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm:2527
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm:2528
msgid "%1 OTP secrets reset."
msgstr ""
@@ -120,22 +140,22 @@ msgid "%1 Problems:"
msgstr ""
#. ('templates', 'html')
-#: /opt/webwork/webwork2/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep:180
+#: /opt/webwork/webwork2/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep:181
msgid "%1 and %2 folders"
msgstr ""
#. ($achievementID =~ s/_/ /gr)
-#: /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:333
+#: /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:309
msgid "%1 evaluator"
msgstr ""
#. ($achievementID =~ s/_/ /gr)
-#: /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:340
+#: /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:316
msgid "%1 notifications"
msgstr ""
#. ($count)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2141
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2120
msgid "%1 sets"
msgstr ""
@@ -145,13 +165,13 @@ msgid "%1 setting"
msgstr ""
#. ($count, $numUsers)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2127
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2106
msgid "%1 students out of %2"
msgstr ""
#. ($achievementItem->name, $message)
#: /opt/webwork/webwork2/lib/WeBWorK/AchievementItems.pm:93
-msgid "%1 successfuly used. %2"
+msgid "%1 successfully used. %2"
msgstr ""
#. ($rename_oldCourseID, $rename_oldCourseTitle, $rename_newCourseTitle, $rename_oldCourseInstitution, $rename_newCourseInstitution)
@@ -160,17 +180,17 @@ msgid "%1 title and institution changed from %2 to %3 and from %4 to %5"
msgstr ""
#. ($achievementID =~ s/_/ /gr)
-#: /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:347
+#: /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:323
msgid "%1 users"
msgstr ""
#. (scalar @userIDsToExport, "$dir/$fileName")
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm:458
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm:481
msgid "%1 users exported to file %2"
msgstr ""
#. ($numReplaced, $numAdded, $numSkipped, join(', ', @$skipped)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm:432
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/UserList.pm:455
msgid "%1 users replaced, %2 users added, %3 users skipped. Skipped users: (%4)"
msgstr ""
@@ -219,12 +239,12 @@ msgstr ""
#. ($c->tag('span', dir => 'ltr', format_set_name_display($setID)
#. ($c->tag('span', dir => 'ltr', $prettySetID)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Login.pm:27 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm:1028 /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:162
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Login.pm:27 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm:957 /opt/webwork/webwork2/templates/ContentGenerator/Base/links.html.ep:162
msgid "%1: Problem %2"
msgstr ""
#. ($c->tag('span', dir => 'ltr', format_set_name_display($c->stash('setID')
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/ShowMeAnother.pm:252
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/ShowMeAnother.pm:254
msgid "%1: Problem %2 Show Me Another"
msgstr ""
@@ -251,7 +271,7 @@ msgstr ""
#. (sprintf('%3.1f', $testTime)
#. ($timeLeft)
#. ($minutes)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/LTIUpdate.pm:93 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm:146 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm:164
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/LTIUpdate.pm:93 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm:145 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm:163
msgid "%quant(%1,minute)"
msgstr ""
@@ -291,43 +311,43 @@ msgid "(%quant(%1,item))"
msgstr ""
#. ($problemValue)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm:1040
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm:969
msgid "(%quant(%1,point))"
msgstr ""
-#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep:713
+#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep:714
msgid "(Any unsaved changes will be lost.)"
msgstr ""
-#: /opt/webwork/webwork2/templates/HelpFiles/InstructorAchievementNotificationEditor.html.ep:11 /opt/webwork/webwork2/templates/HelpFiles/InstructorPGProblemEditor.html.ep:137
+#: /opt/webwork/webwork2/templates/HelpFiles/InstructorAchievementNotificationEditor.html.ep:11 /opt/webwork/webwork2/templates/HelpFiles/InstructorPGProblemEditor.html.ep:123
msgid "(If an action cannot be executed it will not appear.)"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm:1221
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Problem.pm:1162
msgid "(This problem will not count toward your grade.)"
msgstr ""
-#: /opt/webwork/webwork2/templates/ContentGenerator/ProblemSets/set_list_row.html.ep:34
+#: /opt/webwork/webwork2/templates/ContentGenerator/ProblemSets/set_list_row.html.ep:33
msgid "(This set is hidden from students.)"
msgstr ""
-#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:359
+#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:342
msgid "(This test is overtime because it was not submitted in the allowed time.)"
msgstr ""
# $testNoun is either "test" or "submission"
#. ($testNoun, $c->formatDateTime($c->{set}->answer_date, $ce->{studentDateDisplayFormat})
-#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:170
+#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:151
msgid "(Your score on this %1 is not available until %2.)"
msgstr ""
# $testNoun is either "test" or "submission"
#. ($testNoun)
-#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:172
+#: /opt/webwork/webwork2/templates/ContentGenerator/GatewayQuiz.html.ep:153
msgid "(Your score on this %1 is not available.)"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1276 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1286
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1280 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1290
msgid "(correct)"
msgstr ""
@@ -335,24 +355,28 @@ msgstr ""
msgid "(in target set)"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1278 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1288
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1282 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1292
msgid "(incorrect)"
msgstr ""
#. ($pgScore)
#. ($recScore)
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1280 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1290
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1284 /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Hardcopy.pm:1294
msgid "(score %1)"
msgstr ""
+#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/ProblemSetList/import_form.html.ep:37
+msgid "(taken from filenames)"
+msgstr ""
+
#. ($versionID)
-#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/ProblemGrader.html.ep:164
+#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/ProblemGrader.html.ep:160
msgid "(version %1)"
msgstr ""
#. ($display_sort_method_name{$secondary_sort_method})
#. ($display_sort_method_name{$ternary_sort_method})
-#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/StudentProgress/set_progress.html.ep:141 /opt/webwork/webwork2/templates/ContentGenerator/Instructor/StudentProgress/set_progress.html.ep:144
+#: /opt/webwork/webwork2/templates/ContentGenerator/Instructor/StudentProgress/set_progress.html.ep:142 /opt/webwork/webwork2/templates/ContentGenerator/Instructor/StudentProgress/set_progress.html.ep:145
msgid ", then by %1"
msgstr ""
@@ -364,11 +388,11 @@ msgstr ""
msgid "0 seconds"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2137
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2116
msgid "1 set"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2123
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/Instructor/ProblemSetDetail.pm:2102
msgid "1 student"
msgstr ""
@@ -402,11 +426,11 @@ msgstr ""
msgid "Some servers handle courses taking place in different timezones. If this course is not showing the correct timezone, enter the correct value here. The format consists of unix times, such as \"America/New_York\", \"America/Chicago\", \"America/Denver\", \"America/Phoenix\" or \"America/Los_Angeles\".
Complete list: TimeZoneFiles "
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:982
+#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:985
msgid "This can be set to one of the dates associated with assignments, or \"Never\". For each assignment, if this setting is \"After the ... \" then if it is after the indicated date, WeBWorK will send scores. If this setting is \"Never\" then there is no date that will force WeBWorK to send scores and only the $LTISendGradesEarlyThreshold can cause scores to be sent. If scores are sent:
For 'course' grade passback mode, the assignment will be included in the overall course score calculation. For 'homework' grade passback mode, the assignment's score itself will be sent. If $LTISendScoresAfterDate is set to \"After the reduced scoring date\" and an assignment has no reduced scoring date or reduced scoring is disabled, the fallback is to use the close date.
For a given assignment, WeBWorK will still send a score to the LMS if the $LTISendGradesEarlyThreshold has been met, regardless of how $LTISendScoresAfterDate is set.
"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:1008
+#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:1011
msgid "This can either be set to a score or set to Attempted. When something triggers a potential grade passback, if it is earlier than $LTISendScoresAfterDate, the condition described by this variable must be met or else no score will be sent.
If this variable is a score, then the set will need to have a score that reaches or exceeds this score for its score to be sent to the LMS (or included in the 'course' score calculation). If this variable is set to Attempted, then the set needs to have been attempted for its score to be sent to the LMS (or included in the 'course' score calculation).
For a regular or jitar set, 'attempted' means that at least one exercise was attempted. For a test, 'attempted' means that either multiple versions exist or there is one version with a graded submission.
"
msgstr ""
@@ -414,7 +438,11 @@ msgstr ""
msgid "This sets whether the Reduced Scoring system will be enabled. If enabled you will need to set the default length of the reduced scoring period and the value of work done in the reduced scoring period below.
To use this, you also have to enable Reduced Scoring for individual assignments and set their Reduced Scoring Dates by editing the set data.
This works with the avg_problem_grader (which is the default grader) and the std_problem_grader (the all or nothing grader). It will work with custom graders if they are written appropriately.
"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:948
+#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:835
+msgid "When students click the Email Instructor button to send feedback, WeBWorK fills in the subject line. Here you can set the subject line. In it, you can have various bits of information filled in with the following escape sequences.
%c = course ID %u = user ID %s = set ID %p = problem ID %x = section %r = recitation %% = literal percent sign If content is between a brace pair, like '{ rec:%r}', then it will only be included in the subject line if all substitutions within the double brace pair are defined and nonempty."
+msgstr ""
+
+#: /opt/webwork/webwork2/lib/WeBWorK/ConfigValues.pm:951
msgid "
When this is true, any time WeBWorK is about to send a score to the LMS, it will first request from the LMS what that score currently is. Then if there is no significant difference between the LMS score and the WeBWorK score, WeBWorK will not follow through with updating the LMS score. This is to avoid frequent insignificant updates to a student score in the LMS. With some LMSs, students may receive notifications each time a score is updated, and setting this variable will prevent too many notifications for them. This does create a two-step process, first querying the current score from the LMS and then actually updating the score (if there is a significant difference). Additional details:
If the LMS score is not 100%, but the WeBWorK score is, then even if the LMS score is only insignificantly less than 100%, it will be updated anyway. If the LMS score is not set and the WeBWorK score is 0, this is considered a significant difference and the LMS score will updated to 0. However, the constraints of the $LTISendScoresAfterDate and the $LTISendGradesEarlyThreshold variables (described below) might apply, and the score may still not be updated in this case. \"Significant\" means an absolute difference of 0.001, or 0.1%. At this time this is not configurable. "
msgstr ""
@@ -422,7 +450,7 @@ msgstr ""
msgid "When viewing a problem, users may choose different methods of rendering formulas via an options box in the left panel. Here, you can adjust what display modes are listed.
The display modes are
plainText: shows the raw LaTeX strings for formulas. images: produces images using the external programs LaTeX and dvipng. MathJax: uses javascript to render mathematics. You must use at least one display mode. If you select only one, then the options box will not give a choice of modes (since there will only be one active).
"
msgstr ""
-#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm:1447
+#: /opt/webwork/webwork2/lib/WeBWorK/ContentGenerator/GatewayQuiz.pm:1451
msgid "Warning : There may be something wrong with a question in this test. Please inform your instructor including the warning messages below."
msgstr ""
@@ -451,7 +479,7 @@ msgid "