--- loncom/interface/lonhtmlcommon.pm	2022/05/29 20:37:21	1.403
+++ loncom/interface/lonhtmlcommon.pm	2025/02/18 03:42:04	1.418
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common html routines
 #
-# $Id: lonhtmlcommon.pm,v 1.403 2022/05/29 20:37:21 raeburn Exp $
+# $Id: lonhtmlcommon.pm,v 1.418 2025/02/18 03:42:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -231,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[
@@ -541,7 +542,14 @@ 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
@@ -642,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/;
@@ -656,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>};
     }
@@ -682,8 +690,8 @@ 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;
     unless (($nolink) || ($state eq 'disabled')) {
         $cal_link = qq{<a href="javascript:$dname\_opencalendar()">};
@@ -1316,9 +1324,9 @@ sub htmlareaheaders {
 ENDEDITOR
 	}
     $s.=(<<ENDJQUERY);
-<script type="text/javascript" src="/adm/jQuery/js/jquery-3.2.1.min.js"></script>
-<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.12.1.custom.min.js"></script>
-<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.12.1.custom.min.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" />
@@ -1737,14 +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');
+    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|navmaps)($|\?)})
+    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 '')
             ||
@@ -1813,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
+}
+
 ############################################################
 ############################################################
 
@@ -1849,9 +1922,10 @@ boolean, controls whether to include a l
 
 if 'nohelp' don't include the orange help link
 
-=item $css_class
+=item $crumbs_style
 
-optional name for the class to apply to the table for CSS
+optional style attribute for div containing breadcrumbs
+unless called from docs_breadcrumbs
 
 =item $no_mt 
 
@@ -1909,11 +1983,9 @@ returns: nothing
     my %tools = ();
     
     sub breadcrumbs {
-        my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, 
+        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  = '';
@@ -2015,7 +2087,7 @@ returns: nothing
 
         if ($lasttext ne '') {
             $links .= &htmltag( 'li', htmltag($CourseBreadcrumbs ? 'b' : 'h1',
-                    $lasttext), {title => $lasttext});
+                    $lasttext));
         }
 
         my $icons = '';
@@ -2067,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') {
@@ -2085,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 {
@@ -2237,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/) {
@@ -2263,8 +2339,10 @@ sub docs_breadcrumbs {
 # 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 {
@@ -2305,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,
@@ -2410,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';
@@ -2418,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;
@@ -3527,7 +3613,10 @@ 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) {
@@ -3548,9 +3637,13 @@ function go(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);
         }
     }
@@ -3562,15 +3655,35 @@ ENDUTILITY
 }
 
 sub jump_to_editres {
-    my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$folderpath,
-        $title,$hostname,$idx,$suppurl,$todocs,$suppanchor) = @_;
-    my ($jscall,$anchor,$usehttp,$usehttps,$is_ext);
+    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,'"<>&');
             }
@@ -3580,7 +3693,7 @@ 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/}) {