--- loncom/interface/lonhtmlcommon.pm	2007/05/13 18:03:15	1.159
+++ loncom/interface/lonhtmlcommon.pm	2024/08/22 18:43:03	1.358.2.19.2.10
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common html routines
 #
-# $Id: lonhtmlcommon.pm,v 1.159 2007/05/13 18:03:15 banghart Exp $
+# $Id: lonhtmlcommon.pm,v 1.358.2.19.2.10 2024/08/22 18:43:03 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -60,14 +60,201 @@ use Time::Local;
 use Time::HiRes;
 use Apache::lonlocal;
 use Apache::lonnet;
-use LONCAPA;
+use HTML::Entities();
+use LONCAPA qw(:DEFAULT :match);
+
+sub java_not_enabled {
+    if (($env{'browser.mobile'}) && ($env{'browser.mobile'} =~ /^ipad|ipod|iphone$/i)) {
+        return "\n".'<span class="LC_error">'.
+               &mt('The required Java applet could not be started, because Java is not supported by your mobile device.').
+               "</span>\n";
+    } else {
+        return "\n".'<span class="LC_error">'.
+               &mt('The required Java applet could not be started. Please make sure to have Java installed and active in your browser.').
+               "</span>\n";
+    }
+}
+
+sub coursepreflink {
+   my ($text,$category)=@_;
+   if (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) {
+      my $target =' target="_top"';
+      if (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self')) {
+          $target = '';
+      }
+      return '<a'.$target.' href="'.&HTML::Entities::encode("/adm/courseprefs?phase=display&actions=$category",'<>&"').'"><span class="LC_setting">'.$text.'</span></a>';
+   } else {
+      return '';
+   }
+}
+
+sub raw_href_to_link {
+   my ($message)=@_;
+   $message=~s/(https?\:\/\/[^\s\'\"\<]+)([\s\<]|$)/<a href="$1"><tt>$1<\/tt><\/a>$2/gi;
+   return $message;
+}
+
+sub entity_encode {
+    my ($text)=@_;
+    return &HTML::Entities::encode($text, '\'<>&"');
+}
+
+sub direct_parm_link {
+    my ($linktext,$symb,$filter,$part,$target)=@_;
+    $symb=&entity_encode($symb);
+    $filter=&entity_encode($filter);
+    $part=&entity_encode($part);
+    if (($symb) && (&Apache::lonnet::allowed('opa')) && ($target ne 'tex')) {
+        my $target=' target="_top"';
+        if (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self')) {
+            $target = '';
+        }
+       return "<a".$target." href='/adm/parmset?symb=$symb&amp;filter=$filter&amp;part=$part'><span class='LC_setting'>$linktext</span></a>";
+    } else {
+       return $linktext;
+    }
+}
+##############################################
+##############################################
+
+=item &confirm_success()
+
+Successful completion of an operation message
+
+=cut
+
+sub confirm_success {
+   my ($message,$failure)=@_;
+   if ($failure) {
+      return '<span class="LC_error" style="font-size: inherit;">'."\n"
+            .'<img src="/adm/lonIcons/navmap.wrong.gif" alt="'.&mt('Error').'" /> '."\n"
+            .$message."\n"
+            .'</span>'."\n";
+   } else {
+      return '<span class="LC_success">'."\n"
+            .'<img src="/adm/lonIcons/navmap.correct.gif" alt="'.&mt('OK').'" /> '."\n"
+            .$message."\n"
+            .'</span>'."\n";
+   }
+}
 
 ##############################################
 ##############################################
 
 =pod
 
-=item authorbombs
+=item &dragmath_button()
+
+Creates a button that launches a dragmath popup-window, in which an 
+expression can be edited and pasted as LaTeX into a specified textarea. 
+
+  textarea - Name of the textarea to edit.
+  helpicon - If true, show a help icon to the right of the button.
+
+=cut
+
+sub dragmath_button {
+    my ($textarea,$helpicon) = @_;
+    my $help_text; 
+    if ($helpicon) {
+        $help_text = &Apache::loncommon::help_open_topic('Authoring_Math_Editor',undef,undef,undef,undef,'mathhelpicon_'.$textarea);
+    }
+    my $buttontext=&mt('Edit Math');
+    return <<ENDDRAGMATH;
+                <input type="button" value="$buttontext" onclick="javascript:mathedit('$textarea',document)" />$help_text
+ENDDRAGMATH
+}
+
+##############################################
+
+=pod
+
+=item &dragmath_js()
+
+Javascript used to open pop-up window containing dragmath applet which 
+can be used to paste LaTeX into a textarea.
+
+=cut
+
+sub dragmath_js {
+    my ($popup) = @_;
+    return <<ENDDRAGMATHJS;
+                <script type="text/javascript">
+                // <![CDATA[
+                  function mathedit(textarea, doc) {
+                     targetEntry = textarea;
+                     targetDoc   = doc;
+                     newwin  = window.open("/adm/dragmath/$popup.html","","width=565,height=500,resizable");
+                  }
+                // ]]>
+                </script>
+
+ENDDRAGMATHJS
+}
+
+##############################################
+##############################################
+
+=pod
+
+=item &dependencies_button()
+
+Creates a button that launches a popup-window, in which dependencies  
+for the web page in the main window can be added to, replaced or deleted.  
+
+=cut
+
+sub dependencies_button {
+    my $buttontext=&mt('Manage Dependencies');
+    return <<"END";
+                <input type="button" value="$buttontext" onclick="javascript:dependencycheck();" />
+END
+}
+
+##############################################
+
+=pod
+
+=item &dependencycheck_js()
+
+Javascript used to open pop-up window containing interface to manage 
+dependencies for a web page uploaded diretcly to a course.
+
+=cut
+
+sub dependencycheck_js {
+    my ($symb,$title,$url,$folderpath,$uri) = @_;
+    my $link;
+    if ($symb) {
+        $link = '/adm/dependencies?symb='.&HTML::Entities::encode($symb,'<>&"');
+    } elsif ($folderpath) {
+        $link = '/adm/dependencies?folderpath='.&HTML::Entities::encode($folderpath,'<>&"');
+         $url = $uri;
+    } elsif ($uri =~ m{^/public/$match_domain/$match_courseid/syllabus$}) {
+        $link = '/adm/dependencies';
+    }
+    $link .= (($link=~/\?/)?'&amp;':'?').'title='.
+             &HTML::Entities::encode($title,'<>&"');
+    if ($url) {
+        $link .= '&url='.&HTML::Entities::encode($url,'<>&"');
+    }
+    return <<ENDJS;
+                <script type="text/javascript">
+                // <![CDATA[
+                  function dependencycheck() {
+                     depwin  = window.open("$link","","width=750,height=500,resizable,scrollbars=yes");
+                  }
+                // ]]>
+                </script>
+ENDJS
+}
+
+##############################################
+##############################################
+
+=pod
+
+=item &authorbombs()
 
 =cut
 
@@ -79,10 +266,10 @@ sub authorbombs {
     $url=&Apache::lonnet::declutter($url);
     my ($udom,$uname)=($url=~m{^($LONCAPA::domain_re)/($LONCAPA::username_re)/});
     my %bombs=&Apache::lonmsg::all_url_author_res_msg($uname,$udom);
-    foreach (keys %bombs) {
-	if ($_=~/^$udom\/$uname\//) {
+    foreach my $bomb (keys(%bombs)) {
+	if ($bomb =~ /^$udom\/$uname\//) {
 	    return '<a href="/adm/bombs/'.$url.
-		'"><img src="'.&Apache::loncommon::lonhttpdurl('/adm/lonMisc/bomb.gif').'" border="0" /></a>'.
+		'"><img src="'.&Apache::loncommon::lonhttpdurl('/adm/lonMisc/bomb.gif').'" alt="'.&mt('Bomb').'" border="0" /></a>'.
 		&Apache::loncommon::help_open_topic('About_Bombs');
 	}
     }
@@ -140,6 +327,10 @@ sub select_recent {
     foreach my $value (sort(keys(%recent))) {
 	unless ($value =~/^error\:/) {
 	    my $escaped = &Apache::loncommon::escape_url($value);
+	    &Apache::loncommon::inhibit_menu_check(\$escaped);
+            if ($area eq 'residx') {
+                next if ((!&Apache::lonnet::allowed('bre',$value)) && (!&Apache::lonnet::allowed('bro',$value)));
+            }
 	    $return.="\n<option value='$escaped'>".
 		&unescape((split(/\&/,$recent{$value}))[1]).
 		'</option>';
@@ -157,7 +348,7 @@ sub get_recent {
 # Begin filling return_hash with any 'always_include' option
     my %time_hash = ();
     my %return_hash = ();
-    foreach my $item (keys %recent) {
+    foreach my $item (keys(%recent)) {
         my ($thistime,$thisvalue)=(split(/\&/,$recent{$item}));
         if ($thistime eq 'always_include') {
             $return_hash{$item} = &unescape($thisvalue);
@@ -197,7 +388,7 @@ sub get_recent_frozen {
 
 =pod
 
-=item textbox
+=item &textbox()
 
 =cut
 
@@ -217,29 +408,29 @@ sub textbox {
 
 =pod
 
-=item checkbox
+=item &checkbox()
 
 =cut
 
 ##############################################
 ##############################################
 sub checkbox {
-    my ($name,$checked,$value) = @_;
+    my ($name,$checked,$value,$special) = @_;
     my $Str = '<input type="checkbox" name="'.$name.'" ';
     if (defined($value)) {
         $Str .= 'value="'.$value.'"';
     } 
     if ($checked) {
-        $Str .= ' checked="1"';
+        $Str .= ' checked="checked"';
     }
-    $Str .= ' />';
+    $Str .= $special.' />';
     return $Str;
 }
 
 
 =pod
 
-=item radiobutton
+=item &radiobutton()
 
 =cut
 
@@ -252,7 +443,7 @@ sub radio {
         $Str .= 'value="'.$value.'"';
     } 
     if ($checked eq $value) {
-        $Str .= ' checked="1"';
+        $Str .= ' checked="checked"';
     }
     $Str .= ' />';
     return $Str;
@@ -263,10 +454,10 @@ sub radio {
 
 =pod
 
-=item &date_setter
+=item &date_setter()
 
 &date_setter returns html and javascript for a compact date-setting form.
-To retrieve values from it, use &get_date_from_form().
+To retrieve values from it, use &get_date_from_form.
 
 Inputs
 
@@ -282,7 +473,8 @@ dname_hour, dname_min, and dname_sec.
 
 The current setting for this time parameter.  A unix format time
 (time in seconds since the beginning of Jan 1st, 1970, GMT.  
-An undefined value is taken to indicate the value is the current time.
+An undefined value is taken to indicate the value is the current time
+unless it is requested to leave it empty. See $includeempty.
 Also, to be explicit, a value of 'now' also indicates the current time.
 
 =item $special
@@ -292,10 +484,42 @@ the date_setter.  See lonparmset for exa
 
 =item $includeempty 
 
+If it is set (true) and no date/time value is provided,
+the date/time fields are left empty.
+
 =item $state
 
 Specifies the initial state of the form elements.  Either 'disabled' or empty.
-Defaults to empty, which indiciates the form elements are not disabled. 
+Defaults to empty, which indicates the form elements are not disabled.
+
+=item $no_hh_mm_ss
+
+If true, text boxes for hours, minutes and seconds are omitted.
+
+=item $defhour
+
+Default value for hours (a default of 0 is used otherwise).
+
+=item $defmin
+
+Default value for minutes (a default of 0 is used otherwise).
+
+=item defsec
+
+Default value for seconds (a default of 0 is used otherwise).
+
+=item $nolink
+
+If true, a "Select calendar" link (to pop-up a calendar) is not displayed
+to the right of the items.
+
+=item $no_mm_ss
+
+If true, text boxes for minutes and seconds are omitted.
+
+=item $no_ss
+
+If true, text boxes for seconds are omitted.
 
 =back
 
@@ -309,49 +533,45 @@ The method used to restrict user input w
 ##############################################
 sub date_setter {
     my ($formname,$dname,$currentvalue,$special,$includeempty,$state,
-        $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink) = @_;
-    my $wasdefined=1;
+        $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink,$no_mm_ss,$no_ss) = @_;
+    my $now = time;
+
+    my $tzname;
+    my ($sec,$min,$hour,$mday,$month,$year) = ('', '', undef,''.''.'');
+    #other potentially useful values:    wkday,yrday,is_daylight_savings
+
     if (! defined($state) || $state ne 'disabled') {
         $state = '';
+    } else {
+        $state = 'disabled="disabled"';
     }
     if (! defined($no_hh_mm_ss)) {
         $no_hh_mm_ss = 0;
     }
     if ($currentvalue eq 'now') {
-	$currentvalue=time;
+        $currentvalue = $now;
     }
-    if ((!defined($currentvalue)) || ($currentvalue eq '')) {
-	$wasdefined=0;
-	if ($includeempty) {
-	    $currentvalue = 0;
-	} else {
-	    $currentvalue = time;
-	}
+    
+    # Default value: Set empty date field to current time
+    # unless empty inclusion is requested
+    if ((!$includeempty) && (!$currentvalue)) {
+        $currentvalue = $now;
     }
-    # other potentially useful values:     wkday,yrday,is_daylight_savings
-    my ($sec,$min,$hour,$mday,$month,$year)=('','',undef,'','','');
+    # Do we have a date? Split it!
     if ($currentvalue) {
-	($sec,$min,$hour,$mday,$month,$year,undef,undef,undef) = 
-	    localtime($currentvalue);
-	$year += 1900;
-    }
-    unless ($wasdefined) {
-	if (($defhour) || ($defmin) || ($defsec)) {
-	    ($sec,$min,$hour,$mday,$month,$year,undef,undef,undef) = 
-		localtime(time);
-	    $year += 1900;
-	    $sec=($defsec?$defsec:0);
-	    $min=($defmin?$defmin:0);
-	    $hour=($defhour?$defhour:0);
-	} elsif (!$includeempty) {
-	    $sec=0;
-	    $min=0;
-	    $hour=0;
-	}
+	($tzname,$sec,$min,$hour,$mday,$month,$year) = &get_timedates($currentvalue);
+
+        #No values provided for hour, min, sec? Use default 0
+        if (($defhour) || ($defmin) || ($defsec)) {
+            $sec  = ($defsec  ? $defsec  : 0);
+            $min  = ($defmin  ? $defmin  : 0);
+            $hour = ($defhour ? $defhour : 0);
+        }
     }
     my $result = "\n<!-- $dname date setting form -->\n";
     $result .= <<ENDJS;
 <script type="text/javascript">
+// <![CDATA[
     function $dname\_checkday() {
         var day   = document.$formname.$dname\_day.value;
         var month = document.$formname.$dname\_month.value;
@@ -412,9 +632,10 @@ document.$formname.$dname\_year.value,
         }
 
     }
+// ]]>
 </script>
 ENDJS
-    $result .= '  <span style="white-space: nowrap;">';
+    $result .= '  <span class="LC_nobreak">';
     my $monthselector = qq{<select name="$dname\_month" $special $state onchange="javascript:$dname\_checkday()" >};
     # Month
     my @Months = qw/January February  March     April   May      June 
@@ -423,23 +644,23 @@ ENDJS
     unshift(@Months,'If you can read this an error occurred');
     if ($includeempty) { $monthselector.="<option value=''></option>"; }
     for(my $m = 1;$m <=$#Months;$m++) {
-        $monthselector .= qq{      <option value="$m" };
-        $monthselector .= "selected " if ($m-1 eq $month);
-        $monthselector .= '> '.&mt($Months[$m]).' </option>';
+        $monthselector .= qq{      <option value="$m"};
+        $monthselector .= ' selected="selected"' if ($m-1 eq $month);
+        $monthselector .= '> '.&mt($Months[$m]).' </option>'."\n";
     }
     $monthselector.= '  </select>';
     # Day
     my $dayselector = qq{<input type="text" name="$dname\_day" $state value="$mday" size="3" $special onchange="javascript:$dname\_checkday()" />};
     # Year
-    my $yearselector = qq{<input type="year" name="$dname\_year" $state value="$year" size="5" $special onchange="javascript:$dname\_checkday()" />};
+    my $yearselector = qq{<input type="text" name="$dname\_year" $state value="$year" size="5" $special onchange="javascript:$dname\_checkday()" />};
     #
     my $hourselector = qq{<select name="$dname\_hour" $special $state >};
     if ($includeempty) { 
         $hourselector.=qq{<option value=''></option>};
     }
     for (my $h = 0;$h<24;$h++) {
-        $hourselector .= qq{<option value="$h" };
-        $hourselector .= "selected " if (defined($hour) && $hour == $h);
+        $hourselector .= qq{<option value="$h"};
+        $hourselector .= ' selected="selected"' if (defined($hour) && $hour == $h);
         $hourselector .= ">";
         my $timest='';
         if ($h == 0) {
@@ -458,34 +679,73 @@ ENDJS
     my $minuteselector = qq{<input type="text" name="$dname\_minute" $special $state value="$min" size="3" />};
     my $secondselector= qq{<input type="text" name="$dname\_second" $special $state value="$sec" size="3" />};
     my $cal_link;
-    if (!$nolink) {
+    unless (($nolink) || ($state eq 'disabled')) {
         $cal_link = qq{<a href="javascript:$dname\_opencalendar()">};
     }
     #
+    my $tzone = ' '.$tzname.' ';
     if ($no_hh_mm_ss) {
         $result .= &mt('[_1] [_2] [_3] ',
-                       $monthselector,$dayselector,$yearselector);
-        if (!$nolink) {
-            $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
-        }
+                       $monthselector,$dayselector,$yearselector).
+                   $tzone;
+    } elsif ($no_mm_ss) {
+        $result .= &mt('[_1] [_2] [_3] [_4]',
+                      $monthselector,$dayselector,$yearselector,
+                      $hourselector).
+                   $tzone;
+    } elsif ($no_ss) {
+        $result .= &mt('[_1] [_2] [_3] [_4] [_5]m',
+                      $monthselector,$dayselector,$yearselector,
+                      $hourselector,$minuteselector).
+                   $tzone;
     } else {
         $result .= &mt('[_1] [_2] [_3] [_4] [_5]m [_6]s ',
                       $monthselector,$dayselector,$yearselector,
-                      $hourselector,$minuteselector,$secondselector);
-        if (!$nolink) {
-            $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
-        }
+                      $hourselector,$minuteselector,$secondselector).
+                   $tzone;
+    }
+    unless (($nolink) || ($state eq 'disabled')) {
+        $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
     }
     $result .= "</span>\n<!-- end $dname date setting form -->\n";
     return $result;
 }
 
+sub get_timedates {
+    my ($epoch) = @_;
+    my $dt = DateTime->from_epoch(epoch => $epoch)
+                     ->set_time_zone(&Apache::lonlocal::gettimezone());
+    my $tzname = $dt->time_zone_short_name();
+    my $sec = $dt->second;
+    my $min = $dt->minute;
+    my $hour = $dt->hour;
+    my $mday = $dt->day;
+    my $month = $dt->month;
+    if ($month) {
+        $month --;
+    }
+    my $year = $dt->year;
+    return ($tzname,$sec,$min,$hour,$mday,$month,$year);
+}
+
+sub build_url {
+    my ($base, $fields)=@_;
+    my $url;
+    $url = $base.'?';
+    foreach my $key (keys(%$fields)) {
+        $url.=&escape($key).'='.&escape($$fields{$key}).'&amp;';
+    }
+    $url =~ s/&amp;$//;
+    return $url;
+}
+
+
 ##############################################
 ##############################################
 
 =pod
 
-=item &get_date_from_form
+=item &get_date_from_form()
 
 get_date_from_form retrieves the date specified in an &date_setter form.
 
@@ -495,7 +755,7 @@ Inputs:
 
 =item $dname
 
-The name passed to &datesetter, which prefixes the form elements.
+The name passed to &date_setter, which prefixes the form elements.
 
 =item $defaulttime
 
@@ -548,20 +808,33 @@ sub get_date_from_form {
     if (defined($env{'form.'.$dname.'_month'})) {
         my $tmpmonth = $env{'form.'.$dname.'_month'};
         if (($tmpmonth =~ /^\d+$/) && ($tmpmonth > 0) && ($tmpmonth < 13)) {
-            $month = $tmpmonth - 1;
+            $month = $tmpmonth;
         }
     }
     if (defined($env{'form.'.$dname.'_year'})) {
         my $tmpyear = $env{'form.'.$dname.'_year'};
-        if (($tmpyear =~ /^\d+$/) && ($tmpyear > 1900)) {
-            $year = $tmpyear - 1900;
+        if (($tmpyear =~ /^\d+$/) && ($tmpyear >= 1970)) {
+            $year = $tmpyear;
         }
     }
-    if (($year<70) || ($year>137)) { return undef; }
+    if (($year<1970) || ($year>2037)) { return undef; }
     if (defined($sec) && defined($min)   && defined($hour) &&
-        defined($day) && defined($month) && defined($year) &&
-        eval('&timelocal($sec,$min,$hour,$day,$month,$year)')) {
-        return &timelocal($sec,$min,$hour,$day,$month,$year);
+        defined($day) && defined($month) && defined($year)) {
+        my $timezone = &Apache::lonlocal::gettimezone();
+        my $dt = DateTime->new( year   => $year,
+                                month  => $month,
+                                day    => $day,
+                                hour   => $hour,
+                                minute => $min,
+                                second => $sec,
+                                time_zone => $timezone,
+                              );
+        my $epoch_time  = $dt->epoch;
+        if ($epoch_time ne '') {
+            return $epoch_time;
+        } else {
+            return undef;
+        }
     } else {
         return undef;
     }
@@ -583,14 +856,14 @@ parameter setting wizard.
 ##############################################
 sub pjump_javascript_definition {
     my $Str = <<END;
-    function pjump(type,dis,value,marker,ret,call,hour,min,sec) {
-        parmwin=window.open("/adm/rat/parameter.html?type="+escape(type)
+    function pjump(type,dis,value,marker,ret,call,hour,min,sec,extra) {
+        openMyModal("/adm/rat/parameter.html?type="+escape(type)
                  +"&value="+escape(value)+"&marker="+escape(marker)
                  +"&return="+escape(ret)
                  +"&call="+escape(call)+"&name="+escape(dis)
                  +"&defhour="+escape(hour)+"&defmin="+escape(min)
-                 +"&defsec="+escape(sec),"LONCAPAparms",
-                 "height=350,width=350,scrollbars=no,menubar=no");
+                 +"&defsec="+escape(sec)+"&extra="+escape(extra)
+                 +"&modal=1",350,350,'no');
     }
 END
     return $Str;
@@ -631,6 +904,8 @@ sub javascript_nothing {
 ##############################################
 ##############################################
 sub javascript_docopen {
+    my ($mimetype) = @_;
+    $mimetype ||= 'text/html';
     # safari does not understand document.open() and loads "text/html"
     my $nothing = "''";
     my $user_browser;
@@ -644,7 +919,7 @@ sub javascript_docopen {
     if ($user_browser eq 'safari' && $user_os =~ 'mac') {
         $nothing = "document.clear()";
     } else {
-	$nothing = "document.open('text/html','replace')";
+	$nothing = "document.open('$mimetype','replace')";
     }
     return $nothing;
 }
@@ -681,7 +956,7 @@ Returns: a perl string as described.
 ##############################################
 ##############################################
 sub StatusOptions {
-    my ($status, $formName,$size,$onchange)=@_;
+    my ($status, $formName,$size,$onchange,$mult)=@_;
     $size = 1 if (!defined($size));
     if (! defined($status)) {
         $status = 'Active';
@@ -690,6 +965,9 @@ sub StatusOptions {
 
     my $Str = '';
     $Str .= '<select name="Status"';
+    if (defined($mult)){
+        $Str .= ' multiple="multiple" ';
+    }
     if(defined($formName) && $formName ne '' && ! defined($onchange)) {
         $Str .= ' onchange="document.'.$formName.'.submit()"';
     }
@@ -727,7 +1005,7 @@ of items completed and an estimate of th
 =over 4
 
 
-=item &Create_PrgWin
+=item &Create_PrgWin()
 
 Writes javascript to the client to open a progress window and returns a
 data structure used for bookkeeping.
@@ -738,27 +1016,17 @@ Inputs
 
 =item $r Apache request
 
-=item $title The title of the progress window
-
-=item $heading A description (usually 1 line) of the process being initiated.
-
 =item $number_to_do The total number of items being processed.
 
-=item $type Either 'popup' or 'inline' (popup is assumed if nothing is
-       specified)
-
-=item $width Specify the width in charaters of the input field.
-
-=item $formname Only useful in the inline case, if a form already exists, this needs to be used and specfiy the name of the form, otherwise the Progress line will be created in a new form of it's own
-
-=item $inputname Only useful in the inline case, if a form and an input of type text exists, use this to specify the name of the input field 
+=item $preamble Optional HTML to display before the progress bar.
 
 =back
 
 Returns a hash containing the progress state data structure.
+If $number_to_do is zero or null, an indeterminate progress bar will
+be used.
 
-
-=item &Update_PrgWin
+=item &Update_PrgWin()
 
 Updates the text in the progress indicator.  Does not increment the count.
 See &Increment_PrgWin.
@@ -778,9 +1046,9 @@ Inputs:
 Returns: none
 
 
-=item Increment_PrgWin
+=item Increment_PrgWin()
 
-Increment the count of items completed for the progress window by 1.  
+Increment the count of items completed for the progress window by $step or 1 if no step is provided.
 
 Inputs:
 
@@ -793,12 +1061,14 @@ Inputs:
 =item $extraInfo A description of the items being iterated over.  Typically
 'student'.
 
+=item $step (optional) counter step. Will be set to default 1 if ommited. step must be greater than 0 or empty.
+
 =back
 
 Returns: none
 
 
-=item Close_PrgWin
+=item &Close_PrgWin()
 
 Closes the progress window.
 
@@ -821,109 +1091,47 @@ Returns: none
 ########################################################
 ########################################################
 
-my $uniq=0;
-sub get_uniq_name {
-    $uniq++;
-    return 'uniquename'.$uniq;
-}
 
 # Create progress
 sub Create_PrgWin {
-    my ($r, $title, $heading, $number_to_do,$type,$width,$formname,
-	$inputname)=@_;
-    if (!defined($type)) { $type='popup'; }
-    if (!defined($width)) { $width=55; }
+    my ($r,$number_to_do,$preamble)=@_;
     my %prog_state;
-    $prog_state{'type'}=$type;
-    if ($type eq 'popup') {
-	$prog_state{'window'}='popwin';
-	my $start_page =
-	    &Apache::loncommon::start_page($title,undef,
-					   {'only_body' => 1,
-					    'bgcolor'   => '#88DDFF',
-					    'js_ready'  => 1});
-	my $end_page = &Apache::loncommon::end_page({'js_ready'  => 1});
-
-	#the whole function called through timeout is due to issues
-	#in mozilla Read BUG #2665 if you want to know the whole story
-	&r_print($r,'<script type="text/javascript">'.
-        "var popwin;
-         function openpopwin () {
-         popwin=open(\'\',\'popwin\',\'width=400,height=100\');".
-        "popwin.document.writeln(\'".$start_page.
-              "<h4>$heading<\/h4>".
-              "<form action= \"\" name=\"popremain\" method=\"post\">".
-              '<input type="text" size="'.$width.'" name="remaining" value="'.
-	      &mt('Starting').'" /><\\/form>'.$end_page.
-              "\');".
-        "popwin.document.close();}".
-        "\nwindow.setTimeout(openpopwin,0)</script>");
-	$prog_state{'formname'}='popremain';
-	$prog_state{'inputname'}="remaining";
-    } elsif ($type eq 'inline') {
-	$prog_state{'window'}='window';
-	if (!$formname) {
-	    $prog_state{'formname'}=&get_uniq_name();
-	    &r_print($r,'<form action="" name="'.$prog_state{'formname'}.'">');
-	} else {
-	    $prog_state{'formname'}=$formname;
-	}
-	if (!$inputname) {
-	    $prog_state{'inputname'}=&get_uniq_name();
-	    &r_print($r,$heading.' <input type="text" name="'.$prog_state{'inputname'}.
-		     '" size="'.$width.'" />');
-	} else {
-	    $prog_state{'inputname'}=$inputname;
-	    
-	}
-	if (!$formname) { &r_print($r,'</form>'); }
-	&Update_PrgWin($r,\%prog_state,&mt('Starting'));
-    }
-
     $prog_state{'done'}=0;
     $prog_state{'firststart'}=&Time::HiRes::time();
     $prog_state{'laststart'}=&Time::HiRes::time();
     $prog_state{'max'}=$number_to_do;
-    
+    &Apache::loncommon::LCprogressbar($r,$prog_state{'max'},$preamble); 
     return %prog_state;
 }
 
 # update progress
 sub Update_PrgWin {
     my ($r,$prog_state,$displayString)=@_;
-    &r_print($r,'<script type="text/javascript">'.$$prog_state{'window'}.'.document.'.
-	     $$prog_state{'formname'}.'.'.
-	     $$prog_state{'inputname'}.'.value="'.
-	     $displayString.'";</script>');
+    &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString,$$prog_state{'max'});
     $$prog_state{'laststart'}=&Time::HiRes::time();
 }
 
 # increment progress state
 sub Increment_PrgWin {
-    my ($r,$prog_state,$extraInfo)=@_;
-    $$prog_state{'done'}++;
+    my ($r,$prog_state,$extraInfo,$step)=@_;
+    $step = $step > 0 ? $step : 1;
+    $$prog_state{'done'} += $step;
+
+    # Catch (max modulo step) <> 0
+    my $current = $$prog_state{'done'};
+    my $last = ($$prog_state{'max'} - $current);
+    if ($last <= 0) {
+        $last = 1;
+        $current = $$prog_state{'max'};
+    }
+
     my $time_est= (&Time::HiRes::time() - $$prog_state{'firststart'})/
-        $$prog_state{'done'} *
-	($$prog_state{'max'}-$$prog_state{'done'});
+        $current * $last;
     $time_est = int($time_est);
     #
     my $min = int($time_est/60);
     my $sec = $time_est % 60;
-    # 
-    my $str;
-    if ($min == 0 && $sec > 1) {
-        $str = '[_2] seconds';
-    } elsif ($min == 1 && $sec > 1) {
-        $str = '1 minute [_2] seconds';
-    } elsif ($min == 1 && $sec < 2) {
-        $str = '1 minute';
-    } elsif ($min < 10 && $sec > 1) {
-        $str = '[_1] minutes, [_2] seconds';
-    } elsif ($min >= 10 || $sec < 2) {
-        $str = '[_1] minutes';
-    }
-    $time_est = &mt($str,$min,$sec);
-    #
+
     my $lasttime = &Time::HiRes::time()-$$prog_state{'laststart'};
     if ($lasttime > 9) {
         $lasttime = int($lasttime);
@@ -932,93 +1140,86 @@ sub Increment_PrgWin {
     } else {
         $lasttime = sprintf("%3.2f",$lasttime);
     }
-    if ($lasttime == 1) {
-        $lasttime = '('.$lasttime.' '.&mt('second for').' '.$extraInfo.')';
-    } else {
-        $lasttime = '('.$lasttime.' '.&mt('seconds for').' '.$extraInfo.')';
-    }
-    #
-    my $user_browser = $env{'browser.type'} if (exists($env{'browser.type'}));
-    my $user_os      = $env{'browser.os'}   if (exists($env{'browser.os'}));
-    if (! defined($user_browser) || ! defined($user_os)) {
-        (undef,$user_browser,undef,undef,undef,$user_os) = 
-                           &Apache::loncommon::decode_user_agent();
-    }
-    if ($user_browser eq 'explorer' && $user_os =~ 'mac') {
-        $lasttime = '';
+
+    $sec = 0 if ($min >= 10); # Don't show seconds if remaining time >= 10 min.
+    $sec = 1 if ( ($min == 0) && ($sec == 0) ); # Little cheating: pretend to have 1 second remaining instead of 0 to have something to display
+
+    my $timeinfo =
+        &mt('[_1]/[_2]:'
+           .' [quant,_3,minute,minutes,] [quant,_4,second ,seconds ,]remaining'
+           .' ([quant,_5,second] for '.$extraInfo.')',
+            $current,
+            $$prog_state{'max'},
+            $min,
+            $sec,
+            $lasttime);
+    my $percent=0;
+    if ($$prog_state{'max'}) {
+       $percent=int(100.*$current/$$prog_state{'max'});
     }
-    &r_print($r,'<script>'.$$prog_state{'window'}.'.document.'.
-	     $$prog_state{'formname'}.'.'.
-	     $$prog_state{'inputname'}.'.value="'.
-	     $$prog_state{'done'}.'/'.$$prog_state{'max'}.
-	     ': '.$time_est.' '.&mt('remaining').' '.$lasttime.'";'.'</script>');
+    &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo,$$prog_state{'max'});
     $$prog_state{'laststart'}=&Time::HiRes::time();
 }
 
 # close Progress Line
 sub Close_PrgWin {
     my ($r,$prog_state)=@_;
-    if ($$prog_state{'type'} eq 'popup') {
-	&r_print($r,'<script>popwin.close()</script>'."\n");
-    } elsif ($$prog_state{'type'} eq 'inline') {
-	&Update_PrgWin($r,$prog_state,&mt('Done'));
-    }
+    &Apache::loncommon::LCprogressbarClose($r);
     undef(%$prog_state);
 }
 
-sub r_print {
-    my ($r,$to_print)=@_;
-    if ($r) {
-	$r->print($to_print);
-	$r->rflush();
-    } else {
-	print($to_print);
-    }
-}
 
 # ------------------------------------------------------- Puts directory header
 
 sub crumbs {
-    my ($uri,$target,$prefix,$form,$size,$noformat,$skiplast)=@_;
-    if (! defined($size)) {
-        $size = '+2';
-    }
+    my ($uri,$target,$prefix,$form,$skiplast,$onclick)=@_;
+# You cannot crumbnify uploaded or adm resources
+    if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Community Content)'); }
     if ($target) {
         $target = ' target="'.
                   &Apache::loncommon::escape_single($target).'"';
     }
-    my $output='';
-    unless ($noformat) { $output.='<br /><tt><b>'; }
-    $output.='<font size="'.$size.'">'.$prefix.'/';
-    if ($env{'user.adv'}) {
-	my $path=$prefix.'/';
-	foreach my $dir (split('/',$uri)) {
+    my $output='<span class="LC_filename">';
+    $output.=$prefix.'/';
+    if (($env{'user.adv'}) || ($env{'user.author'})) {
+        my $path=$prefix.'/';
+        foreach my $dir (split('/',$uri)) {
             if (! $dir) { next; }
             $path .= $dir;
-	    if ($path eq $uri) {
-		if ($skiplast) {
-		    $output.=$dir;
+            if ($path eq $uri) {
+                if ($skiplast) {
+                    $output.=$dir;
                     last;
-		} 
-	    } else {
-		$path.='/'; 
-	    }	    
-            my $linkpath = &Apache::loncommon::escape_single($path);
-            if ($form) {
-		$linkpath=
-                    qq{javascript:$form.action='$linkpath';$form.submit();};
+                } 
+            } else {
+                $path.='/'; 
+            }
+            if ($path eq '/res/') {
+                unless (&Apache::lonnet::allowed('bre',$path)) {
+                    $output.="$dir/";
+                    next;
+                }
             }
             my $href_path = &HTML::Entities::encode($path,'<>&"');
-	    $output.=qq{<a href="$path" $target>$dir</a>/};
-	}
+            &Apache::loncommon::inhibit_menu_check(\$href_path);
+            if ($form) {
+                my $href = 'javascript:'.$form.".action='".$href_path."';".$form.'.submit();';
+                $output.=qq{<a href="$href"$onclick$target>$dir</a>/};
+            } else {
+                $output.=qq{<a href="$href_path"$onclick$target>$dir</a>/};
+            }
+        }
     } else {
-	foreach my $dir (split('/',$uri)) {
+        foreach my $dir (split('/',$uri)) {
             if (! $dir) { next; }
-	    $output.=$dir.'/';
-	}
+            $output.=$dir.'/';
+        }
     }
     if ($uri !~ m|/$|) { $output=~s|/$||; }
-    return $output.'</font>'.($noformat?'':'</b></tt><br />');
+    $output.='</span>';
+
+
+    return $output;
 }
 
 # --------------------- A function that generates a window for the spellchecker
@@ -1040,6 +1241,7 @@ sub spellheader {
     my $nothing=&javascript_nothing();
     return (<<ENDCHECK);
 <script type="text/javascript"> 
+// <![CDATA[
 //<!-- BEGIN LON-CAPA Internal
 var checkwin;
 
@@ -1050,6 +1252,7 @@ function spellcheckerwindow(string) {
     checkwin.document.close();
 }
 // END LON-CAPA Internal -->
+// ]]>
 </script>
 ENDCHECK
 }
@@ -1064,96 +1267,401 @@ sub spelllink {
 ENDLINK
 }
 
-# ------------------------------------------------- Output headers for HTMLArea
+# ------------------------------------------------- Output headers for CKEditor
 
-{
-    my @htmlareafields;
-    sub init_htmlareafields {
-	undef(@htmlareafields);
-    }
-    
-    sub add_htmlareafields {
-	my (@newfields) = @_;
-	push(@htmlareafields,@newfields);
-    }
+sub htmlareaheaders {
+	my $s="";
+	if (&htmlareabrowser()) {
+		$s.=(<<ENDEDITOR);
+<script type="text/javascript" src="/ckeditor/ckeditor.js"></script>
+ENDEDITOR
+	}
+    $s.=(<<ENDJQUERY);
+<script type="text/javascript" src="/adm/jQuery/js/jquery-3.7.1.min.js"></script>
+<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.13.3.custom.min.js"></script>
+<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.13.3.custom.min.css" />
+<script type="text/javascript" src="/adm/jpicker/js/jpicker-1.1.6.min.js" >
+</script>
+<link rel="stylesheet" type="text/css" href="/adm/jpicker/css/jPicker-1.1.6.min.css" />
+<script type="text/javascript" src="/adm/countdown/js/jquery.countdown.min.js"></script>
+<link rel="stylesheet" type="text/css" href="/adm/countdown/css/jquery.countdown.css" />
+
+<script type="text/javascript" src="/adm/spellchecker/js/jquery.spellchecker.min.js"></script>
+<link rel="stylesheet" type="text/css" href="/adm/spellchecker/css/spellchecker.css" />
+<script type="text/javascript" src="/adm/nicescroll/jquery.nicescroll.min.js"></script>
 
-    sub get_htmlareafields {
-	return @htmlareafields;
-    }
+ENDJQUERY
+	return $s;
 }
 
-sub htmlareaheaders {
-    if (&htmlareablocked()) { return ''; }
-    unless (&htmlareabrowser()) { return ''; }
+# ----------------------------------------------------------------- Preferences
+
+# ------------------------------------------------- lang to use in html editor
+sub htmlarea_lang {
     my $lang='en';
     if (&mt('htmlarea_lang') ne 'htmlarea_lang') {
 	$lang=&mt('htmlarea_lang');
     }
-    return (<<ENDHEADERS);
-<script type="text/javascript">
-_editor_url='/htmlarea/';
-_editor_lang='$lang';
-</script>
-<script type="text/javascript" src="/htmlarea/htmlarea.js"></script>
-<link rel="stylesheet" type="text/css" href="/htmlarea/htmlarea.css" />
-ENDHEADERS
+    return $lang;
 }
 
-# ------------------------------------------------- Activate additional buttons
-
-sub htmlareaaddbuttons {
-    if (&htmlareablocked()) { return ''; }
-    unless (&htmlareabrowser()) { return ''; }
-    return (<<ENDADDBUTTON);
-    var config=new HTMLArea.Config();
-    config.registerButton('ed_math','LaTeX Inline',
-			  '/htmlarea/images/ed_math.gif',false,
-			    function(editor,id) {
-			      editor.surroundHTML('&nbsp;<m>\$','\$</m>&nbsp;');
-			    }
-			  );
-    config.registerButton('ed_math_eqn','LaTeX Equation',
-			  '/htmlarea/images/ed_math_eqn.gif',false,
-			    function(editor,id) {
-			      editor.surroundHTML(
-				     '&nbsp;\\n<center><m>\\\\[','\\\\]</m></center>\\n&nbsp;');
-			    }
-			  );
-    config.toolbar.push(['ed_math','ed_math_eqn']);
-ENDADDBUTTON
+# return javacsript to activate elements of .colorchooser with jpicker:
+# Caller is responsible for enclosing this in <script> tags:
+#
+sub color_picker {
+    return '
+$(document).ready(function(){
+    $.fn.jPicker.defaults.images.clientPath="/adm/jpicker/images/";
+    $(".colorchooser").jPicker({window: { position: {x: "screenCenter", y: "bottom"}}});
+});';
 }
 
-# ----------------------------------------------------------------- Preferences
+# ----------------------------------------- Script to activate only some fields
 
-sub disablelink {
-    my @fields=@_;
-    if (defined($#fields)) {
-	unless ($#fields>=0) { return ''; }
+sub htmlareaselectactive {
+    my ($args) = @_; 
+    unless (&htmlareabrowser()) { return ''; }
+    my $output='<script type="text/javascript" defer="defer">'."\n"
+              .'// <![CDATA['."\n"
+              .'//<!-- BEGIN LON-CAPA Internal'."\n";
+    my $lang = &htmlarea_lang();
+    my $fullpage = 'false';
+    my ($dragmath_prefix,$dragmath_helpicon,$dragmath_whitespace);
+    if (ref($args) eq 'HASH') {
+        if (exists($args->{'lang'})) {
+            if ($args->{'lang'} ne '') {
+                $lang = $args->{'lang'};
+            }
+        }
+        if (exists($args->{'fullpage'})) { 
+            if ($args->{'fullpage'} eq 'true') {
+                $fullpage = $args->{'fullpage'};
+            }
+        }
+        if (exists($args->{'dragmath'})) {
+            if ($args->{'dragmath'} ne '') {
+                $dragmath_prefix = $args->{'dragmath'};
+                $dragmath_helpicon=&Apache::loncommon::lonhttpdurl("/adm/help/help.png");
+                $dragmath_whitespace=&Apache::loncommon::lonhttpdurl("/adm/lonIcons/transparent1x1.gif");
+            }
+        }
     }
-    return '<a href="'.&HTML::Entities::encode('/adm/preferences?action=set_wysiwyg&wysiwyg=off&returnurl=','<>&"').&escape($ENV{'REQUEST_URI'}).'">'.&mt('Disable WYSIWYG Editor').'</a>';
-}
 
-sub enablelink {
-    my @fields=@_;
-    if (defined($#fields)) {
-	unless ($#fields>=0) { return ''; }
+    my %lt = &Apache::lonlocal::texthash(
+              'plain'       => 'Plain text',
+              'rich'        => 'Rich formatting',
+              'plain_title' => 'Disable rich text formatting and edit in plain text',
+              'rich_title'  => 'Enable rich text formatting (bold, italic, etc.)',
+          );
+
+    $output.='
+    
+    function containsBlockHtml(id) {
+		var re = $("#"+id).html().search(/(?:\&lt\;|\<)(br|h1|h2|h3|h4|h5|h6|p|ol|ul|table|pre|address|blockquote|center|div)[\s]*((?:[\/]*[\s]*(?:\&gt\;|\>)|(?:\&gt\;|\>)[\s\S]*(?:\&lt\;|\<)\/[\s]*\1[\s]*\(?:\&gt\;|\>))/im);
+    	return (re >= 0);
     }
-    return '<a href="'.&HTML::Entities::encode('/adm/preferences?action=set_wysiwyg&wysiwyg=on&returnurl=','<>&"').&escape($ENV{'REQUEST_URI'}).'">'.&mt('Enable WYSIWYG Editor').'</a>';
-}
+    
+    function startRichEditor(id) {
+        // fix character entities inside <m>
+        // NOTE: this is not fixing characters inside <parse>
+        // NOTE: < and > inside <chem> should fix automatically because there should not be a letter after <.
+        var ta = document.getElementById(id);
+        var value = ta.value;
+        var in_m = false; // in the m element
+        var in_text = false; // in the text inside the m element
+        var im = -1; // position of <m>
+        var it = -1; // position of the text inside
+        for (var i=0; i<value.length; i++) {
+            if (value.substr(i, 2) == "<m") {
+                // ignore previous <m> if found twice
+                in_m = true;
+                in_text = false;
+                im = i;
+                it = -1;
+            } else if (in_m) {
+                if (!in_text) {
+                    if (value.charAt(i) == ">") {
+                        in_text = true;
+                        it = i+1;
+                    }
+                } else if (value.substr(i, 4) == "</m>") {
+                    in_m = false;
+                    var text = value.substr(it, i-it);
+                    var l1 = text.length;
+                    text = text.replace(/</g, "&lt;");
+                    text = text.replace(/>/g, "&gt;");
+                    var l2 = text.length;
+                    value = value.substr(0, it) + text + "</m>" + value.substr(i+4);
+                    i = i + (l2-l1);
+                }
+            }
+        }
+        ta.value = value;
+    	CKEDITOR.replace(id, 
+    		{
+    			customConfig: "/ckeditor/loncapaconfig.js",
+                        language : "'.$lang.'",
+                        fullPage : '.$fullpage.',
+    		}
+    	);
+    }
+    
+    function destroyRichEditor(id) {
+    	CKEDITOR.instances[id].destroy();
+        // replace character entities &lt; and &gt; in <m> and <chem>
+        // and "&amp;fctname(" by "&fctname("
+        // and the quotes inside functions: "&fct(1, &quot;a&quot;)" -> "&fct(1, "a")"
+        var ta = document.getElementById(id);
+        var value = ta.value;
+        var in_element = false; // in the m or chem element
+        var tagname = ""; // m or chem
+        var in_text = false; // in the text inside the element
+        var im = -1; // position of start tag
+        var it = -1; // position of the text inside
+        for (var i=0; i<value.length; i++) {
+            if (value.substr(i, 2) == "<m" || value.substr(i, 5) == "<chem") {
+                // ignore previous tags if found twice
+                in_element = true;
+                if (value.substr(i, 2) == "<m")
+                    tagname = "m";
+                else
+                    tagname = "chem";
+                in_text = false;
+                im = i;
+                it = -1;
+            } else if (in_element) {
+                if (!in_text) {
+                    if (value.charAt(i) == ">") {
+                        in_text = true;
+                        it = i+1;
+                    }
+                } else if (value.substr(i, 3+tagname.length) == "</"+tagname+">") {
+                    in_element = false;
+                    var text = value.substr(it, i-it);
+                    var l1 = text.length;
+                    text = text.replace(/&lt;/g, "<");
+                    text = text.replace(/&gt;/g, ">");
+                    var l2 = text.length;
+                    value = value.substr(0, it) + text + value.substr(i);
+                    i = i + (l2-l1);
+                }
+            }
+        }
+        // fix function names
+        value = value.replace(/&amp;([a-zA-Z_]+)\(/g, "&$1(");
+        // fix quotes in functions
+        var pos_next_fct = value.search(/&[a-zA-Z_]+\(/);
+        var depth = 0;
+        for (var i=0; i<value.length; i++) {
+            if (i == pos_next_fct) {
+                depth++;
+                var sub = value.substring(i+1);
+                var pos2 = sub.search(/&[a-zA-Z_]+\(/);
+                if (pos2 == -1)
+                    pos_next_fct = -1;
+                else
+                    pos_next_fct = i + 1 + pos2;
+            } else if (depth > 0) {
+                if (value.charAt(i) == ")")
+                    depth--;
+                else if (value.substr(i, 6) == "&quot;")
+                    value = value.substr(0, i) + "\"" + value.substr(i+6);
+            }
+        }
+        // replace the text value
+        ta.value = value;
+    }
+    
+    function editorHandler(event) {
+    	var rawid = $(this).attr("id");
+    	var id = new RegExp("LC_rt_(.*)").exec(rawid)[1];
+    	event.preventDefault();
+    	var rt_enabled  = $(this).hasClass("LC_enable_rt");
+        if (rt_enabled) {
+    		startRichEditor(id);
+			$("#LC_rt_"+id).html("<b>&laquo; '.$lt{'plain'}.'</b>");
+			$("#LC_rt_"+id).attr("title", "'.$lt{'plain_title'}.'");
+			$("#LC_rt_"+id).addClass("LC_disable_rt");
+			$("#LC_rt_"+id).removeClass("LC_enable_rt");
+    	} else {
+			destroyRichEditor(id);
+			$("#LC_rt_"+id).html("<b>'.$lt{'rich'}.' &raquo;</b>");
+			$("#LC_rt_"+id).attr("title", "'.$lt{'rich_title'}.'");
+			$("#LC_rt_"+id).addClass("LC_enable_rt");
+			$("#LC_rt_"+id).removeClass("LC_disable_rt");
+	}';
+    if ($dragmath_prefix ne '') {
+        $output .= "\n                 var visible = '';
+                                       if (rt_enabled) {
+                                           visible = 'none';
+                                       }
+                                       editmath_visibility(id,visible);\n";
+    }
+    $output .= '
+    }
+    $(document).ready(function(){
+		$(".LC_richAlwaysOn").each(function() {
+			startRichEditor($(this).attr("id"));
+		});
+		$(".LC_richDetectHtml").each(function() {
+			var id = $(this).attr("id");
+                        var rt_enabled = containsBlockHtml(id);
+			if(rt_enabled) {
+				$(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"'.$lt{'plain_title'}.'\" class=\"LC_disable_rt\"><b>&laquo; '.$lt{'plain'}.'</b></a></div>");				
+				startRichEditor(id);
+				$("#LC_rt_"+id).click(editorHandler);
+			}
+			else {
+				$(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"'.$lt{'rich_title'}.'\" class=\"LC_enable_rt\"><b>'.$lt{'rich'}.' &raquo;</b></a></div>");
+				$("#LC_rt_"+id).click(editorHandler);
+			}';
+    if ($dragmath_prefix ne '') {
+        $output .= "\n                 var visible = '';
+                                       if (rt_enabled) {
+                                           visible = 'none';
+                                       }
+                                       editmath_visibility(id,visible);\n";
+    }
+    $output .= '
+		});
+		$(".LC_richDefaultOn").each(function() {
+			var id = $(this).attr("id");
+			$(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"'.$lt{'plain_title'}.'\" class=\"LC_disable_rt\"><b>&laquo; '.$lt{'plain'}.'</b></a></div>");				
+			startRichEditor(id);
+			$("#LC_rt_"+id).click(editorHandler);
+		});
+		$(".LC_richDefaultOff").each(function() {
+			var id = $(this).attr("id");
+			$(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"'.$lt{'rich_title'}.'\" class=\"LC_enable_rt\"><b>'.$lt{'rich'}.' &raquo;</b></a></div>");
+			$("#LC_rt_"+id).click(editorHandler);
+		});
 
-# ----------------------------------------- Script to activate only some fields
 
-sub htmlareaselectactive {
-    my @fields=@_;
-    unless (&htmlareabrowser()) { return ''; }
-    if (&htmlareablocked()) { return '<br />'.&enablelink(@fields); }
-    my $output='<script type="text/javascript" defer="1">'.
-	&htmlareaaddbuttons();
-    foreach(@fields) {
-	$output.="\nHTMLArea.replace('$_',config);";
+	});
+';
+    $output .= &color_picker;
+
+    # Code to put a due date countdown in 'duedatecountdown' span.
+    # This is currently located in the breadcrumb headers.
+    # note that the dueDateLayout is internatinoalized below.
+    # Here document is used to support the substitution into the javascript below.
+    # ..which unforunately necessitates escaping the $'s in the javascript.
+    # There are several times of importance
+    #
+    # serverDueDate -  The absolute time at which the problem expires.
+    # serverTime    -  The server's time when the problem finished computing.
+    # clientTime    -  The client's time...as close to serverTime as possible.
+    #                  The clientTime will be slightly later due to
+    #                  1. The latency between problem computation and 
+    #                     the first network action.
+    #                  2. The time required between the page load-start and the actual
+    #                     initial javascript execution that got clientTime.
+    # These are used as follows:
+    #   The difference between clientTime and serverTime are used to 
+    #   correct for differences in clock settings between the browser's system and the
+    #   server's.
+    #
+    #   The difference between clientTime and the time at which the ready() method
+    #   starts executing is used to estimate latencies for page load and submission.
+    #   Since this is an estimate, it is doubled.  The latency estimate + one minute
+    #   is used to determine when the countdown timer turns red to warn the user
+    #   to think about submitting.
+
+    my $dueDateLayout = &mt('Due in: {dn} {dl} {hnn}{sep}{mnn}{sep}{snn} [_1]',
+                            "<span id='submitearly'></span>");
+    my $early = '- <b>'.&mt('Submit Early').'</b>';
+    my $pastdue = '- <b>'.&mt('Past Due').'</b>';
+    $output .= <<JAVASCRIPT;
+
+    var documentReadyTime;
+
+\$(document).ready(function() {
+   if (typeof(dueDate) != "undefined") {
+       documentReadyTime = (new Date()).getTime();
+      \$("#duedatecountdown").countdown({until: dueDate, compact: true, 
+         layout: "$dueDateLayout",
+         onTick: function (periods) {
+	    var latencyEstimate = (documentReadyTime - clientTime) * 2;
+            if(\$.countdown.periodsToSeconds(periods) < (300 + latencyEstimate)) {
+               \$("#submitearly").html("$early");
+               if (\$.countdown.periodsToSeconds(periods) < 1) {
+                    \$("#submitearly").html("$pastdue");
+               }
+            }
+            if(\$.countdown.periodsToSeconds(periods) < (60 + latencyEstimate)) {
+               \$(this).css("color", "red");   //Highlight last minute.
+            }
+         }
+      });
+   }
+});
+
+    /* This code describes the spellcheck options that will be used for
+       items with class 'spellchecked'.  It is necessary for those objects'
+       to explicitly request checking (e.g. onblur is a nice event for that).
+     */
+     \$(document).ready(function() {
+	 \$(".spellchecked").spellchecker({
+	   url: "/ajax/spellcheck",
+	   lang: "en",                      
+	   engine: "pspell",
+	   suggestionBoxPosition: "below",
+	   innerDocument: true
+					  });
+	 \$("textarea.spellchecked").spellchecker({
+	   url: "/ajax/spellcheck",
+	   lang: "en",                      
+	   engine: "pspell",
+	   suggestionBoxPosition: "below",
+	   innerDocument: true
+					  });
+
+			});
+
+    /* the muli colored editor can generate spellcheck with language 'none'
+       to disable spellcheck as well
+    */
+    function doSpellcheck(element, lang) {
+	if (lang != 'none') {
+ 	    \$(element).spellchecker('option', {lang: lang});
+	    \$(element).spellchecker('check');
+        }
+    }
+
+
+JAVASCRIPT
+    if ($dragmath_prefix ne '') {
+        $output .= '
+
+     function editmath_visibility(id,value) {
+
+         if ((id == "") || (id == null)) {
+             return;
+         }
+         var mathid = "'.$dragmath_prefix.'_"+id;
+         mathele = document.getElementById(mathid);
+         if (mathele == null) {
+             return;
+         }
+         mathele.style.display = value;
+         var mathhelpicon = "'.$dragmath_prefix.'helpicon'.'_"+id;
+         mathhelpiconele = document.getElementById(mathhelpicon);
+         if (mathhelpiconele == null) {
+             return;
+         }
+         if (value == "none") {
+             mathhelpiconele.src = "'.$dragmath_whitespace.'";
+         } else {
+             mathhelpiconele.src = "'.$dragmath_helpicon.'";
+         }
+     }
+';
+
     }
-    $output.="\nwindow.status='Activated Editfields';\n</script><br />".
-	&disablelink(@fields);
+    $output.="\nwindow.status='Activated Editfields';\n"
+            .'// END LON-CAPA Internal -->'."\n"
+            .'// ]]>'."\n"
+            .'</script>';
     return $output;
 }
 
@@ -1170,34 +1678,190 @@ sub htmlareabrowser {
     return 1;
 }
 
+#
+# Should the "return to content" link be shown?
+#
+
+sub show_return_link {
+
+    unless ($env{'request.course.id'}) { return 0; }
+    if ($env{'request.noversionuri'}=~m{^/priv/} ||
+        $env{'request.uri'}=~m{^/priv/}) { return 1; }
+    return if (($env{'request.noversionuri'} eq '/adm/supplemental') &&
+               ($env{'form.folder'} ne 'supplemental'));
+    return if (($env{'form.folderpath'} ne '') &&
+               (($env{'request.noversionuri'} =~ m{^/adm/$match_domain/$match_username/aboutme$}) ||
+                ($env{'request.noversionuri'} =~ m{^/public/$match_domain/$match_courseid/syllabus$})));
+    if (($env{'request.noversionuri'} =~ m{^/adm/viewclasslist($|\?)})
+        || ($env{'request.noversionuri'} =~ m{^/adm/.*/aboutme($|\?)})) {
+
+        return if ($env{'form.register'});
+    }
+    if ((($env{'request.symb'} ne '') || ($env{'form.folderpath'} ne '')) &&
+         ($env{'request.noversionuri'} =~m{^/adm/coursedocs/showdoc/uploaded/($match_domain)/($match_courseid)/(docs|supplemental)/})) {
+        my ($cdom,$cnum,$area) =  ($1,$2,$3);
+        if (($env{'course.'.$env{'request.course.id'}.'.domain'} eq $cdom) &&
+            ($env{'course.'.$env{'request.course.id'}.'.num'} eq $cnum)) {
+            if (($env{'request.symb'}) && ($area eq 'docs')) {
+                my ($map,$resid,$url) = &Apache::lonnet::decode_symb($env{'request.symb'});
+                return if ($env{'request.noversionuri'} eq '/adm/coursedocs/showdoc/'.$url);
+            } elsif (($env{'form.folderpath'}) && ($area eq 'supplemental')) {
+                return;
+            }
+        }
+    }
+    return (($env{'request.noversionuri'}=~m{^/(res|public)/} &&
+             $env{'request.symb'} eq '')
+            ||
+            ($env{'request.noversionuri'}=~ m{^/cgi-bin/printout.pl})
+            ||
+            (($env{'request.noversionuri'}=~/^\/adm\//) &&
+             ($env{'request.noversionuri'}!~/^\/adm\/wrapper\//) &&
+             ($env{'request.noversionuri'}!~
+              m{^/adm/.*/(smppg|bulletinboard|ext\.tool)($|\?)})
+           ));
+}
+
+
+##
+#   Set the dueDate variable...note this is done in the timezone
+#   of the browser.
+#
+# @param epoch relative time at which the problem is due.
+#
+# @return the javascript fragment to set the date:
+#
+sub set_due_date {
+    my $dueStamp = shift;
+    my $duems    = $dueStamp * 1000; # Javascript Date object needs ms not seconds.
+
+    my $now = time()*1000;
+
+    # This slightly obscure bit of javascript sets the dueDate variable
+    # to the time in the browser at which the problem was due.  
+    # The code should correct for gross differences between the server
+    # and client's time setting
+
+     return <<"END";
+
+<script type="text/javascript">
+  //<![CDATA[
+var serverDueDate = $duems;
+var serverTime    = $now;
+var clientTime    = (new Date()).getTime();
+var dueDate       = new Date(serverDueDate + (clientTime - serverTime));
+
+  //]]>
+</script>
+
+END
+}
+##
+# Sets the time at which the problem finished computing.
+# This just updates the serverTime and clientTime variables above.
+# Calling this in e.g. end_problem provides a better estimate of the
+# difference beetween the server and client time setting as 
+# the difference contains less of the latency/problem compute time.
+#
+sub set_compute_end_time {
+
+    my $now = time()*1000;	# Javascript times are in ms.
+    return <<"END";
+
+<script type="text/javascript">
+//<![CDATA[
+serverTime = $now;
+clientTime = (new Date()).getTime();
+//]]>
+</script>
+
+END
+}
+
+##
+# Client-side javascript to convert any dashes in text pasted
+# into textbox(es) for numericalresponse item(s) to a standard
+# minus, i.e., - . Calls to dash_to_minus_js() in end_problem()
+# and in loncommon::endbodytag() for a .page (arg: dashjs => 1)
+#
+# Will apply to any input tag with class: LC_numresponse_text.
+# Currently set in start_textline for numericalresponse items.
+#
+
+sub dash_to_minus_js {
+    return <<'ENDJS';
+
+<script type="text/javascript">
+//<![CDATA[
+//<!-- BEGIN LON-CAPA Internal
+document.addEventListener("DOMContentLoaded", (event) => {
+    const numresp = document.querySelectorAll("input.LC_numresponse_text");
+    if (numresp.length > 0) {
+        Array.from(numresp).forEach((el) => {
+            el.addEventListener("paste", (e) => {
+                e.preventDefault();
+                e.stopPropagation();
+                let p = (e.clipboardData || window.clipboardData).getData("text");
+                p.toString();
+                var regex;
+                try
+                {
+                    regex = new RegExp ("\\p{Dash}", "gu");
+                }
+                catch (e) { regex = new RegExp ("[\\u058A\\u05BE\\u1400\\u1806\\u2010-\\u2015\\u2E3A\\u2E3B\\u2E5D\\u301C\\uFE58\\uFE63\\uFF0D]","g"); }
+                p = p.replace(regex,'-');
+                putInText(p);
+            });
+        });
+    }
+    const putInText = (newText, el = document.activeElement) => {
+        const [start, end] = [el.selectionStart, el.selectionEnd];
+        el.setRangeText(newText, start, end, 'end');
+    }
+});
+// END LON-CAPA Internal -->
+//]]>
+</script>
+
+ENDJS
+}
+
 ############################################################
 ############################################################
 
 =pod
 
-=item breadcrumbs
+=item &breadcrumbs()
 
 Compiles the previously registered breadcrumbs into an series of links.
-FAQ and BUG links will be placed on the left side of the table if they
-are defined for the last registered breadcrumb.  
 Additionally supports a 'component', which will be displayed on the
-right side of the table (without a link).
+right side of the breadcrumbs enclosing div (without a link).
 A link to help for the component will be included if one is specified.
 
 All inputs can be undef without problems.
 
-Inputs: $component (the large text on the right side of the table),
-        $component_help
+Inputs: $component (the text on the right side of the breadcrumbs trail),
+        $component_help (the help item filename (without .tex extension).
         $menulink (boolean, controls whether to include a link to /adm/menu)
         $helplink (if 'nohelp' don't include the orange help link)
         $css_class (optional name for the class to apply to the table for CSS)
+        $no_mt (optional flag, 1 if &mt() is _not_ to be applied to $component
+           when including the text on the right.
+        $CourseBreadcrumbs (optional flag, 1 if &breadcrumbs called from &docs_breadcrumbs,
+           because breadcrumbs are being)
+        $topic_help (optional help item to be displayed on right side of the breadcrumbs 
+           row, using loncommon::help_open_topic() to generate the link.
+        $topic_help_text (text to include in the link in the optional help item 
+           on the right side of the breadcrumbs row.
+        $links_target optionally includes the target (_top, _parent or _self)
+
 Returns a string containing breadcrumbs for the current page.
 
-=item clear_breadcrumbs
+=item &clear_breadcrumbs()
 
 Clears the previously stored breadcrumbs.
 
-=item add_breadcrumb
+=item &add_breadcrumb()
 
 Pushes a breadcrumb on the stack of crumbs.
 
@@ -1214,66 +1878,120 @@ returns: nothing
 ############################################################
 {
     my @Crumbs;
+    my %tools = ();
     
     sub breadcrumbs {
-        my ($component,$component_help,$menulink,$helplink,$css_class) = @_;
-        #
-	$css_class ||= 'LC_breadcrumbs';
-        my $Str = "\n".'<table class="'.$css_class.'"><tr><td>';
+        my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, 
+            $CourseBreadcrumbs,$topic_help,$topic_help_text,$links_target) = @_;
         #
+        $css_class ||= 'LC_breadcrumbs';
+
         # Make the faq and bug data cascade
-        my $faq = '';
-        my $bug = '';
-	my $help='';
+        my $faq  = '';
+        my $bug  = '';
+        my $help = '';
+        # Crumb Symbol
+        my $crumbsymbol = '&raquo;';
         # The last breadcrumb does not have a link, so handle it separately.
         my $last = pop(@Crumbs);
         #
         # The first one should be the course or a menu link
-	if (!defined($menulink)) { $menulink=1; }
+        if (!defined($menulink)) { $menulink=1; }
+        if ($menulink) {
+            if ($env{'request.course.id'}) {
+                my ($menucoll,$deeplinkmenu,$menuref) = &Apache::loncommon::menucoll_in_effect();
+                if (($menucoll) && (ref($menuref) eq 'HASH')) {
+                    if ($menuref->{'main'} eq 'n') {
+                       undef($menulink);
+                    }
+                }
+            }
+        }
         if ($menulink) {
             my $description = 'Menu';
-            if (exists($env{'request.course.id'}) && 
-                $env{'request.course.id'} ne '') {
+            my $no_mt_descr = 0;
+            if ((exists($env{'request.course.id'})) && 
+                ($env{'request.course.id'} ne '') && 
+                ($env{'course.'.$env{'request.course.id'}.'.description'} ne '')) {
                 $description = 
                     $env{'course.'.$env{'request.course.id'}.'.description'};
+                $no_mt_descr = 1;
+                if ($env{'request.noversionuri'} =~ 
+                    m{^/?public/($match_domain)/($match_courseid)/syllabus$}) {
+                    unless (($env{'course.'.$env{'request.course.id'}.'.domain'} eq $1) &&
+                            ($env{'course.'.$env{'request.course.id'}.'.num'} eq $2)) {
+                        $description = 'Menu';
+                        $no_mt_descr = 0;
+                    }
+                }
             }
-            unshift(@Crumbs,{
-                    href   =>'/adm/menu',
-                    title  =>'Go to main menu',
-                    target =>'_top',
-                    text   =>$description,
-                });
-        }
-        my $links .= 
-            join('-&gt;',
-                 map {
-                     $faq = $_->{'faq'} if (exists($_->{'faq'}));
-                     $bug = $_->{'bug'} if (exists($_->{'bug'}));
-                     $help = $_->{'help'} if (exists($_->{'help'}));
-                     my $result = '<a href="'.$_->{'href'}.'" ';
-                     if (defined($_->{'target'}) && $_->{'target'} ne '') {
-                         $result .= 'target="'.$_->{'target'}.'" ';
-                     }
-		     if ($_->{'no_mt'}) {
-			 $result .='title="'.$_->{'title'}.'">'.
-			     $_->{'text'}.'</a>';
-		     } else {
-			 $result .='title="'.&mt($_->{'title'}).'">'.
-			     &mt($_->{'text'}).'</a>';
-		     }
-                     $result;
-                     } @Crumbs
-                 );
-        $links .= '-&gt;' if ($links ne '');
-	if ($last->{'no_mt'}) {
-	    $links .= '<b>'.$last->{'text'}.'</b>';
-	} else {
-	    $links .= '<b>'.&mt($last->{'text'}).'</b>';
-	}
-        #
+            my $target = '_top';
+            if ($links_target) {
+                $target = $links_target;
+            } elsif (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self')) {
+                $target = '';
+            }
+            $menulink =  {  href   =>'/adm/menu',
+                            title  =>'Go to main menu',
+                            target =>$target,
+                            text   =>$description,
+                            no_mt  =>$no_mt_descr, };
+            if($last) {
+                #$last set, so we have some crumbs
+                unshift(@Crumbs,$menulink);
+            } else {
+                #only menulink crumb present
+                $last = $menulink;
+            }
+        }
+        my $links;
+        if ((&show_return_link) && (!$CourseBreadcrumbs) && (ref($last) eq 'HASH')) {
+            my $alttext = &mt('Go Back');
+            my $hashref = { href => '/adm/flip?postdata=return:',
+                            title => &mt('Back to most recent content resource'),
+                            class => 'LC_menubuttons_link',
+                          };
+            if ($links_target) {
+                $hashref->{'target'} = $links_target;
+            }
+            $links=&htmltag( 'a','<img src="/res/adm/pages/tolastloc.png" alt="'.$alttext.'" class="LC_icon" />',
+                             $hashref);
+            $links=&htmltag('li',$links);
+        }
+        $links.= join "", 
+             map {
+                 $faq  = $_->{'faq'}  if (exists($_->{'faq'}));
+                 $bug  = $_->{'bug'}  if (exists($_->{'bug'}));
+                 $help = $_->{'help'} if (exists($_->{'help'}));
+
+                 my $result = $_->{no_mt} ? $_->{text} : &mt($_->{text});
+
+                 if ($_->{href}){
+                     $result = &htmltag( 'a', $result, 
+                       { href   => $_->{href},
+                         title  => $_->{no_mt} ? $_->{title} : &mt($_->{title}),
+                         target => $_->{target}, });
+                 }
+
+                 $result = &htmltag( 'li', "$result $crumbsymbol");
+             } @Crumbs;
+
+        #should the last Element be translated?
+
+        my $lasttext = $last->{'no_mt'} ? $last->{'text'} 
+                     : mt( $last->{'text'} );
+
+        # last breadcrumb is the first order heading of a page
+        # for course breadcrumbs it's just bold
+
+        if ($lasttext ne '') {
+            $links .= &htmltag( 'li', htmltag($CourseBreadcrumbs ? 'b' : 'h1',
+                    $lasttext), {title => $lasttext});
+        }
+
         my $icons = '';
-        $faq = $last->{'faq'} if (exists($last->{'faq'}));
-        $bug = $last->{'bug'} if (exists($last->{'bug'}));
+        $faq  = $last->{'faq'}  if (exists($last->{'faq'}));
+        $bug  = $last->{'bug'}  if (exists($last->{'bug'}));
         $help = $last->{'help'} if (exists($last->{'help'}));
         $component_help=($component_help?$component_help:$help);
 #        if ($faq ne '') {
@@ -1282,41 +2000,282 @@ returns: nothing
 #        if ($bug ne '') {
 #            $icons .= &Apache::loncommon::help_open_bug($bug);
 #        }
-	if ($faq ne '' || $component_help ne '' || $bug ne '') {
-	    $icons .= &Apache::loncommon::help_open_menu($component,
-							 $component_help,
-							 $faq,$bug);
-	}
-        #
-        $Str .= $links.'</td>';
-        #
-        if (defined($component)) {
-            $Str .= '<td class="'.$css_class.'_component">'.
-                &mt($component);
-	    if ($icons ne '') {
-		$Str .= '&nbsp;'.$icons;
-	    }
-	    $Str .= '</td>';
+        if ($faq ne '' || $component_help ne '' || $bug ne '') {
+            $icons .= &Apache::loncommon::help_open_menu($component,
+                                                         $component_help,
+                                                         $faq,$bug,'','','','',
+                                                         $links_target);
+        }
+        if ($topic_help && $topic_help_text) {
+           $icons .= ' '.&Apache::loncommon::help_open_topic($topic_help,&mt($topic_help_text),'',
+                                                             undef,600,'',$links_target);
         }
-        $Str .= '</tr></table>'."\n";
         #
+
+		
+        if ($links ne '') {
+            unless ($CourseBreadcrumbs) {
+                $links = &htmltag('ol',  $links, { id => "LC_MenuBreadcrumbs"   });
+            } else {
+                $links = &htmltag('ul',  $links, { class => "LC_CourseBreadcrumbs" });
+            }
+        }
+
+
+        if (($component) || ($topic_help && $topic_help_text)) {
+            $links = &htmltag('span', 
+                             ( $no_mt ? $component : mt($component) ).
+                             ( $icons ? $icons : '' ),
+                             { class => 'LC_breadcrumbs_component' } )
+                             .$links 
+;
+        }
+        my $nav_and_tools = 0;
+        foreach my $item ('navigation','tools') {
+            if (ref($tools{$item}) eq 'ARRAY') {
+                $nav_and_tools += scalar(@{$tools{$item}})
+            }
+        }
+        if (($links ne '') || ($nav_and_tools)) {
+            &render_tools(\$links);
+            $links = &htmltag('div', $links, 
+                              { id => "LC_breadcrumbs" }) unless ($CourseBreadcrumbs) ;
+        }
+        my $adv_tools = 0;
+        if (ref($tools{'advtools'}) eq 'ARRAY') {
+            $adv_tools = scalar(@{$tools{'advtools'}});
+        }
+        if (($links ne '') || ($adv_tools)) {
+            &render_advtools(\$links);
+        }
+
         # Return the @Crumbs stack to what we started with
         push(@Crumbs,$last);
         shift(@Crumbs);
-        #
-        return $Str;
+
+
+        # Return the breadcrumb's line
+
+    
+
+        return "$links";
     }
 
     sub clear_breadcrumbs {
         undef(@Crumbs);
+        undef(%tools);
     }
 
     sub add_breadcrumb {
-        push (@Crumbs,@_);
+        push(@Crumbs,@_);
+    }
+    
+=item &add_breadcrumb_tool($category, $html)
+
+Adds $html to $category of the breadcrumb toolbar container.
+
+$html is usually a link to a page that invokes a function on the currently 
+displayed data (e.g. print when viewing a problem)
+
+Currently there are 3 possible values for $category: 
+
+=over 
+
+=item navigation 
+left of breadcrumbs line
+
+=item tools 
+remaining items in right of breadcrumbs line
+
+=item advtools 
+advanced tools shown in a separate box below breadcrumbs line 
+
+=back
+ 
+returns: nothing
+
+=cut
+
+    sub add_breadcrumb_tool {
+        my ($category, @html) = @_;
+        return unless @html;
+        if (!keys(%tools)) { 
+            %tools = ( navigation => [], tools => [], advtools => []);
+        }
+
+        #this cleans data received from lonmenu::innerregister
+        @html = grep {defined $_ && $_ ne ''} @html;
+        for (@html) { 
+            s/align="(right|left)"//; 
+#            s/<span.*?\/span>// if $category ne 'advtools'; 
+        } 
+
+        push @{$tools{$category}}, @html;
+    }
+
+=item &clear_breadcrumb_tools()
+
+Clears the breadcrumb toolbar container.
+
+returns: nothing
+
+=cut
+
+    sub clear_breadcrumb_tools {
+        undef(%tools);
+    }
+
+=item &current_breadcrumb_tools()
+
+returns: a hash containing the current breadcrumb tools.
+
+=cut
+
+    sub current_breadcrumb_tools {
+        return %tools;
+    }
+
+=item &render_tools(\$breadcrumbs)
+
+Creates html for breadcrumb tools (categories navigation and tools) and inserts 
+\$breadcrumbs at the correct position.
+
+input: \$breadcrumbs - a reference to the string containing prepared 
+breadcrumbs.
+
+returns: nothing
+
+=cut
+
+#TODO might split this in separate functions for each category
+    sub render_tools {
+        my ($breadcrumbs) = @_;
+        return unless (keys(%tools));
+
+        my $navigation = list_from_array($tools{navigation}, 
+                   { listattr => { class=>"LC_breadcrumb_tools_navigation" } });
+        my $tools = list_from_array($tools{tools}, 
+                   { listattr => { class=>"LC_breadcrumb_tools_tools" } });
+        $$breadcrumbs = list_from_array([$navigation, $tools, $$breadcrumbs], 
+                   { listattr => { class=>'LC_breadcrumb_tools_outerlist' } });
+    }
+
+=pod
+
+=item &render_advtools(\$breadcrumbs)
+
+Creates html for advanced tools (category advtools) and inserts \$breadcrumbs 
+at the correct position.
+
+input: \$breadcrumbs - a reference to the string containing prepared 
+breadcrumbs (after render_tools call).
+
+returns: nothing
+
+=cut
+
+    sub render_advtools {
+        my ($breadcrumbs) = @_;
+        return unless     (defined $tools{'advtools'}) 
+                      and (scalar(@{$tools{'advtools'}}) > 0);
+
+        $$breadcrumbs .= Apache::loncommon::head_subbox(
+                            funclist_from_array($tools{'advtools'}) );
     }
 
 } # End of scope for @Crumbs
 
+sub docs_breadcrumbs {
+    my ($allowed,$crstype,$contenteditor,$title,$precleared,$checklinkprot)=@_;
+    my ($folderpath,@folders,$supplementalflag);
+    @folders = split('&',$env{'form.folderpath'});
+    if ($env{'form.folderpath'} =~ /^supplemental/) {
+        $supplementalflag = 1;
+    }
+    my $plain='';
+    my $container = 'sequence';
+    my ($randompick,$isencrypted,$ishidden,$is_random_order) = (-1,0,0,0);
+    my @docs_crumbs;
+    while (@folders) {
+        my $folder=shift(@folders);
+        my $foldername=shift(@folders);
+        if ($folderpath) {$folderpath.='&';}
+        $folderpath.=$folder.'&'.$foldername;
+        my $url = $env{'request.use_absolute'};
+        if ($allowed) {
+            $url .= '/adm/coursedocs?folderpath=';
+        } else {
+            $url .= '/adm/supplemental?folderpath=';
+        }
+        $url .= &escape($folderpath);
+        my $name=&unescape($foldername);
+# each of randompick number, hidden, encrypted, random order, is_page 
+# are appended with ":"s to the foldername
+        $name=~s/\:(\d*)\:(\w*)\:(\w*):(\d*)\:?(\d*)$//;
+        if ($contenteditor) {
+            if ($supplementalflag) {
+                if ($2) { $ishidden=1; }
+            } else {
+                if ($1 ne '') {
+                    $randompick=$1;
+                } else {
+                    $randompick=-1;
+                }
+                if ($2) { $ishidden=1; }
+                if ($3) { $isencrypted=1; }
+                if ($4 ne '') { $is_random_order = 1; }
+                if ($5 == 1) {$container = 'page'; }
+            }
+        }
+        if ($folder eq 'supplemental') {
+            $name = &mt('Supplemental Content');
+        }
+        if ($contenteditor) {
+            $plain.=$name.' &gt; ';
+        }
+        push(@docs_crumbs,
+                          {'href'  => $url,
+                           'title' => $name,
+                           'text'  => $name,
+                           'no_mt' => 1,
+                          });
+    }
+    if ($title) {
+        push(@docs_crumbs,
+                          {'title' => $title,
+                           'text'  => $title,
+                           'no_mt' => 1,}
+                          );
+    }
+    if (wantarray) {
+        unless ($precleared) {
+            &clear_breadcrumbs();
+        }
+        &add_breadcrumb(@docs_crumbs);
+        if ($contenteditor) {
+            $plain=~s/\&gt\;\s*$//;
+        }
+        my $menulink = 0;
+        if (!$allowed && !$contenteditor && !$supplementalflag) {
+            $menulink = 1;
+        }
+        if ($checklinkprot) {
+            if ($env{'request.deeplink.login'}) {
+                my $linkprotout = &Apache::lonmenu::linkprot_exit();
+                if ($linkprotout) {
+                    &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$linkprotout);
+                }
+            }
+        }
+        return (&breadcrumbs(undef,undef,$menulink,'nohelp',undef,undef,
+                             $contenteditor),
+                             $randompick,$ishidden,$isencrypted,$plain,
+                             $is_random_order,$container);
+    } else {
+        return \@docs_crumbs;
+    }
+}
+
 ############################################################
 ############################################################
 
@@ -1331,8 +2290,8 @@ returns: nothing
 # row1
 # row2
 # row3   ... etc.
-# &submit_row(0
-# &end_pickbox()
+# &submit_row()
+# &end_pick_box()
 #
 # where row1, row 2 etc. are chosen from &role_select_row,&course_select_row,
 # &status_select_row and &email_default_row
@@ -1373,40 +2332,60 @@ returns: nothing
 # routines, but can also be called directly to start and end rows which have 
 # needs that are not accommodated by the *_select_row() routines.    
 
+{ # Start: row_count block for pick_box
+my @row_count;
+
 sub start_pick_box {
-    my ($css_class) = @_;
+    my ($css_class,$id) = @_;
     if (defined($css_class)) {
 	$css_class = 'class="'.$css_class.'"';
     } else {
 	$css_class= 'class="LC_pick_box"';
     }
+    my $table_id;
+    if (defined($id)) {
+        $table_id = ' id="'.$id.'"';
+    }
+    unshift(@row_count,0);
     my $output = <<"END";
- <table $css_class>
+ <table $css_class $table_id>
 END
     return $output;
 }
 
 sub end_pick_box {
+    shift(@row_count);
     my $output = <<"END";
        </table>
 END
     return $output;
 }
 
+sub row_headline {
+    my $output = <<"END";
+           <tr><td colspan="2">
+END
+    return $output;
+}
+
 sub row_title {
-    my ($title,$css_title_class,$css_value_class) = @_;
+    my ($title,$css_title_class,$css_value_class, $css_value_furtherAttributes) = @_;
+    $row_count[0]++;
+    my $css_class = ($row_count[0] % 2)?'LC_odd_row':'LC_even_row';
     $css_title_class ||= 'LC_pick_box_title';
     $css_title_class = 'class="'.$css_title_class.'"';
 
     $css_value_class ||= 'LC_pick_box_value';
-    $css_value_class = 'class="'.$css_value_class.'"';
 
+    if ($title ne '') {
+        $title .= ':';
+    }
     my $output = <<"ENDONE";
-           <tr class="LC_pick_box_row">
+           <tr class="LC_pick_box_row" $css_value_furtherAttributes> 
             <td $css_title_class>
-	       $title:
+	       $title
             </td>
-            <td $css_value_class>
+            <td class="$css_value_class $css_class">
 ENDONE
     return $output;
 }
@@ -1428,14 +2407,20 @@ ENDTWO
     return $output;
 }
 
+} # End: row_count block for pick_box
+
 sub role_select_row {
     my ($roles,$title,$css_class,$show_separate_custom,$cdom,$cnum) = @_;
+    my $crstype = 'Course';
+    if ($cdom ne '' && $cnum ne '') {
+        $crstype = &Apache::loncommon::course_type($cdom.'_'.$cnum);
+    }
     my $output;
     if (defined($title)) {
         $output = &row_title($title,$css_class);
     }
     $output .= qq|
-                                  <select name="roles" multiple >\n|;
+                                  <select name="roles" multiple="multiple">\n|;
     foreach my $role (@$roles) {
         my $plrole;
         if ($role eq 'ow') {
@@ -1454,7 +2439,7 @@ sub role_select_row {
                 $plrole = &mt('Custom Role');
             }
         } else {
-            $plrole=&Apache::lonnet::plaintext($role);
+            $plrole=&Apache::lonnet::plaintext($role,$crstype);
         }
         if (($role ne 'cr') || (!$show_separate_custom)) {
             $output .= '  <option value="'.$role.'">'.$plrole.'</option>';
@@ -1469,10 +2454,18 @@ sub role_select_row {
 
 sub course_select_row {
     my ($title,$formname,$totcodes,$codetitles,$idlist,$idlist_titles,
-	$css_class) = @_;
+	$css_class,$crstype,$standardnames) = @_;
     my $output = &row_title($title,$css_class);
-    $output .= qq|
+    $output .= &course_selection($formname,$totcodes,$codetitles,$idlist,$idlist_titles,$crstype,$standardnames);
+    $output .= &row_closure();
+    return $output;
+}
+
+sub course_selection {
+    my ($formname,$totcodes,$codetitles,$idlist,$idlist_titles,$crstype,$standardnames) = @_;
+    my $output = qq|
 <script type="text/javascript">
+// <![CDATA[
     function coursePick (formname) {
         for  (var i=0; i<formname.coursepick.length; i++) {
             if (formname.coursepick[i].value == 'category') {
@@ -1495,18 +2488,29 @@ sub course_select_row {
             formname.courselist = '';
         }
     }
+// ]]>
 </script>
     |;
+
+    my ($allcrs,$pickspec);
+    if ($crstype eq 'Community') {
+        $allcrs = &mt('All communities');
+        $pickspec = &mt('Pick specific communities:');
+    } else {
+        $allcrs = &mt('All courses');
+        $pickspec = &mt('Pick specific course(s):');
+    }
+
     my $courseform='<b>'.&Apache::loncommon::selectcourse_link
-                     ($formname,'pickcourse','pickdomain','coursedesc','',1).'</b>';
-        $output .= '<input type="radio" name="coursepick" value="all" onclick="coursePick(this.form)" />'.&mt('All courses').'<br />';
+                     ($formname,'pickcourse','pickdomain','coursedesc','',1,$crstype).'</b>';
+        $output .= '<label><input type="radio" name="coursepick" value="all" onclick="coursePick(this.form)" />'.$allcrs.'</label><br />';
     if ($totcodes > 0) {
         my $numtitles = @$codetitles;
         if ($numtitles > 0) {
-            $output .= '<input type="radio" name="coursepick" value="category" onclick="coursePick(this.form);alert('."'".&mt('Choose categories, from left to right')."'".')" />'.&mt('Pick courses by category:').' <br />';
+            $output .= '<label><input type="radio" name="coursepick" value="category" onclick="coursePick(this.form);alert('."'".&html_escape(&mt('Choose categories, from left to right'))."'".')" />'.&mt('Pick courses by category:').'</label><br />';
             $output .= '<table><tr><td>'.$$codetitles[0].'<br />'."\n".
-               '<select name="'.$$codetitles[0].
-               '" onChange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n".
+               '<select name="'.$standardnames->[0].
+               '" onchange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n".
                ' <option value="-1" />Select'."\n";
             my @items = ();
             my @longitems = ();
@@ -1535,8 +2539,8 @@ sub course_select_row {
             $output .= '</select></td>';
             for (my $i=1; $i<$numtitles; $i++) {
                 $output .= '<td>'.$$codetitles[$i].'<br />'."\n".
-                          '<select name="'.$$codetitles[$i].
-                          '" onChange="courseSet('."'$$codetitles[$i]'".')">'."\n".
+                          '<select name="'.$standardnames->[$i].
+                          '" onchange="courseSet('."'$$codetitles[$i]'".')">'."\n".
                           '<option value="-1">&lt;-Pick '.$$codetitles[$i-1].'</option>'."\n".
                           '</select>'."\n".
                           '</td>';
@@ -1544,8 +2548,15 @@ sub course_select_row {
             $output .= '</tr></table><br />';
         }
     }
-    $output .= '<input type="radio" name="coursepick" value="specific" onclick="coursePick(this.form);opencrsbrowser('."'".$formname."'".','."'".'dccourse'."'".','."'".'dcdomain'."'".','."'".'coursedesc'."','','1'".')" />'.&mt('Pick specific course(s):').' '.$courseform.'&nbsp;&nbsp;<input type="text" value="0" size="4" name="coursetotal" /><input type="hidden" name="courselist" value="" />selected.<br />'."\n";
-    $output .= &row_closure();
+    $output .=
+        '<label><input type="radio" name="coursepick" value="specific"'
+       .' onclick="coursePick(this.form);opencrsbrowser('."'".$formname."','dccourse','dcdomain','coursedesc','','1','$crstype'".')" />'
+       .$pickspec.'</label>'
+       .' '.$courseform.'&nbsp;&nbsp;'
+       .&mt('[_1] selected.',
+                '<input type="text" value="0" size="4" name="coursetotal" readonly="readonly" />'
+               .'<input type="hidden" name="courselist" value="" />')
+       .'<br />'."\n";
     return $output;
 }
 
@@ -1556,7 +2567,7 @@ sub status_select_row {
         $output = &row_title($title,$css_class,'LC_pick_box_select');
     }
     $output .= qq|
-                                    <select name="types" multiple>\n|;
+                                    <select name="types" multiple="multiple">\n|;
     foreach my $status_type (sort(keys(%{$types}))) {
         $output .= '  <option value="'.$status_type.'">'.$$types{$status_type}.'</option>';
     }
@@ -1570,7 +2581,6 @@ sub status_select_row {
 sub email_default_row {
     my ($authtypes,$title,$descrip,$css_class) = @_;
     my $output = &row_title($title,$css_class);
-    my @rowcols = ('#eeeeee','#dddddd');
     $output .= $descrip.
 	&Apache::loncommon::start_data_table().
 	&Apache::loncommon::start_data_table_header_row().
@@ -1629,9 +2639,119 @@ sub course_custom_roles {
 }
 
 
+sub resource_info_box {
+   my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp,$divforres)=@_;
+   my $return='';
+   if (($stuvcurrent ne '') || ($divforres)) {
+       $return = '<div class="LC_left_float">';
+   }
+   if ($symb) {
+       $return.=&Apache::loncommon::start_data_table();
+       my ($map,$id,$resource)=&Apache::lonnet::decode_symb($symb);
+       my $folder=&Apache::lonnet::gettitle($map);
+       $return.=&Apache::loncommon::start_data_table_row().
+                    '<th align="left">'.&mt('Folder:').'</th><td>'.$folder.'</td>'.
+                    &Apache::loncommon::end_data_table_row();
+       unless ($onlyfolderflag) {
+          $return.=&Apache::loncommon::start_data_table_row().
+                    '<th align="left">'.&mt('Resource:').'</th><td>'.&Apache::lonnet::gettitle($symb).'</td>'.
+                    &Apache::loncommon::end_data_table_row();
+       }
+       if ($stuvcurrent ne '') {
+           $return .= &Apache::loncommon::start_data_table_row().
+                    '<th align="left">'.&mt("Student's current version:").'</th><td>'.$stuvcurrent.'</td>'.
+                    &Apache::loncommon::end_data_table_row();
+       }
+       if ($stuvdisp ne '') {
+           $return .= &Apache::loncommon::start_data_table_row().
+                    '<th align="left">'.&mt("Student's version displayed:").'</th><td>'.$stuvdisp.'</td>'.
+                    &Apache::loncommon::end_data_table_row();
+       }
+       $return.=&Apache::loncommon::end_data_table();
+    } else {
+       $return='<p><span class="LC_error">'.&mt('No context provided.').'</span></p>';
+    }
+    if (($stuvcurrent ne '') || ($divforres)) {
+        $return .= '</div>';
+    }
+    return $return;
+}
+
+# display_usage
+# 
+# Generates a div containing a block, filled to show percentage of current quota used
+#
+# Quotas available for user portfolios, group portfolios, authoring spaces, and course
+# content stored directly within a course (i.e., excluding published content).
+#
+
+sub display_usage {
+    my ($current_disk_usage,$disk_quota,$context) = @_;
+    my $usage = $current_disk_usage/1024;
+    my $quota = $disk_quota/1024;
+    my $percent;
+    if ($disk_quota == 0) {
+        $percent = 100.0;
+    } else {
+        $percent = 100*($current_disk_usage/$disk_quota);
+    }
+    $usage = sprintf("%.2f",$usage);
+    $quota = sprintf("%.2f",$quota);
+    $percent = sprintf("%.0f",$percent);
+    my ($color,$cssclass);
+    if ($percent <= 60) {
+        $color = '#00A000';
+    } elsif ($percent > 60 && $percent < 90) {
+        $color = '#FFD300';
+        $cssclass = 'class="LC_warning"';
+    } elsif( $percent >= 90) {
+        $color = '#FF0000';
+        $cssclass = 'class="LC_error"';
+    }
+    my $prog_width = $percent;
+    if ($prog_width > 100) {
+        $prog_width = 100;
+    }
+    my $display = 'block';
+    if ($context eq 'authoring') {
+        $display = 'inline';
+    }
+    return '
+  <div id="meter1" align="left" style="display:'.$display.'" '.$cssclass.'>'.&mt('Currently using [_1] of the [_2] available.',$usage.' MB <span style="font-weight:bold;">('.$percent.'%)</span>',$quota.' MB')."\n".
+'   <div id="meter2" style="display:block; margin-top:3px; margin-bottom:3px; margin-left:0px; margin-right:0px; width:400px; border:1px solid #000000; height:10px;">'."\n".
+'    <div id="meter3" style="display:block; background-color:'.$color.'; width:'.$prog_width.'%; height:10px; color:#000000; margin:0px;"></div>'."\n".
+'   </div>'."\n".
+'  </div>';
+}
+
+##############################################
+##############################################
+
+# topic_bar
+#
+# Generates a div containing an (optional) number with a white background followed by a 
+# title with a background color defined in the corresponding CSS: LC_topic_bar
+# Inputs:
+# 1. number to display.
+#    If input for number is empty only the title will be displayed. 
+# 2. title text to display.
+# 3. optional id for the <div>
+# Outputs - a scalar containing html mark-up for the div.
+
+sub topic_bar {
+    my ($num,$title,$id) = @_;
+    my $number = '';
+    if ($num ne '') {
+        $number = '<span>'.$num.'</span>';
+    }
+    if ($id ne '') {
+        $id = 'id="'.$id.'"';
+    }
+    return '<div class="LC_topic_bar" '.$id.'>'.$number.$title.'</div>';
+}
+
 ##############################################
 ##############################################
-                                                                             
 # echo_form_input
 #
 # Generates html markup to add form elements from the referrer page
@@ -1664,30 +2784,30 @@ sub echo_form_input {
         if ($key =~ /^form\.(.+)$/) {
             my $name = $1;
             my $match = 0;
-            if ((!@{$excluded}) || (!grep/^$name$/,@{$excluded})) {
-                if (defined($regexps)) {
-                    if (@{$regexps} > 0) {
-                        foreach my $regexp (@{$regexps}) {
-                            if ($name =~ /\Q$regexp\E/) {
-                                $match = 1;
-                                last;
-                            }
+            if (ref($excluded) eq 'ARRAY') {    
+                next if (grep(/^\Q$name\E$/,@{$excluded}));
+            }
+            if (ref($regexps) eq 'ARRAY') {
+                if (@{$regexps} > 0) {
+                    foreach my $regexp (@{$regexps}) {
+                        if ($name =~ /$regexp/) {
+                            $match = 1;
+                            last;
                         }
                     }
                 }
-                if (!$match) {
-                    if (ref($env{$key})) {
-                        foreach my $value (@{$env{$key}}) {
-                            $value = &HTML::Entities::encode($value,'<>&"');
-                            $output .= '<input type="hidden" name="'.$name.
-                                             '" value="'.$value.'" />'."\n";
-                        }
-                    } else {
-                        my $value = &HTML::Entities::encode($env{$key},'<>&"');
-                        $output .= '<input type="hidden" name="'.$name.
-                                             '" value="'.$value.'" />'."\n";
-                    }
+            }
+            next if ($match);
+            if (ref($env{$key}) eq 'ARRAY') {
+                foreach my $value (@{$env{$key}}) {
+                    $value = &HTML::Entities::encode($value,'<>&"');
+                    $output .= '<input type="hidden" name="'.$name.
+                               '" value="'.$value.'" />'."\n";
                 }
+            } else {
+                my $value = &HTML::Entities::encode($env{$key},'<>&"');
+                $output .= '<input type="hidden" name="'.$name.
+                           '" value="'.$value.'" />'."\n";
             }
         }
     }
@@ -1696,7 +2816,6 @@ sub echo_form_input {
 
 ##############################################
 ##############################################
-                                                                             
 # set_form_elements
 #
 # Generates javascript to set form elements to values based on
@@ -1761,7 +2880,7 @@ sub set_form_elements {
             $values{$name}[$i] =~ s/([\r\n\f]+)/\\n/g;
             $values{$name}[$i] =~ s/"/\\"/g;
         }
-        if ($$elements{$name} eq 'text') {
+        if (($$elements{$name} eq 'text') || ($$elements{$name} eq 'hidden')) {
             my $numvalues = @{$values{$name}};
             if ($numvalues > 1) {
                 my $valuestring = join('","',@{$values{$name}});
@@ -1796,6 +2915,8 @@ sub set_form_elements {
                     $output .= qq|
       if (courseForm.elements['$name'].value == "$value") {
           courseForm.elements['$name'].checked = true;
+      } else {
+          courseForm.elements['$name'].checked = false;
       }|;
                 }
             }
@@ -1837,6 +2958,7 @@ sub set_form_elements {
         }
     }
     $output .= "
+    return;
 }\n";
     return $output;
 }
@@ -1844,18 +2966,710 @@ sub set_form_elements {
 ##############################################
 ##############################################
 
+sub file_submissionchk_js {
+    my ($turninpaths,$multiples) = @_;
+    my $overwritewarn = &mt('File(s) you uploaded for your submission will overwrite existing file(s) submitted for this item')."\n".
+                      &mt('Continue submission and overwrite the file(s)?');
+    &js_escape(\$overwritewarn);
+    my $delfilewarn = &mt('You have indicated you wish to remove some files previously included in your submission.')."\n".
+                      &mt('Continue submission with these files removed?');
+    &js_escape(\$delfilewarn);
+    my ($turninpathtext,$multtext,$arrayindexofjs);
+    if (ref($turninpaths) eq 'HASH') {
+        foreach my $key (sort(keys(%{$turninpaths}))) {
+            $turninpathtext .= "    if (prefix == '$key') {\n".
+                               "        return '$turninpaths->{$key}';\n".
+                               "    }\n";
+        }
+    }
+    $turninpathtext .= "    return '';\n";
+    if (ref($multiples) eq 'HASH') {
+        foreach my $key (sort(keys(%{$multiples}))) {
+            $multtext .= "    if (prefix == '$key') {\n".
+                         "        return '$multiples->{$key}';\n".
+                         "    }\n";
+        }
+    }
+    $multtext .= "    return '';\n";
+
+    $arrayindexofjs = &Apache::loncommon::javascript_array_indexof();
+    return <<"ENDSCRIPT";
+<script type="text/javascript">
+// <![CDATA[
+
+function file_submission_check(formname,path,multiresp) {
+    var elemnum = formname.elements.length;
+    if (elemnum == 0) {
+        return true;
+    }
+    var alloverwrites = [];
+    var alldelconfirm = [];
+    var result = [];
+    var submitter;
+    var subprefix;
+    var allsub = getIndexByName(formname,'all_submit');
+    if (allsub == -1) {
+        var idx = getIndexByName(formname,'submitted');
+        if (idx != -1) {
+            var subval = String(formname.elements[idx].value);
+            submitter = subval.replace(/^part_/,'');
+            result = overwritten_check(formname,path,multiresp,submitter);
+            alloverwrites.push.apply(alloverwrites,result['overwrite']);
+            alldelconfirm.push.apply(alldelconfirm,result['delete']);
+        }
+    } else {
+        if (formname.elements[allsub].type == 'submit') {
+            var partsub = /^\\d+\\.\\d+_submit_.+\$/;
+            var allprefixes = [];
+            var allparts = [];
+            for (var i=0; i<formname.elements.length; i++) {
+                if (formname.elements[i].type == 'submit') {
+                    var elemname = formname.elements[i].name;
+                    var subname = String(elemname);
+                    var savesub = String(elemname);
+                    if (partsub.test(subname)) {
+                        var prefix = subname.replace(/_submit_.+\$/,'');
+                        if (allprefixes.indexOf(prefix) == -1) {
+                            allprefixes.push(prefix);
+                            allparts[prefix] = [];
+                        }
+                        var part = savesub.replace(/^\\d+\\.\\d+_submit_/,'');
+                        allparts[prefix].push(part);
+                    }
+                }
+            }
+            for (var k=0; k<allprefixes.length; k++) {
+                var idx = getIndexByName(formname,allprefixes[k]+'_submitted');
+                if (idx > -1) {
+                    if (formname.elements[idx].value != 'yes') {
+                        submitterval = formname.elements[idx].value;
+                        submitter = submitterval.replace(/^part_/,'');
+                        subprefix = allprefixes[k];
+                        result = overwritten_check(formname,path,multiresp,submitter,subprefix);
+                        alloverwrites.push.apply(alloverwrites,result['overwrite']);
+                        alldelconfirm.push.apply(alldelconfirm,result['delete']);
+                        break;
+                    }
+                }
+            }
+            if (submitter == '' || submitter == undefined) {
+                for (var m=0; m<allprefixes.length; m++) {
+                    for (var n=0; n<allparts[allprefixes[m]].length; n++) {
+                        var result = overwritten_check(formname,path,multiresp,allparts[allprefixes[m]][n],allprefixes[m]);
+                        alloverwrites.push.apply(alloverwrites,result['overwrite']);
+                        alldelconfirm.push.apply(alldelconfirm,result['delete']);
+                    }
+                }
+            }
+        }
+    }
+    if (alloverwrites.length > 0) {
+        if (!confirm("$overwritewarn")) {
+            for (var n=0; n<alloverwrites.length; n++) {
+                formname.elements[alloverwrites[n]].value = "";
+            }
+            return false;
+        }
+    }
+    if (alldelconfirm.length > 0) {
+        if (!confirm("$delfilewarn")) {
+            for (var p=0; p<alldelconfirm.length; p++) {
+                formname.elements[alldelconfirm[p]].checked = false;
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
+function getIndexByName(formname,item) {
+    for (var i=0;i<formname.elements.length;i++) {
+        if (formname.elements[i].name == item) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+function overwritten_check(formname,path,multiresp,part,prefix) {
+    var result = [];
+    result['overwrite'] = [];
+    result['delete'] = [];
+    var elemnum = formname.elements.length;
+    if (elemnum == 0) {
+        return result;
+    }
+    var uploadstr;
+    var deletestr;
+    if ((prefix != undefined) && (prefix != '')) {
+        var prepend = prefix+'_';
+        uploadstr = new RegExp("^"+prepend+"HWFILE"+part+".+\$");
+        deletestr = new RegExp("^"+prepend+"HWFILE"+part+".+_\\\\d+_delete\$");
+        multiresp = check_for_multiples(prepend);
+        path = check_for_turninpath(prepend);
+    } else {
+        uploadstr = new RegExp("^HWFILE"+part+".+\$");
+        deletestr = new RegExp("^HWFILE"+part+".+_\\\\d+_delete\$");
+    }
+    var alluploads = [];
+    var allchecked = [];
+    var allskipdel = [];
+    var fnametrim = /[^\\/\\\\]+\$/;
+    for (var i=0; i<formname.elements.length; i++) {
+        var id = formname.elements[i].id;
+        if (id != '') {
+            if (uploadstr.test(id)) {
+                if (formname.elements[i].type == 'file') {
+                    alluploads.push(id);
+                } else {
+                    if (deletestr.test(id)) {
+                        if (formname.elements[i].type == 'checkbox') {
+                            if (formname.elements[i].checked) {
+                                allchecked.push(id);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    for (var j=0; j<alluploads.length; j++) {
+        var delstr = new RegExp("^"+alluploads[j]+"_\\\\d+_delete\$");
+        var delboxes = [];
+        for (var k=0; k<formname.elements.length; k++) {
+            var id = formname.elements[k].id;
+            if ((id != '') && (id != undefined)) {
+                if (delstr.test(id)) {
+                    if (formname.elements[k].type == 'checkbox') {
+                        delboxes.push(id);
+                    }
+                }
+            }
+        }
+        if (delboxes.length > 0) {
+            if ((formname.elements[alluploads[j]].value != undefined) &&
+                (formname.elements[alluploads[j]].value != '')) {
+                var filepath = formname.elements[alluploads[j]].value;
+                var newfilename = fnametrim.exec(filepath);
+                if (newfilename != null) {
+                    var filename = String(newfilename);
+                    var nospaces = filename.replace(/\\s+/g,'_');
+                    var nospecials = nospaces.replace(/[^\\/\\w\\.\\-]/g,'');
+                    var cleanfilename = nospecials.replace(/\\.(\\d+\\.)/g,"_\$1");
+                    if (cleanfilename != '') {
+                        var fullpath = path+"/"+cleanfilename;
+                        if (multiresp == 1) {
+                            var partid = String(alluploads[i]);
+                            var subdir = partid.replace(/^\\d*.?\\d*_?HWFILE/,'');
+                            if (subdir != "" && subdir != undefined) {
+                                fullpath = path+"/"+subdir+"/"+cleanfilename;
+                            }
+                        }
+                        for (var m=0; m<delboxes.length; m++) {
+                            if (fullpath == formname.elements[delboxes[m]].value) {
+                                if (formname.elements[delboxes[m]].checked) {
+                                    allskipdel.push(delboxes[m]);
+                                } else {
+                                    result['overwrite'].push(alluploads[j]);
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    if (allchecked.length > 0) {
+        if (allskipdel.length > 0) {
+            for (var n=0; n<allchecked.length; n++) {
+                if (allskipdel.indexOf(allchecked[n]) == -1) {
+                    result['delete'].push(allchecked[n]);
+                }
+            }
+        } else {
+            result['delete'].push.apply(result['delete'],allchecked);
+        }
+    }
+    return result;
+}
+
+function check_for_multiples(prefix) {
+$multtext
+}
+
+function check_for_turninpath(prefix) {
+$turninpathtext
+}
+
+// ]]>
+</script>
+
+$arrayindexofjs
+
+ENDSCRIPT
+}
+
+##############################################
+##############################################
+
+sub resize_scrollbox_js {
+    my ($context,$tabidstr,$tid) = @_;
+    my (%names,$paddingwfrac,$offsetwfrac,$offsetv,$minw,$minv);
+    if ($context eq 'docs') {
+        %names = (
+                   boxw   => 'contenteditor',
+                   item   => 'contentlist',
+                   header => 'uploadfileresult',
+                   scroll => 'contentscroll',
+                   boxh   => 'contenteditor',
+                 );
+        $paddingwfrac = 0.09;
+        $offsetwfrac = 0.015;
+        $offsetv = 20;
+        $minw = 250;
+        $minv = 200;
+    } elsif ($context eq 'params') {
+        %names = (
+                   boxw   => 'parameditor',
+                   item   => 'mapmenuinner',
+                   header => 'parmstep1',
+                   scroll => 'mapmenuscroll',
+                   boxh   => 'parmlevel',
+                 );
+        $paddingwfrac = 0.2;
+        $offsetwfrac = 0.015;
+        $offsetv = 80;
+        $minw = 100;
+        $minv = 100; 
+    }
+    my $viewport_js = &Apache::loncommon::viewport_geometry_js();
+    my $output = '
+
+window.onresize=callResize;
+
+';
+    if ($context eq 'docs') {
+        if ($env{'form.active'}) {
+            $output .= "\nvar activeTab = '$env{'form.active'}$tid';\n";
+        } else {
+            $output .= "\nvar activeTab = '';\n";
+        }
+    }
+    $output .=  <<"FIRST";
+
+$viewport_js
+
+function resize_scrollbox(scrollboxname,chkw,chkh) {
+    var scrollboxid = 'div_'+scrollboxname;
+    var scrolltableid = 'table_'+scrollboxname;
+    var scrollbox;
+    var scrolltable;
+    var ismobile = '$env{'browser.mobile'}';
+
+    if (document.getElementById("$names{'boxw'}") == null) {
+        return;
+    }
+
+    if (document.getElementById(scrollboxid) == null) {
+        return;
+    } else {
+        scrollbox = document.getElementById(scrollboxid);
+    }
+
+
+    if (document.getElementById(scrolltableid) == null) {
+        return;
+    } else {
+        scrolltable = document.getElementById(scrolltableid);
+    }
+
+    init_geometry();
+    var vph = Geometry.getViewportHeight();
+    var vpw = Geometry.getViewportWidth();
+
+FIRST
+    if ($context eq 'docs') {
+        $output .= "
+    var alltabs = ['$tabidstr'];
+";
+    } elsif ($context eq 'params') {
+        $output .= "
+    if (document.getElementById('$names{'boxh'}') == null) {
+        return;
+    }
+";
+    }
+    $output .= <<"SECOND";
+    var listwchange;
+    var scrollchange;
+    if (chkw == 1) {
+        var boxw = document.getElementById("$names{'boxw'}").offsetWidth;
+        var itemw;
+        var itemid = document.getElementById("$names{'item'}");
+        if (itemid != null) {
+            itemw = itemid.offsetWidth;
+        }
+        var itemwstart = itemw;
+
+        var scrollboxw = scrollbox.offsetWidth;
+        var scrollboxscrollw = scrollbox.scrollWidth;
+        var scrollstart = scrollboxw;
+
+        var offsetw = parseInt(vpw * $offsetwfrac);
+        var paddingw = parseInt(vpw * $paddingwfrac);
+
+        var minscrollboxw = $minw;
+        var maxcolw = 0;
+SECOND
+    if ($context eq 'docs') {
+        $output .= <<"DOCSONE";
+        var actabw = 0;
+        for (var i=0; i<alltabs.length; i++) {
+            if (activeTab == alltabs[i]) {
+                actabw = document.getElementById(alltabs[i]).offsetWidth;
+                if (actabw > maxcolw) {
+                    maxcolw = actabw;
+                }
+            } else {
+                if (document.getElementById(alltabs[i]) != null) {
+                    var thistab = document.getElementById(alltabs[i]);
+                    thistab.style.visibility = 'hidden';
+                    thistab.style.display = 'block';
+                    var tabw = document.getElementById(alltabs[i]).offsetWidth;
+                    thistab.style.display = 'none';
+                    thistab.style.visibility = '';
+                    if (tabw > maxcolw) {
+                        maxcolw = tabw;
+                    }
+                }
+            }
+        }
+DOCSONE
+    } elsif ($context eq 'params') {
+        $output .= <<"PARAMSONE";
+        var parmlevelrows = new Array();
+        var mapmenucells = new Array();
+        parmlevelrows = document.getElementById("$names{'boxh'}").rows;
+        var numrows = parmlevelrows.length;
+        if (numrows > 1) {
+            mapmenucells = parmlevelrows[2].getElementsByTagName('td');
+        }
+        maxcolw = mapmenucells[0].offsetWidth;
+PARAMSONE
+    }
+    $output .= <<"THIRD";
+        if (maxcolw > 0) {
+            var newscrollboxw;
+            if (maxcolw+paddingw+scrollboxscrollw<boxw) {
+                newscrollboxw = boxw-paddingw-maxcolw;
+                if (newscrollboxw < minscrollboxw) {
+                    newscrollboxw = minscrollboxw;
+                }
+                scrollbox.style.width = newscrollboxw+"px";
+                if (newscrollboxw != scrollboxw) {
+                    var newitemw = newscrollboxw-offsetw;
+                    itemid.style.width = newitemw+"px";
+                }
+            } else {
+                newscrollboxw = boxw-paddingw-maxcolw;
+                if (newscrollboxw < minscrollboxw) {
+                    newscrollboxw = minscrollboxw;
+                }
+                scrollbox.style.width = newscrollboxw+"px";
+                if (newscrollboxw != scrollboxw) {
+                    var newitemw = newscrollboxw-offsetw;
+                    itemid.style.width = newitemw+"px";
+                }
+            }
+
+            if (newscrollboxw != scrollboxw) {
+                var newscrolltablew = newscrollboxw+offsetw;
+                scrolltable.style.width = newscrolltablew+"px";
+            }
+        }
+
+        if (newscrollboxw != scrollboxw) {
+            scrollchange = 1;
+        }
+
+        if (itemid.offsetWidth != itemwstart) {
+            listwchange = 1;
+        }
+    }
+    if ((chkh == 1) || (listwchange)) {
+        var itemid = document.getElementById("$names{'item'}");
+        if (itemid != null) {
+            itemh = itemid.offsetHeight;
+        }
+        var primaryheight = 0;
+        if (document.getElementById('LC_nav_bar') != null) {
+            primaryheight = document.getElementById('LC_nav_bar').offsetHeight;
+        }
+        var secondaryheight = 0;
+        if (document.getElementById('LC_secondary_menu') != null) { 
+            secondaryheight = document.getElementById('LC_secondary_menu').offsetHeight;
+        }
+        var crumbsheight = 0;
+        if (document.getElementById('LC_breadcrumbs') != null) {
+            crumbsheight = document.getElementById('LC_breadcrumbs').offsetHeight;
+        }
+        var dccidheight = 0;
+        if (document.getElementById('dccid') != null) {
+            dccidheight = document.getElementById('dccid').offsetHeight;
+        }
+        var headerheight = 0;
+        if (document.getElementById("$names{'header'}") != null) {
+            headerheight = document.getElementById("$names{'header'}").offsetHeight;
+        }
+        var tabbedheight = document.getElementById("tabbededitor").offsetHeight;
+        var boxheight = document.getElementById("$names{'boxh'}").offsetHeight;
+        var freevspace = vph-(primaryheight+secondaryheight+crumbsheight+dccidheight+headerheight+tabbedheight+boxheight);
+
+        var scrollboxheight = scrollbox.offsetHeight;
+        var scrollboxscrollheight = scrollbox.scrollHeight;
+        var scrollboxh = scrollboxheight;
+
+        var minvscrollbox = $minv;
+        var offsetv = $offsetv;
+        var newscrollboxheight;
+        if (freevspace < 0) {
+            newscrollboxheight = scrollboxheight+freevspace-offsetv;
+            if (newscrollboxheight < minvscrollbox) {
+                newscrollboxheight = minvscrollbox;
+            }
+            scrollbox.style.height = newscrollboxheight + "px";
+        } else {
+            if (scrollboxscrollheight > scrollboxheight) {
+                if (freevspace > offsetv) {
+                    newscrollboxheight = scrollboxheight+freevspace-offsetv;
+                    if (newscrollboxheight < minvscrollbox) {
+                        newscrollboxheight = minvscrollbox;
+                    }
+                    scrollbox.style.height = newscrollboxheight+"px";
+                }
+            }
+        }
+        scrollboxheight = scrollbox.offsetHeight;
+        var itemh = document.getElementById("$names{'item'}").offsetHeight;
+
+        if (scrollboxscrollheight <= scrollboxheight) {
+            if ((itemh+offsetv)<scrollboxheight) {
+                newscrollheight = itemh+offsetv;
+                scrollbox.style.height = newscrollheight+"px";
+            }
+        }
+        var newscrollboxh = scrollbox.offsetHeight;
+        if (scrollboxh != newscrollboxh) {
+            scrollchange = 1;
+        }
+    }
+    if (ismobile && scrollchange) {
+        \$("#div_$names{'scroll'}").getNiceScroll().onResize();
+    }
+    return;
+}
+
+function callResize() {
+    var timer;
+    clearTimeout(timer);
+    timer=setTimeout('resize_scrollbox("$names{'scroll'}","1","1")',500);
+}
+
+THIRD
+    return $output;
+}
+
+##############################################
+##############################################
+
+sub javascript_jumpto_resource {
+    my $confirm_switch = &mt("Editing requires switching to the resource's home server.")."\n".
+                         &mt('Switch server?');
+    my $confirm_new_tab = &mt("Editing requires using the resource's home server.")."\n".
+                          &mt('Open a new browser tab?');
+    &js_escape(\$confirm_switch);
+    &js_escape(\$confirm_new_tab);
+    return (<<ENDUTILITY)
+
+function go(url) {
+   if (url!='' && url!= null) {
+       currentURL = null;
+       currentSymb= null;
+       var lcHostname = setLCHost();
+       if (lcHostname!='' && lcHostname!= null) {
+           var RegExp = /^https?\:/;
+           if (RegExp.test(url)) {
+               window.location.href=url;
+           } else {
+               window.location.href=lcHostname+url;
+           }
+       } else {
+           window.location.href=url;
+       }
+   }
+}
+
+function need_switchserver(url,target) {
+    if (url!='' && url!= null) {
+        if (target == '_blank') {
+            if (confirm("$confirm_new_tab")) {
+                window.open(url,target);
+            }
+        } else if (confirm("$confirm_switch")) {
+            go(url);
+        }
+    }
+    return;
+}
+
+ENDUTILITY
+
+}
+
+sub jump_to_editres {
+    my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$shownsymb,
+        $folderpath,$title,$hostname,$idx,$suppurl,$todocs,$suppanchor) = @_;
+    my ($jscall,$anchor,$usehttp,$usehttps,$is_ext,$target);
+    if ($switchserver) {
+        if ($home) {
+            my $resedit;
+            if ($cfile =~ m{^/priv/($match_domain)/($match_username)/}) {
+                my ($audom,$auname) = ($1,$2);
+                unless (&Apache::lonnet::is_course($audom,$auname)) {
+                    if (($symb ne '') && ($env{'request.course.id'}) &&
+                        (&Apache::lonnet::allowed('mdc',$env{'request.course.id'}))) {
+                        unless (&Apache::lonnet::can_switchserver($env{'user.domain'},$home)) {
+                            $target = '_blank';
+                            $resedit = 1;
+                        }
+                    }
+                }
+            }
+            $cfile = '/adm/switchserver?otherserver='.$home.'&amp;role='.
+                     &HTML::Entities::encode($env{'request.role'},'"<>&');
+            if ($shownsymb) {
+                $cfile .= '&amp;symb='.&HTML::Entities::encode($shownsymb,'"<>&');
+                if ($resedit) {
+                    $cfile .= '&amp;edit=1';
+                }
+            } elsif ($folderpath) {
+                $cfile .= '&amp;folderpath='.&HTML::Entities::encode($folderpath,'"<>&');
+            }
+            if ($forceedit) {
+                $cfile .= '&amp;forceedit=1';
+            }
+            if ($forcereg) {
+                $cfile .= '&amp;register=1';
+            }
+            $jscall = "need_switchserver('".&Apache::loncommon::escape_single($cfile)."','$target')";
+        }
+    } else {
+        unless ($cfile =~ m{^/priv/}) {
+            if ($cfile =~ m{^(/adm/wrapper/ext/([^#]+))(?:|#([^#]+))$}) {
+                $cfile = $1;
+                my $extlink = $2;
+                $anchor = $3;
+                $is_ext = 1;
+                if (($extlink !~ /^https:/) && ($ENV{'SERVER_PORT'} == 443)) {
+                    unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+                        $usehttp = 1;
+                    }
+                } elsif ($env{'request.use_absolute'}) {
+                    if ($env{'request.use_absolute'} =~ m{^https://}) {
+                        $usehttps = 1;
+                    }
+                }
+            } elsif ($cfile =~ m{^/?public/($match_domain)/($match_courseid)/syllabus}) {
+                if ($ENV{'SERVER_PORT'} == 443) {
+                    my ($cdom,$cnum) = ($1,$2);
+                    if (($env{'request.course.id'}) &&
+                        ($env{'course.'.$env{'request.course.id'}.'.num'} eq $cnum) &&
+                        ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $cdom)) {
+                        if ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://}) {
+                            unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+                                $usehttp = 1;
+                            }
+                        }
+                    }
+                } elsif ($env{'request.use_absolute'}) {
+                    if ($env{'request.use_absolute'} =~ m{^https://}) {
+                        $usehttps = 1;
+                    }
+                }
+            }
+            if ($symb) {
+                if ($anchor ne '') {
+                    if ($symb =~ m{^([^#]+)\Q#$anchor\E$}) {
+                        $symb = $1.&escape(&escape('#')).$anchor;
+                    }
+                }
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?')."symb=$symb";
+            } elsif ($folderpath) {
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').
+                          'folderpath='.&HTML::Entities::encode(&escape($folderpath),'"<>&');
+                if ($title) {
+                    $cfile .= (($cfile=~/\?/)?'&amp;':'?').
+                              'title='.&HTML::Entities::encode(&escape($title),'"<>&');
+                }
+                if ($idx) {
+                    $cfile .= (($cfile=~/\?/)?'&amp;':'?').'idx='.$idx;
+                }
+                if ($suppurl) {
+                    $cfile .= (($cfile=~/\?/)?'&amp;':'?').
+                              'suppurl='.&HTML::Entities::encode(&escape($suppurl));
+                }
+            }
+            if ($forceedit) {
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'forceedit=1';
+                if ($usehttps) {
+                    $cfile = $env{'request.use_absolute'}.(($cfile =~ /^\//)? '':'/').$cfile;
+                }
+            } elsif ($usehttp) {
+                if ($hostname ne '') {
+                    $cfile = 'http://'.$hostname.(($cfile =~ /^\//)? '':'/').$cfile;
+                }
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'usehttp=1';
+            } elsif ($usehttps) {
+                $cfile = $env{'request.use_absolute'}.(($cfile =~ /^\//)? '':'/').$cfile;
+            }
+            if ($forcereg) {
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'register=1';
+            }
+            if ($todocs) {
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'todocs=1';
+            }
+            if ($suppanchor ne '') {
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'anchor='.
+                          &HTML::Entities::encode($suppanchor,'"<>&');
+            }
+        }
+        if ($anchor ne '') {
+            $cfile .= '#'.$anchor;
+        }
+        $jscall = "go('".&Apache::loncommon::escape_single($cfile)."')";
+    }
+    return $jscall;
+}
+
+##############################################
+##############################################
+
 # javascript_valid_email
 #
 # Generates javascript to validate an e-mail address.
-# Returns a javascript function which accetps a form field as argumnent, and
+# Returns a javascript function which accepts a form field as argument, and
 # returns false if field.value does not satisfy two regular expression matches
 # for a valid e-mail address.  Backwards compatible with old browsers without
 # support for javascript RegExp (just checks for @ in field.value in this case). 
 
 sub javascript_valid_email {
     my $scripttag .= <<'END';
-function validmail(field) {
+function validmail(field,suffix) {
     var str = field.value;
+    if (suffix != '' && suffix != undefined) {
+        str += suffix;
+    }
     if (window.RegExp) {
         var reg1str = "(@.*@)|(\\.\\.)|(@\\.)|(\\.@)|(^\\.)";
         var reg2str = "^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$"; //"
@@ -1878,6 +3692,372 @@ END
     return $scripttag;
 }
 
+
+# USAGE: htmltag(element, content, {attribute => value,...});
+#
+# EXAMPLES: 
+#  - htmltag('a', 'this is an anchor', {href  => 'www.example.com', 
+#                                       title => 'this is a title'})
+#
+#  - You might want to set up needed tags like: 
+#
+#     my $h3  = sub { return htmltag( "h3",  @_ ) };
+#
+#    ... and use them: $h3->("This is a headline")
+#
+#  - To set up a couple of tags, see sub inittags
+#
+# NOTES:
+# - Empty elements, such as <br/> are correctly terminated, 
+#   i.e. htmltag('br') returns <br/> 
+# - Empty attributes (title="") are filtered out.
+# - The function will not check for deprecated attributes.
+#
+# OUTPUT: content enclosed in xhtml conform tags
+sub htmltag{
+    return
+        qq|<$_[0]|
+        . join( '', map { qq| $_="${$_[2]}{$_}"| if ${$_[2]}{$_} } keys(%{ $_[2] }) )
+        . ($_[1] ? qq|>$_[1]</$_[0]>| : qq|/>|). "\n";
+};
+
+
+# USAGE: inittags(@tags);
+#
+# EXAMPLES:
+#  - my ($h1, $h2, $h3) = inittags( qw( h1 h2 h3 ) )
+#    $h1->("This is a headline") #Returns: <h1>This is a headline</h1>
+#
+# NOTES: See sub htmltag for further information.
+#
+# OUTPUT: List of subroutines. 
+sub inittags {
+    my @tags = @_;
+    return map { my $tag = $_;
+                 sub { return htmltag( $tag, @_ ) }
+               } @tags;
+}
+
+
+# USAGE: scripttag(scriptcode, [start|end|both]);
+#
+# EXAMPLES: 
+#  - scripttag("alert('Hello World!')", 'both') 
+#    returns:
+#    <script type="text/javascript">
+#    // BEGIN LON-CAPA Internal
+#    alert(Hello World!')
+#    // END LON-CAPA Internal
+#    </script>
+#
+# NOTES:
+# - works currently only for javascripts
+#
+# OUTPUT: 
+# Scriptcode properly enclosed in <script> and CDATA tags (and LC
+# Internal markers if 2nd argument is given)
+sub scripttag {
+    my ( $content, $marker ) = @_;
+    return unless defined $content;
+
+    my $begin = "\n// BEGIN LON-CAPA Internal\n";
+    my $end   = "\n// END LON-CAPA Internal\n";
+
+    if ($marker) {
+        $content  = $begin . $content if $marker eq 'start' or $marker eq 'both';
+        $content .= $end              if $marker eq 'end'   or $marker eq 'both';
+    }
+
+    $content = "\n// <![CDATA[\n$content\n// ]]>\n";
+
+    return htmltag('script', $content, {type => 'text/javascript'});
+};
+
+=pod
+
+=item &list_from_array( \@array, { listattr =>{}, itemattr =>{} } )
+
+Constructs a XHTML list from \@array.
+
+input: 
+
+=over
+
+=item \@array 
+
+A reference to the array containing text that will be wrapped in <li></li> tags.
+
+=item { listattr => {}, itemattr =>{} } 
+
+Attributes for <ul> and <li> passed in as hash references. 
+See htmltag() for more details.
+
+=back
+ 
+returns: XHTML list as String. 
+
+=cut   
+
+# \@items, {listattr => { class => 'abc', id => 'xyx' }, itemattr => {class => 'abc', id => 'xyx'}}
+sub list_from_array {
+    my ($items, $args) = @_;
+    return unless (ref($items) eq 'ARRAY');
+    return unless scalar @$items;
+    my ($ul, $li) = inittags( qw(ul li) );
+    my $listitems = join '', map { $li->($_, $args->{itemattr}) } @$items;
+    return $ul->( $listitems, $args->{listattr} );
+}
+
+
+##############################################
+##############################################
+
+# generate_menu
+#
+# Generates html markup for a menu. 
+#
+# Inputs:
+# An array of following structure:
+#   ({	categorytitle => 'Categorytitle',
+#	items => [
+#		    {	
+#           linktext    =>	'Text to be displayed',
+#			url	        =>	'URL the link is pointing to, i.e. /adm/site?action=dosomething',
+#			permission  =>	'Contains permissions as returned from lonnet::allowed(),
+#					         must evaluate to true in order to activate the link',
+#			icon        =>  'icon filename',
+#			alttext	    =>	'alt text for the icon',
+#			help	    =>	'Name of the corresponding helpfile',
+#			linktitle   =>	'Description of the link (used for title tag)'
+#		    },
+#		    ...
+#		]
+#   }, 
+#   ...
+#   )
+#
+# Outputs: A scalar containing the html markup for the menu.
+
+sub generate_menu {
+    my @menu = @_;
+    # subs for specific html elements
+    my ($h3, $div, $ul, $li, $a, $img) = inittags( qw(h3 div ul li a img) ); 
+    
+    my @categories; # each element represents the entire markup for a category
+   
+    foreach my $category (@menu) {
+        my @links;  # contains the links for the current $category
+        foreach my $link (@{$$category{items}}) {
+            next unless $$link{permission};
+            
+            # create the markup for the current $link and push it into @links.
+            # each entry consists of an image and a text optionally followed 
+            # by a help link.
+            my $src;
+            if ($$link{icon} ne '') {
+                $src = '/res/adm/pages/'.$$link{icon};
+            }
+            push(@links,$li->(
+                        $a->(
+                            $img->("", {
+                                class => "LC_noBorder LC_middle",
+                                src   => $src,
+                                alt   => mt(defined($$link{alttext}) ?
+                                $$link{alttext} : $$link{linktext})
+                            }), {
+                            href  => $$link{url},
+                            title => mt($$link{linktitle}),
+                            class => 'LC_menubuttons_link'
+                            }).
+                        $a->(mt($$link{linktext}), {
+                            href  => $$link{url},
+                            title => mt($$link{linktitle}),
+                            class => "LC_menubuttons_link"
+                            }).
+                         (defined($$link{help}) ? 
+                         Apache::loncommon::help_open_topic($$link{help}) : ''),
+                         {class => "LC_menubuttons_inline_text"}));
+        }
+
+        # wrap categorytitle in <h3>, concatenate with 
+        # joined and in <ul> tags wrapped @links
+        # and wrap everything in an enclosing <div> and push it into
+        # @categories
+        # such that each element looks like:
+        # <div><h3>title</h3><ul><li>...</li>...</ul></div>
+        # the category won't be added if there aren't any links
+        push(@categories, 
+            $div->($h3->(mt($$category{categorytitle}), {class=>"LC_hcell"}).
+            $ul->(join('' ,@links),  {class =>"LC_ListStyleNormal" }),
+            {class=>"LC_Box LC_400Box"})) if scalar(@links);
+    }
+
+    # wrap the joined @categories in another <div> (column layout)
+    return $div->(join('', @categories), {class => "LC_columnSection"});
+}
+
+##############################################
+##############################################
+
+=pod
+
+=item &start_funclist()
+
+Start list of available functions
+
+Typically used to offer a simple list of available functions
+at top or bottom of page.
+All available functions/actions for the current page
+should be included in this list.
+
+If the optional headline text is not provided, a default text will be used.
+
+
+Related routines:
+=over 4
+add_item_funclist
+end_funclist
+=back
+
+
+Inputs: (optional) headline text
+
+Returns: HTML code with function list start
+
+=cut
+
+##############################################
+##############################################
+
+sub start_funclist {
+    my($legendtext)=@_;
+    $legendtext=&mt('Functions') if !$legendtext;
+    return '<ul class="LC_funclist"><li style="font-weight:bold; margin-left:0.8em;">'.$legendtext.'</li>'."\n";
+}
+
+
+##############################################
+##############################################
+
+=pod
+
+=item &add_item_funclist()
+
+Adds an item to the list of available functions
+
+Related routines:
+=over 4
+start_funclist
+end_funclist
+=back
+
+Inputs: content item with text and link to function
+
+Returns: HTML code with list item for funclist
+
+=cut
+
+##############################################
+##############################################
+
+sub add_item_funclist {
+    my($content) = @_;
+    return '<li>'.$content.'</li>'."\n";
+}
+
+=pod
+
+=item &end_funclist()
+
+End list of available functions
+
+Related routines:
+=over 4
+start_funclist
+add_item_funclist
+=back
+
+Inputs: ./.
+
+Returns: HTML code with function list end
+
+=cut
+
+sub end_funclist {
+    return "</ul>\n";
+}
+
+=pod
+
+=item &funclist_from_array( \@array, {legend => 'text for legend'} )
+
+Constructs a XHTML list from \@array with the first item being visually
+highlighted and set to the value of legend or 'Functions' if legend is
+empty. 
+
+=over
+
+=item \@array
+
+A reference to the array containing text that will be wrapped in <li></li> tags.
+
+=item { legend => 'text' }
+
+A string that's used as visually highlighted first item. 'Functions' is used if
+it's value evaluates to false.
+
+=back
+ 
+returns: XHTML list as string. 
+
+=back
+
+=cut  
+
+sub funclist_from_array {
+    my ($items, $args) = @_;
+    return unless(ref($items) eq 'ARRAY');
+    $args->{legend} ||= mt('Functions');
+    return list_from_array( [$args->{legend}, @$items], 
+               { listattr => {class => 'LC_funclist'} });
+}   
+
+=pod
+
+=over
+
+=item &actionbox( \@array )
+
+Constructs a XHTML list from \@array with the first item being visually
+highlighted and set to the value 'Actions'. The list is wrapped in a division.
+
+The actionlist is used to offer contextual actions, mostly at the bottom
+of a page, on which the outcome of an processed action is shown,
+e.g. a file operation in Authoring Space.
+
+=over
+
+=item \@array
+
+A reference to the array containing text. Details: sub funclist_from_array
+
+=back
+ 
+Returns: XHTML div as string.
+
+=back
+
+=cut  
+
+sub actionbox {
+    my ($items) = @_;
+    return unless(ref($items) eq 'ARRAY');
+    return
+        '<div class="LC_actionbox">'
+       .&funclist_from_array($items, {legend => &mt('Actions')})
+       .'</div>';
+}
+
 1;
 
 __END__