--- loncom/interface/lonhtmlcommon.pm	2016/08/04 21:26:35	1.358.2.4
+++ loncom/interface/lonhtmlcommon.pm	2025/03/06 16:42:40	1.420
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common html routines
 #
-# $Id: lonhtmlcommon.pm,v 1.358.2.4 2016/08/04 21:26:35 raeburn Exp $
+# $Id: lonhtmlcommon.pm,v 1.420 2025/03/06 16:42:40 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -78,7 +78,12 @@ sub java_not_enabled {
 sub coursepreflink {
    my ($text,$category)=@_;
    if (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) {
-      return '<a target="_top" href="'.&HTML::Entities::encode("/adm/courseprefs?phase=display&actions=$category",'<>&"').'"><span class="LC_setting">'.$text.'</span></a>';
+       my $target =' target="_top"';
+       if ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) ||
+           (($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 '';
    }
@@ -92,7 +97,7 @@ sub raw_href_to_link {
 
 sub entity_encode {
     my ($text)=@_;
-    return &HTML::Entities::encode($text, '<>&"');
+    return &HTML::Entities::encode($text, '\'<>&"');
 }
 
 sub direct_parm_link {
@@ -101,9 +106,14 @@ sub direct_parm_link {
     $filter=&entity_encode($filter);
     $part=&entity_encode($part);
     if (($symb) && (&Apache::lonnet::allowed('opa')) && ($target ne 'tex')) {
-       return "<a target='_top' href='/adm/parmset?symb=$symb&amp;filter=$filter&amp;part=$part'><span class='LC_setting'>$linktext</span></a>";
+        my $target=' target="_top"';
+        if ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) ||
+            (($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;
+        return $linktext;
     }
 }
 ##############################################
@@ -221,15 +231,16 @@ sub dependencycheck_js {
         $link = '/adm/dependencies?symb='.&HTML::Entities::encode($symb,'<>&"');
     } elsif ($folderpath) {
         $link = '/adm/dependencies?folderpath='.&HTML::Entities::encode($folderpath,'<>&"');
-         $url = $uri;
+        $url = $uri;
     } elsif ($uri =~ m{^/public/$match_domain/$match_courseid/syllabus$}) {
         $link = '/adm/dependencies';
     }
-    $link .= (($link=~/\?/)?'&amp;':'?').'title='.
+    $link .= (($link=~/\?/)?'&':'?').'title='.
              &HTML::Entities::encode($title,'<>&"');
     if ($url) {
         $link .= '&url='.&HTML::Entities::encode($url,'<>&"');
     }
+    &js_escape(\$link);
     return <<ENDJS;
                 <script type="text/javascript">
                 // <![CDATA[
@@ -407,7 +418,7 @@ sub textbox {
 ##############################################
 ##############################################
 sub checkbox {
-    my ($name,$checked,$value) = @_;
+    my ($name,$checked,$value,$special) = @_;
     my $Str = '<input type="checkbox" name="'.$name.'" ';
     if (defined($value)) {
         $Str .= 'value="'.$value.'"';
@@ -415,7 +426,7 @@ sub checkbox {
     if ($checked) {
         $Str .= ' checked="checked"';
     }
-    $Str .= ' />';
+    $Str .= $special.' />';
     return $Str;
 }
 
@@ -451,10 +462,12 @@ sub radio {
 &date_setter returns html and javascript for a compact date-setting form.
 To retrieve values from it, use &get_date_from_form.
 
-Inputs
-
 =over 4
 
+=item Inputs
+
+=over
+
 =item $dname 
 
 The name to prepend to the form elements.  
@@ -515,10 +528,12 @@ If true, text boxes for seconds are omit
 
 =back
 
-Bugs
+=item Bugs
 
 The method used to restrict user input will fail in the year 2400.
 
+=back
+
 =cut
 
 ##############################################
@@ -527,13 +542,22 @@ sub date_setter {
     my ($formname,$dname,$currentvalue,$special,$includeempty,$state,
         $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink,$no_mm_ss,$no_ss) = @_;
     my $now = time;
-
+    my %labels = &Apache::lonlocal::texthash(
+                                               day   => 'day',
+                                               month => 'month',
+                                               year  => 'year',
+                                               sec   => 'seconds',
+                                               min   => 'minutes',
+                                               hour  => 'hours',
+                                            );
     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;
@@ -626,7 +650,7 @@ document.$formname.$dname\_year.value,
 </script>
 ENDJS
     $result .= '  <span class="LC_nobreak">';
-    my $monthselector = qq{<select name="$dname\_month" $special $state onchange="javascript:$dname\_checkday()" >};
+    my $monthselector = qq{<select name="$dname\_month" $special $state onchange="javascript:$dname\_checkday()" aria-label="$labels{'month'}">};
     # Month
     my @Months = qw/January February  March     April   May      June 
                     July    August    September October November December/;
@@ -640,11 +664,11 @@ ENDJS
     }
     $monthselector.= '  </select>';
     # Day
-    my $dayselector = qq{<input type="text" name="$dname\_day" $state value="$mday" size="3" $special onchange="javascript:$dname\_checkday()" />};
+    my $dayselector = qq{<input type="text" name="$dname\_day" $state value="$mday" size="3" $special onchange="javascript:$dname\_checkday()" aria-label="$labels{'day'}" />};
     # Year
-    my $yearselector = qq{<input type="text" 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()" aria-label="$labels{'year'}" />};
     #
-    my $hourselector = qq{<select name="$dname\_hour" $special $state >};
+    my $hourselector = qq{<select name="$dname\_hour" $special $state aria-label="$labels{'hour'}">};
     if ($includeempty) { 
         $hourselector.=qq{<option value=''></option>};
     }
@@ -666,10 +690,10 @@ ENDJS
         $hourselector .= $timest." </option>\n";
     }
     $hourselector .= "  </select>\n";
-    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 $minuteselector = qq{<input type="text" name="$dname\_minute" $special $state value="$min" size="3" aria-label="$labels{'min'}" />};
+    my $secondselector= qq{<input type="text" name="$dname\_second" $special $state value="$sec" size="3" aria-label="$labels{'sec'}" />};
     my $cal_link;
-    if (!$nolink) {
+    unless (($nolink) || ($state eq 'disabled')) {
         $cal_link = qq{<a href="javascript:$dname\_opencalendar()">};
     }
     #
@@ -683,18 +707,20 @@ ENDJS
                       $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).
                    $tzone;
     }
-    if (!$nolink) {
+    unless (($nolink) || ($state eq 'disabled')) {
         $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
     }
     $result .= "</span>\n<!-- end $dname date setting form -->\n";
@@ -739,7 +765,9 @@ sub build_url {
 
 get_date_from_form retrieves the date specified in an &date_setter form.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4
 
@@ -753,6 +781,8 @@ The unix time to use as the default in c
 
 =back
 
+=back
+
 Returns: Unix time represented in the form.
 
 =cut
@@ -846,13 +876,14 @@ parameter setting wizard.
 ##############################################
 sub pjump_javascript_definition {
     my $Str = <<END;
-    function pjump(type,dis,value,marker,ret,call,hour,min,sec) {
+    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)+"&modal=1",350,350,'no');
+                 +"&defsec="+escape(sec)+"&extra="+escape(extra)
+                 +"&modal=1",350,350,'no');
     }
 END
     return $Str;
@@ -924,7 +955,9 @@ sub javascript_docopen {
 Returns html for a selection box which allows the user to choose the
 enrollment status of students.  The selection box name is 'Status'.
 
-Inputs:
+=over
+
+=item Inputs:
 
 $status: the currently selected status.  If undefined the value of
 $env{'form.Status'} is taken.  If that is undefined, a value of 'Active'
@@ -938,6 +971,8 @@ $size: the size (number of lines) of the
 $onchange: javascript to use when the value is changed.  Enclosed in 
 double quotes, ""s, not single quotes.
 
+=back
+
 Returns: a perl string as described.
 
 =cut
@@ -985,7 +1020,7 @@ sub StatusOptions {
 
 =pod
 
-=item Progess Window Handling Routines
+=item Progress Window Handling Routines
 
 These routines handle the creation, update, increment, and closure of 
 progress windows.  The progress window reports to the user the number
@@ -999,25 +1034,34 @@ of items completed and an estimate of th
 Writes javascript to the client to open a progress window and returns a
 data structure used for bookkeeping.
 
-Inputs
+=over
 
-=over 4
+=item Inputs
+
+=over
 
 =item $r Apache request
 
 =item $number_to_do The total number of items being processed.
 
+=item $preamble Optional HTML to display before the progress bar.
+
 =back
 
-Returns a hash containing the progress state data structure.
+=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()
 
 Updates the text in the progress indicator.  Does not increment the count.
 See &Increment_PrgWin.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4
 
@@ -1029,25 +1073,38 @@ Inputs:
 
 =back
 
+=back
+
 Returns: none
 
 
-=item Increment_PrgWin()
+=item &Increment_PrgWin()
 
 Increment the count of items completed for the progress window by $step or 1 if no step is provided.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4
 
-=item $r Apache request
+=item $r 
 
-=item $prog_state Pointer to the data structure returned by Create_PrgWin
+Apache request
+
+=item $prog_state
+
+Pointer to the data structure returned by Create_PrgWin
 
-=item $extraInfo A description of the items being iterated over.  Typically
-'student'.
+=item $extraInfo
 
-=item $step (optional) counter step. Will be set to default 1 if ommited. step must be greater than 0 or empty.
+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
 
 =back
 
@@ -1058,7 +1115,9 @@ Returns: none
 
 Closes the progress window.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4 
 
@@ -1068,6 +1127,8 @@ Inputs:
 
 =back
 
+=back
+
 Returns: none
 
 =back
@@ -1080,20 +1141,20 @@ Returns: none
 
 # Create progress
 sub Create_PrgWin {
-    my ($r,$number_to_do)=@_;
+    my ($r,$number_to_do,$preamble)=@_;
     my %prog_state;
     $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); 
+    &Apache::loncommon::LCprogressbar($r,$prog_state{'max'},$preamble); 
     return %prog_state;
 }
 
 # update progress
 sub Update_PrgWin {
     my ($r,$prog_state,$displayString)=@_;
-    &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString);
+    &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString,$$prog_state{'max'});
     $$prog_state{'laststart'}=&Time::HiRes::time();
 }
 
@@ -1143,7 +1204,7 @@ sub Increment_PrgWin {
     if ($$prog_state{'max'}) {
        $percent=int(100.*$current/$$prog_state{'max'});
     }
-    &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo);
+    &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo,$$prog_state{'max'});
     $$prog_state{'laststart'}=&Time::HiRes::time();
 }
 
@@ -1160,7 +1221,7 @@ sub Close_PrgWin {
 sub crumbs {
     my ($uri,$target,$prefix,$form,$skiplast,$onclick)=@_;
 # You cannot crumbnify uploaded or adm resources
-    if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Group Content)'); }
+    if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Community Content)'); }
     if ($target) {
         $target = ' target="'.
                   &Apache::loncommon::escape_single($target).'"';
@@ -1180,6 +1241,12 @@ sub crumbs {
             } else {
                 $path.='/'; 
             }
+            if ($path eq '/res/') {
+                unless (&Apache::lonnet::allowed('bre',$path)) {
+                    $output.="$dir/";
+                    next;
+                }
+            }
             my $href_path = &HTML::Entities::encode($path,'<>&"');
             &Apache::loncommon::inhibit_menu_check(\$href_path);
             if ($form) {
@@ -1257,9 +1324,9 @@ sub htmlareaheaders {
 ENDEDITOR
 	}
     $s.=(<<ENDJQUERY);
-<script type="text/javascript" src="/adm/jQuery/js/jquery-1.11.3.min.js"></script>
-<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.11.4.custom.min.js"></script>
-<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.11.4.custom.css" />
+<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" />
@@ -1296,6 +1363,68 @@ $(document).ready(function(){
 });';
 }
 
+sub countdown {
+
+    # 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>';
+    return <<"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.
+            }
+         }
+      });
+   }
+});
+
+JAVASCRIPT
+
+}
+
 # ----------------------------------------- Script to activate only some fields
 
 sub htmlareaselectactive {
@@ -1342,6 +1471,41 @@ sub htmlareaselectactive {
     }
     
     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",
@@ -1353,6 +1517,68 @@ sub htmlareaselectactive {
     
     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) {
@@ -1422,62 +1648,11 @@ sub htmlareaselectactive {
 
 	});
 ';
-    $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.
+    $output .= &color_picker();
 
-    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;
+    $output .= &countdown();
 
-\$(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.
-            }
-         }
-      });
-   }
-});
+    $output .= <<"JAVASCRIPT";
 
     /* This code describes the spellcheck options that will be used for
        items with class 'spellchecked'.  It is necessary for those objects'
@@ -1570,13 +1745,31 @@ 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');
-
-    if (($env{'request.noversionuri'} =~ m{^/adm/(viewclasslist|navmaps)($|\?)})
+    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$})));
+    return if (($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') &&
+               (!$env{'request.role.adv'}));
+    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 '')
             ||
@@ -1585,7 +1778,7 @@ sub show_return_link {
             (($env{'request.noversionuri'}=~/^\/adm\//) &&
              ($env{'request.noversionuri'}!~/^\/adm\/wrapper\//) &&
              ($env{'request.noversionuri'}!~
-              m{^/adm/.*/(smppg|bulletinboard)($|\?)})
+              m{^/adm/.*/(smppg|bulletinboard|ext\.tool)($|\?)})
            ));
 }
 
@@ -1645,6 +1838,54 @@ clientTime = (new Date()).getTime();
 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\\u2212\\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
+}
+
 ############################################################
 ############################################################
 
@@ -1659,19 +1900,62 @@ A link to help for the component will be
 
 All inputs can be undef without problems.
 
-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.
+=over
+
+=item Inputs:
+
+=over
+
+=item $component
+
+the text on the right side of the breadcrumbs trail
+
+=item $component_help
+
+the help item filename (without .tex extension).
+
+=item $menulink
+
+boolean, controls whether to include a link to /adm/menu
+
+=item $helplink
+
+if 'nohelp' don't include the orange help link
+
+=item $crumbs_style
+
+optional style attribute for div containing breadcrumbs
+unless called from docs_breadcrumbs
+
+=item $no_mt 
+
+optional flag, 1 if &mt() is _not_ to be applied to $component when including the text on the right
+
+=item $CourseBreadcrumbs
+
+optional flag, 1 if &breadcrumbs called from &docs_breadcrumbs, because breadcrumbs are being
+used to display hierarchy for current folder shown in the Course Editor. 
+
+=item $topic_help
+
+optional help item to be displayed on right side of the breadcrumbs row, using 
+loncommon::help_open_topic() to generate the link. 
+
+=item $topic_help_text
+
+text to include in the link in the optional help item ($topic_help) on the right
+side of the breadcrumbs row.
+
+=item $links_target
+
+optionally includes the target (_top, _parent or _self) for (i) initial
+$menulink item in the breadcrumbs (if present), (ii) return to last location
+(if present), and (iii) help item at the right side of breadcrumbs menu, 
+created by loncommon::help_open_topic() or loncommon::help_open_menu().
+
+=back
+
+=back
 
 Returns a string containing breadcrumbs for the current page.
 
@@ -1699,11 +1983,9 @@ returns: nothing
     my %tools = ();
     
     sub breadcrumbs {
-        my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, 
-            $CourseBreadcrumbs,$topic_help,$topic_help_text) = @_;
+        my ($component,$component_help,$menulink,$helplink,$crumbs_style,$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  = '';
@@ -1716,6 +1998,16 @@ returns: nothing
         # The first one should be the course or a menu link
         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';
             my $no_mt_descr = 0;
             if ((exists($env{'request.course.id'})) && 
@@ -1725,7 +2017,7 @@ returns: nothing
                     $env{'course.'.$env{'request.course.id'}.'.description'};
                 $no_mt_descr = 1;
                 if ($env{'request.noversionuri'} =~ 
-                    m{^/public/($match_domain)/($match_courseid)/syllabus$}) {
+                    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';
@@ -1733,9 +2025,16 @@ returns: nothing
                     }
                 }
             }
+            my $target = '_top';
+            if ($links_target) {
+                $target = $links_target;
+            } elsif ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) ||
+                (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) {
+                $target='';
+            }
             $menulink =  {  href   =>'/adm/menu',
                             title  =>'Go to main menu',
-                            target =>'_top',
+                            target =>$target,
                             text   =>$description,
                             no_mt  =>$no_mt_descr, };
             if($last) {
@@ -1753,8 +2052,8 @@ returns: nothing
                             title => &mt('Back to most recent content resource'),
                             class => 'LC_menubuttons_link',
                           };
-            if ($env{'request.noversionuri'} eq '/adm/searchcat') {
-                $hashref->{'target'} = '_top'; 
+            if ($links_target) {
+                $hashref->{'target'} = $links_target;
             }
             $links=&htmltag( 'a','<img src="/res/adm/pages/tolastloc.png" alt="'.$alttext.'" class="LC_icon" />',
                              $hashref);
@@ -1788,7 +2087,7 @@ returns: nothing
 
         if ($lasttext ne '') {
             $links .= &htmltag( 'li', htmltag($CourseBreadcrumbs ? 'b' : 'h1',
-                    $lasttext), {title => $lasttext});
+                    $lasttext));
         }
 
         my $icons = '';
@@ -1805,11 +2104,12 @@ returns: nothing
         if ($faq ne '' || $component_help ne '' || $bug ne '') {
             $icons .= &Apache::loncommon::help_open_menu($component,
                                                          $component_help,
-                                                         $faq,$bug);
+                                                         $faq,$bug,'','','','',
+                                                         $links_target);
         }
         if ($topic_help && $topic_help_text) {
            $icons .= ' '.&Apache::loncommon::help_open_topic($topic_help,&mt($topic_help_text),'',
-                                                             undef,600);
+                                                             undef,600,'',$links_target);
         }
         #
 
@@ -1839,8 +2139,13 @@ returns: nothing
         }
         if (($links ne '') || ($nav_and_tools)) {
             &render_tools(\$links);
-            $links = &htmltag('div', $links, 
-                              { id => "LC_breadcrumbs" }) unless ($CourseBreadcrumbs) ;
+            unless ($CourseBreadcrumbs) {
+                my $args = { id => 'LC_breadcrumbs' };
+                if ($crumbs_style ne '') {
+                    $args->{'style'} = $crumbs_style;
+                }
+                $links = &htmltag('div', $links, $args);
+            }
         }
         my $adv_tools = 0;
         if (ref($tools{'advtools'}) eq 'ARRAY') {
@@ -1857,9 +2162,8 @@ returns: nothing
 
         # Return the breadcrumb's line
 
-    
-
-        return "$links";
+        my $labeltext = &HTML::Entities::encode(&mt('Links for navigation and information'));
+        return '<div class="LC_landmark" role="navigation" aria-label="'.$labeltext.'">'.$links.'</div>';
     }
 
     sub clear_breadcrumbs {
@@ -1878,7 +2182,9 @@ Adds $html to $category of the breadcrum
 $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 Currently there are 3 possible values for $category: 
 
 =over 
 
@@ -1892,7 +2198,9 @@ remaining items in right of breadcrumbs
 advanced tools shown in a separate box below breadcrumbs line 
 
 =back
- 
+
+=back
+
 returns: nothing
 
 =cut
@@ -1926,13 +2234,32 @@ returns: nothing
         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.
+=over
+
+=item input: 
+
+=over
+
+=item \$breadcrumbs - a reference to the string containing prepared breadcrumbs.
+
+=back
+
+=back
 
 returns: nothing
 
@@ -1958,8 +2285,17 @@ returns: nothing
 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).
+=over
+
+=item input:
+
+=over
+
+=item \$breadcrumbs - a reference to the string containing prepared breadcrumbs (after render_tools call).
+
+=back
+
+=back
 
 returns: nothing
 
@@ -1977,7 +2313,7 @@ returns: nothing
 } # End of scope for @Crumbs
 
 sub docs_breadcrumbs {
-    my ($allowed,$crstype,$contenteditor,$title,$precleared)=@_;
+    my ($allowed,$crstype,$contenteditor,$title,$precleared,$checklinkprot)=@_;
     my ($folderpath,@folders,$supplementalflag);
     @folders = split('&',$env{'form.folderpath'});
     if ($env{'form.folderpath'} =~ /^supplemental/) {
@@ -1992,19 +2328,21 @@ sub docs_breadcrumbs {
         my $foldername=shift(@folders);
         if ($folderpath) {$folderpath.='&';}
         $folderpath.=$folder.'&'.$foldername;
-        my $url;
+        my $url = $env{'request.use_absolute'};
         if ($allowed) {
-            $url = '/adm/coursedocs?folderpath=';
+            $url .= '/adm/coursedocs?folderpath=';
         } else {
-            $url = '/adm/supplemental?folderpath=';
+            $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*)$//;
-        unless ($supplementalflag) {
-            if ($contenteditor) { 
+        if ($contenteditor) {
+            if ($supplementalflag) {
+                if ($2) { $ishidden=1; }
+            } else {
                 if ($1 ne '') {
                     $randompick=$1;
                 } else {
@@ -2045,9 +2383,17 @@ sub docs_breadcrumbs {
             $plain=~s/\&gt\;\s*$//;
         }
         my $menulink = 0;
-        if (!$allowed && !$contenteditor) {
+        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,
@@ -2150,7 +2496,7 @@ END
 }
 
 sub row_title {
-    my ($title,$css_title_class,$css_value_class, $css_value_furtherAttributes) = @_;
+    my ($title,$css_title_class,$css_value_class,$css_value_furtherAttributes,$nocolon) = @_;
     $row_count[0]++;
     my $css_class = ($row_count[0] % 2)?'LC_odd_row':'LC_even_row';
     $css_title_class ||= 'LC_pick_box_title';
@@ -2158,14 +2504,14 @@ sub row_title {
 
     $css_value_class ||= 'LC_pick_box_value';
 
-    if ($title ne '') {
+    if (($title ne '') && (!$nocolon)) {
         $title .= ':';
     }
     my $output = <<"ENDONE";
            <tr class="LC_pick_box_row" $css_value_furtherAttributes> 
-            <td $css_title_class>
+            <th $css_title_class>
 	       $title
-            </td>
+            </th>
             <td class="$css_value_class $css_class">
 ENDONE
     return $output;
@@ -2421,9 +2767,9 @@ sub course_custom_roles {
 
 
 sub resource_info_box {
-   my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp)=@_;
+   my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp,$divforres)=@_;
    my $return='';
-   if ($stuvcurrent ne '') {
+   if (($stuvcurrent ne '') || ($divforres)) {
        $return = '<div class="LC_left_float">';
    }
    if ($symb) {
@@ -2452,7 +2798,7 @@ sub resource_info_box {
     } else {
        $return='<p><span class="LC_error">'.&mt('No context provided.').'</span></p>';
     }
-    if ($stuvcurrent ne '') {
+    if (($stuvcurrent ne '') || ($divforres)) {
         $return .= '</div>';
     }
     return $return;
@@ -2467,9 +2813,9 @@ sub resource_info_box {
 #
 
 sub display_usage {
-    my ($current_disk_usage,$disk_quota) = @_;
-    my $usage = $current_disk_usage/1000;
-    my $quota = $disk_quota/1000;
+    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;
@@ -2493,9 +2839,13 @@ sub display_usage {
     if ($prog_width > 100) {
         $prog_width = 100;
     }
+    my $display = 'block';
+    if ($context eq 'authoring') {
+        $display = 'inline';
+    }
     return '
-  <div id="meter1" align="left" '.$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:5px; margin-bottom:5px; margin-left:0px; margin-right:0px; width:400px; border:1px solid #000000; height:10px;">'."\n".
+  <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>';
@@ -3179,12 +3529,18 @@ PARAMSONE
         if (itemid != null) {
             itemh = itemid.offsetHeight;
         }
-        var primaryheight = document.getElementById('LC_nav_bar').offsetHeight;
-        var secondaryheight;
+        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 = document.getElementById('LC_breadcrumbs').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;
@@ -3257,20 +3613,37 @@ THIRD
 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;
-       window.location.href=url;
+       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) {
+function need_switchserver(url,target) {
     if (url!='' && url!= null) {
-        if (confirm("$confirm_switch")) {
+        if (target == '_blank') {
+            if (confirm("$confirm_new_tab")) {
+                window.open(url,target);
+            }
+        } else if (confirm("$confirm_switch")) {
             go(url);
         }
     }
@@ -3282,15 +3655,35 @@ ENDUTILITY
 }
 
 sub jump_to_editres {
-    my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$folderpath,
-        $title,$idx,$suppurl,$todocs) = @_;
-    my $jscall;
+    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)) {
+                    unless ((&Apache::lonnet::will_trust('othcoau',$env{'user.domain'},$audom)) &&
+                            (&Apache::lonnet::will_trust('coaurem',$audom,$env{'user.domain'}))) {
+                       return;
+                    }
+                    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 ($symb) {
-                $cfile .= '&amp;symb='.&HTML::Entities::encode($symb,'"<>&');
+            if ($shownsymb) {
+                $cfile .= '&amp;symb='.&HTML::Entities::encode($shownsymb,'"<>&');
+                if ($resedit) {
+                    $cfile .= '&amp;edit=1';
+                }
             } elsif ($folderpath) {
                 $cfile .= '&amp;folderpath='.&HTML::Entities::encode($folderpath,'"<>&');
             }
@@ -3300,11 +3693,48 @@ sub jump_to_editres {
             if ($forcereg) {
                 $cfile .= '&amp;register=1';
             }
-            $jscall = "need_switchserver('".&Apache::loncommon::escape_single($cfile)."');";
+            $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;':'?').
@@ -3323,14 +3753,31 @@ sub jump_to_editres {
             }
             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';
+                $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;
@@ -3342,15 +3789,18 @@ sub jump_to_editres {
 # 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})(\\]?)$"; //"
@@ -3460,7 +3910,9 @@ sub scripttag {
 
 Constructs a XHTML list from \@array.
 
-input: 
+=over
+
+=item input: 
 
 =over
 
@@ -3474,7 +3926,9 @@ Attributes for <ul> and <li> passed in a
 See htmltag() for more details.
 
 =back
- 
+
+=back
+
 returns: XHTML list as String. 
 
 =cut   
@@ -3522,7 +3976,7 @@ sub list_from_array {
 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 ($h2, $div, $ul, $li, $a, $img) = inittags( qw(h2 div ul li a img) );
     
     my @categories; # each element represents the entire markup for a category
    
@@ -3545,30 +3999,25 @@ sub generate_menu {
                                 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}), {
+                            }).mt($$link{linktext}), {
                             href  => $$link{url},
                             title => mt($$link{linktitle}),
                             class => "LC_menubuttons_link"
                             }).
-                         (defined($$link{help}) ? 
+                         (defined($$link{help}) ?
                          Apache::loncommon::help_open_topic($$link{help}) : ''),
                          {class => "LC_menubuttons_inline_text"}));
         }
 
-        # wrap categorytitle in <h3>, concatenate with 
+        # wrap categorytitle in <h2>, 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>
+        # <div><h2>title</h2><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"}).
+            $div->($h2->(mt($$category{categorytitle}), {class=>'LC_hcell LC_heading_2'}).
             $ul->(join('' ,@links),  {class =>"LC_ListStyleNormal" }),
             {class=>"LC_Box LC_400Box"})) if scalar(@links);
     }
@@ -3593,13 +4042,19 @@ should be included in this list.
 
 If the optional headline text is not provided, a default text will be used.
 
+=over
+
+=item Related routines:
 
-Related routines:
 =over 4
-add_item_funclist
-end_funclist
+
+=item add_item_funclist
+
+=item end_funclist
+
 =back
 
+=back
 
 Inputs: (optional) headline text
 
@@ -3626,10 +4081,18 @@ sub start_funclist {
 
 Adds an item to the list of available functions
 
-Related routines:
+=over
+
+=item Related routines:
+
 =over 4
-start_funclist
-end_funclist
+
+=item start_funclist
+
+=item end_funclist
+
+=back
+
 =back
 
 Inputs: content item with text and link to function
@@ -3652,10 +4115,13 @@ sub add_item_funclist {
 
 End list of available functions
 
-Related routines:
-=over 4
-start_funclist
-add_item_funclist
+=over
+
+=item Related routines:
+
+ start_funclist
+ add_item_funclist
+
 =back
 
 Inputs: ./.
@@ -3688,7 +4154,7 @@ A string that's used as visually highlig
 it's value evaluates to false.
 
 =back
- 
+
 returns: XHTML list as string. 
 
 =back
@@ -3701,7 +4167,7 @@ sub funclist_from_array {
     $args->{legend} ||= mt('Functions');
     return list_from_array( [$args->{legend}, @$items], 
                { listattr => {class => 'LC_funclist'} });
-}   
+}
 
 =pod
 
@@ -3723,8 +4189,8 @@ e.g. a file operation in Authoring Space
 A reference to the array containing text. Details: sub funclist_from_array
 
 =back
- 
-Returns: XHTML div as string.
+
+Returns: XHTML div as string. 
 
 =back