--- loncom/interface/lonhtmlcommon.pm	2012/09/04 10:46:05	1.320
+++ loncom/interface/lonhtmlcommon.pm	2016/11/09 01:58:43	1.377
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common html routines
 #
-# $Id: lonhtmlcommon.pm,v 1.320 2012/09/04 10:46:05 foxr Exp $
+# $Id: lonhtmlcommon.pm,v 1.377 2016/11/09 01:58:43 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -61,12 +61,18 @@ use Time::HiRes;
 use Apache::lonlocal;
 use Apache::lonnet;
 use HTML::Entities();
-use LONCAPA;
+use LONCAPA qw(:DEFAULT :match);
 
 sub java_not_enabled {
-   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";
+    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 {
@@ -170,7 +176,7 @@ sub dragmath_js {
                   function mathedit(textarea, doc) {
                      targetEntry = textarea;
                      targetDoc   = doc;
-                     newwin  = window.open("/adm/dragmath/applet/$popup.html","","width=565,height=500,resizable");
+                     newwin  = window.open("/adm/dragmath/$popup.html","","width=565,height=500,resizable");
                   }
                 // ]]>
                 </script>
@@ -209,10 +215,21 @@ dependencies for a web page uploaded dir
 =cut
 
 sub dependencycheck_js {
-    my ($symb,$title,$url) = @_;
-    my $link = '/adm/dependencies?symb='.&HTML::Entities::encode($symb,'<>&"').
-               '&title='.&HTML::Entities::encode($title,'<>&"').
-               '&url='.&HTML::Entities::encode($url,'<>&"');
+    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[
@@ -434,10 +451,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.  
@@ -465,21 +484,52 @@ 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
 
-Bugs
+=item Bugs
 
 The method used to restrict user input will fail in the year 2400.
 
+=back
+
 =cut
 
 ##############################################
 ##############################################
 sub date_setter {
     my ($formname,$dname,$currentvalue,$special,$includeempty,$state,
-        $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink) = @_;
+        $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink,$no_mm_ss,$no_ss) = @_;
     my $now = time;
 
     my $tzname;
@@ -488,6 +538,8 @@ sub date_setter {
 
     if (! defined($state) || $state ne 'disabled') {
         $state = '';
+    } else {
+        $state = 'disabled="disabled"';
     }
     if (! defined($no_hh_mm_ss)) {
         $no_hh_mm_ss = 0;
@@ -623,7 +675,7 @@ 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()">};
     }
     #
@@ -632,17 +684,26 @@ ENDJS
         $result .= &mt('[_1] [_2] [_3] ',
                        $monthselector,$dayselector,$yearselector).
                    $tzone;
-        if (!$nolink) {
-            $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
-        }
+    } 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).
                    $tzone;
-        if (!$nolink) {
-            $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
-        }
+    }
+    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;
@@ -686,7 +747,9 @@ sub build_url {
 
 get_date_from_form retrieves the date specified in an &date_setter form.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4
 
@@ -700,6 +763,8 @@ The unix time to use as the default in c
 
 =back
 
+=back
+
 Returns: Unix time represented in the form.
 
 =cut
@@ -871,7 +936,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'
@@ -885,6 +952,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
@@ -932,7 +1001,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
@@ -946,9 +1015,11 @@ 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
+
+=item Inputs
 
-=over 4
+=over
 
 =item $r Apache request
 
@@ -956,15 +1027,18 @@ Inputs
 
 =back
 
-Returns a hash containing the progress state data structure.
+=back
 
+Returns a hash containing the progress state data structure.
 
 =item &Update_PrgWin()
 
 Updates the text in the progress indicator.  Does not increment the count.
 See &Increment_PrgWin.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4
 
@@ -976,25 +1050,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
 
@@ -1005,7 +1092,9 @@ Returns: none
 
 Closes the progress window.
 
-Inputs:
+=over
+
+=item Inputs:
 
 =over 4 
 
@@ -1015,6 +1104,8 @@ Inputs:
 
 =back
 
+=back
+
 Returns: none
 
 =back
@@ -1101,12 +1192,13 @@ sub Close_PrgWin {
     undef(%$prog_state);
 }
 
+
 # ------------------------------------------------------- Puts directory header
 
 sub crumbs {
-    my ($uri,$target,$prefix,$form,$skiplast)=@_;
+    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).'"';
@@ -1130,9 +1222,9 @@ sub crumbs {
             &Apache::loncommon::inhibit_menu_check(\$href_path);
             if ($form) {
                 my $href = 'javascript:'.$form.".action='".$href_path."';".$form.'.submit();';
-                $output.=qq{<a href="$href"$target>$dir</a>/};
+                $output.=qq{<a href="$href"$onclick$target>$dir</a>/};
             } else {
-                $output.=qq{<a href="$href_path"$target>$dir</a>/};
+                $output.=qq{<a href="$href_path"$onclick$target>$dir</a>/};
             }
         }
     } else {
@@ -1203,17 +1295,18 @@ sub htmlareaheaders {
 ENDEDITOR
 	}
     $s.=(<<ENDJQUERY);
-<script type="text/javascript" src="/adm/jQuery/js/jquery-1.6.2.min.js"></script>
-<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.8.16.custom.min.js"></script>
-<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.8.16.custom.css" />
+<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/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.js"></script>
+<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.js"></script>
+<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>
 
 ENDJQUERY
 	return $s;
@@ -1230,13 +1323,25 @@ sub htmlarea_lang {
     return $lang;
 }
 
+# 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"}}});
+});';
+}
+
 # ----------------------------------------- Script to activate only some fields
 
 sub htmlareaselectactive {
     my ($args) = @_; 
     unless (&htmlareabrowser()) { return ''; }
     my $output='<script type="text/javascript" defer="defer">'."\n"
-              .'// <![CDATA['."\n";
+              .'// <![CDATA['."\n"
+              .'//<!-- BEGIN LON-CAPA Internal'."\n";
     my $lang = &htmlarea_lang();
     my $fullpage = 'false';
     my ($dragmath_prefix,$dragmath_helpicon,$dragmath_whitespace);
@@ -1259,6 +1364,14 @@ sub htmlareaselectactive {
             }
         }
     }
+
+    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) {
@@ -1267,6 +1380,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",
@@ -1278,6 +1426,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) {
@@ -1287,14 +1497,14 @@ sub htmlareaselectactive {
     	var rt_enabled  = $(this).hasClass("LC_enable_rt");
         if (rt_enabled) {
     		startRichEditor(id);
-			$("#LC_rt_"+id).html("<b>&laquo; Plain text</b>");
-			$("#LC_rt_"+id).attr("title", "Disable rich text formatting and edit in plain text");
+			$("#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>Rich formatting &raquo;</b>");
-			$("#LC_rt_"+id).attr("title", "Enable rich text formatting (bold, italic, etc.)");
+			$("#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");
 	}';
@@ -1315,12 +1525,12 @@ sub htmlareaselectactive {
 			var id = $(this).attr("id");
                         var rt_enabled = containsBlockHtml(id);
 			if(rt_enabled) {
-				$(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"Disable rich text formatting and edit in plain text\" class=\"LC_disable_rt\"><b>&laquo; Plain text</b></a></div>");				
+				$(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=\"Enable rich text formatting (bold, italic, etc.)\" class=\"LC_enable_rt\"><b>Rich formatting &raquo;</b></a></div>");
+				$(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 '') {
@@ -1334,21 +1544,21 @@ sub htmlareaselectactive {
 		});
 		$(".LC_richDefaultOn").each(function() {
 			var id = $(this).attr("id");
-			$(this).before("<div><a href=\"#\" id=\"LC_rt_"+id+"\" title=\"Disable rich text formatting and edit in plain text\" class=\"LC_disable_rt\"><b>&laquo; Plain text</b></a></div>");				
+			$(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=\"Enable rich text formatting (bold, italic, etc.)\" class=\"LC_enable_rt\"><b>Rich formatting &raquo;</b></a></div>");
+			$(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);
 		});
-                $.fn.jPicker.defaults.images.clientPath="/adm/jpicker/images/";
-                $(".colorchooser").jPicker();
 
 
 	});
 ';
+    $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.
@@ -1375,7 +1585,8 @@ sub htmlareaselectactive {
     #   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 $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;
@@ -1402,6 +1613,40 @@ sub htmlareaselectactive {
       });
    }
 });
+
+    /* 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 .= '
@@ -1432,6 +1677,7 @@ JAVASCRIPT
 
     }
     $output.="\nwindow.status='Activated Editfields';\n"
+            .'// END LON-CAPA Internal -->'."\n"
             .'// ]]>'."\n"
             .'</script>';
     return $output;
@@ -1459,7 +1705,9 @@ 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{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') &&
+               (!$env{'request.role.adv'}));
     if (($env{'request.noversionuri'} =~ m{^/adm/(viewclasslist|navmaps)($|\?)})
         || ($env{'request.noversionuri'} =~ m{^/adm/.*/aboutme($|\?)})) {
 
@@ -1473,7 +1721,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|exttools?)($|\?)})
            ));
 }
 
@@ -1547,13 +1795,55 @@ 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
-        $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.
+=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 $css_class
+
+optional name for the class to apply to the table for CSS
+
+=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.
+
+=back
+
+=back
+
 Returns a string containing breadcrumbs for the current page.
 
 =item &clear_breadcrumbs()
@@ -1581,7 +1871,7 @@ returns: nothing
     
     sub breadcrumbs {
         my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, 
-            $CourseBreadcrumbs) = @_;
+            $CourseBreadcrumbs,$topic_help,$topic_help_text,$crstype) = @_;
         #
         $css_class ||= 'LC_breadcrumbs';
 
@@ -1596,6 +1886,11 @@ returns: nothing
         #
         # The first one should be the course or a menu link
         if (!defined($menulink)) { $menulink=1; }
+        if ((($crstype eq 'Placement') || (($env{'request.course.id'}) &&
+            ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement'))) &&
+            (!$env{'request.role.adv'})) {
+            undef($menulink);
+        }
         if ($menulink) {
             my $description = 'Menu';
             my $no_mt_descr = 0;
@@ -1605,6 +1900,14 @@ returns: nothing
                 $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;
+                    }
+                }
             }
             $menulink =  {  href   =>'/adm/menu',
                             title  =>'Go to main menu',
@@ -1620,13 +1923,17 @@ returns: nothing
             }
         }
         my $links;
-        if ((&show_return_link) && (!$CourseBreadcrumbs)) {
+        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 ($env{'request.noversionuri'} eq '/adm/searchcat') {
+                $hashref->{'target'} = '_top'; 
+            }
             $links=&htmltag( 'a','<img src="/res/adm/pages/tolastloc.png" alt="'.$alttext.'" class="LC_icon" />',
-                            { href => '/adm/flip?postdata=return:',
-                              title => &mt('Back to most recent content resource'),
-                              class => 'LC_menubuttons_link',
-                            });
+                             $hashref);
             $links=&htmltag('li',$links);
         }
         $links.= join "", 
@@ -1655,8 +1962,10 @@ returns: nothing
         # last breadcrumb is the first order heading of a page
         # for course breadcrumbs it's just bold
 
-        $links .= &htmltag( 'li', htmltag($CourseBreadcrumbs ? 'b' : 'h1',
-                $lasttext), {title => $lasttext});
+        if ($lasttext ne '') {
+            $links .= &htmltag( 'li', htmltag($CourseBreadcrumbs ? 'b' : 'h1',
+                    $lasttext), {title => $lasttext});
+        }
 
         my $icons = '';
         $faq  = $last->{'faq'}  if (exists($last->{'faq'}));
@@ -1674,18 +1983,23 @@ returns: nothing
                                                          $component_help,
                                                          $faq,$bug);
         }
+        if ($topic_help && $topic_help_text) {
+           $icons .= ' '.&Apache::loncommon::help_open_topic($topic_help,&mt($topic_help_text),'',
+                                                             undef,600);
+        }
         #
 
 		
-
-        unless ($CourseBreadcrumbs) {
-            $links = &htmltag('ol',  $links, { id => "LC_MenuBreadcrumbs"   });
-        } else {
-            $links = &htmltag('ul',  $links, { class => "LC_CourseBreadcrumbs" });
+        if ($links ne '') {
+            unless ($CourseBreadcrumbs) {
+                $links = &htmltag('ol',  $links, { id => "LC_MenuBreadcrumbs"   });
+            } else {
+                $links = &htmltag('ul',  $links, { class => "LC_CourseBreadcrumbs" });
+            }
         }
 
 
-        if ($component) {
+        if (($component) || ($topic_help && $topic_help_text)) {
             $links = &htmltag('span', 
                              ( $no_mt ? $component : mt($component) ).
                              ( $icons ? $icons : '' ),
@@ -1693,11 +2007,24 @@ returns: nothing
                              .$links 
 ;
         }
-        
-        &render_tools(\$links);
-        $links = &htmltag('div', $links, 
-                        { id => "LC_breadcrumbs" }) unless ($CourseBreadcrumbs) ;
-        &render_advtools(\$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);
@@ -1727,7 +2054,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 
 
@@ -1741,7 +2070,9 @@ remaining items in right of breadcrumbs
 advanced tools shown in a separate box below breadcrumbs line 
 
 =back
- 
+
+=back
+
 returns: nothing
 
 =cut
@@ -1775,13 +2106,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
 
@@ -1807,8 +2157,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
 
@@ -1825,6 +2184,87 @@ returns: nothing
 
 } # End of scope for @Crumbs
 
+sub docs_breadcrumbs {
+    my ($allowed,$crstype,$contenteditor,$title,$precleared)=@_;
+    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;
+        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*)$//;
+        unless ($supplementalflag) {
+            if ($contenteditor) { 
+                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) {
+            $menulink = 1;
+        }
+        return (&breadcrumbs(undef,undef,$menulink,'nohelp',undef,undef,
+                             $contenteditor),
+                             $randompick,$ishidden,$isencrypted,$plain,
+                             $is_random_order,$container);
+    } else {
+        return \@docs_crumbs;
+    }
+}
+
 ############################################################
 ############################################################
 
@@ -2052,14 +2492,14 @@ sub course_selection {
 
     my $courseform='<b>'.&Apache::loncommon::selectcourse_link
                      ($formname,'pickcourse','pickdomain','coursedesc','',1,$crstype).'</b>';
-        $output .= '<input type="radio" name="coursepick" value="all" onclick="coursePick(this.form)" />'.$allcrs.'<br />';
+        $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="'.$standardnames->[0].
-               '" onChange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n".
+               '" onchange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n".
                ' <option value="-1" />Select'."\n";
             my @items = ();
             my @longitems = ();
@@ -2089,7 +2529,7 @@ sub course_selection {
             for (my $i=1; $i<$numtitles; $i++) {
                 $output .= '<td>'.$$codetitles[$i].'<br />'."\n".
                           '<select name="'.$standardnames->[$i].
-                          '" onChange="courseSet('."'$$codetitles[$i]'".')">'."\n".
+                          '" onchange="courseSet('."'$$codetitles[$i]'".')">'."\n".
                           '<option value="-1">&lt;-Pick '.$$codetitles[$i-1].'</option>'."\n".
                           '</select>'."\n".
                           '</td>';
@@ -2097,7 +2537,15 @@ sub course_selection {
             $output .= '</tr></table><br />';
         }
     }
-    $output .= '<input type="radio" name="coursepick" value="specific" onclick="coursePick(this.form);opencrsbrowser('."'".$formname."','dccourse','dcdomain','coursedesc','','1','$crstype'".')" />'.$pickspec.' '.$courseform.'&nbsp;&nbsp;<input type="text" value="0" size="4" name="coursetotal" /><input type="hidden" name="courselist" value="" />selected.<br />'."\n";
+    $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;
 }
 
@@ -2218,6 +2666,53 @@ sub resource_info_box {
     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>';
+}
+
 ##############################################
 ##############################################
 
@@ -2462,10 +2957,12 @@ 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'.
+    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)?');
-    my $delfilewarn = &mt('You have indicated you wish to remove some files previously included in your submission.').'\\n'.
+    &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}))) {
@@ -2706,7 +3203,7 @@ ENDSCRIPT
 ##############################################
 
 sub resize_scrollbox_js {
-    my ($context,$tabidstr) = @_;
+    my ($context,$tabidstr,$tid) = @_;
     my (%names,$paddingwfrac,$offsetwfrac,$offsetv,$minw,$minv);
     if ($context eq 'docs') {
         %names = (
@@ -2716,7 +3213,7 @@ sub resize_scrollbox_js {
                    scroll => 'contentscroll',
                    boxh   => 'contenteditor',
                  );
-        $paddingwfrac = 0.09; 
+        $paddingwfrac = 0.09;
         $offsetwfrac = 0.015;
         $offsetv = 20;
         $minw = 250;
@@ -2742,9 +3239,11 @@ window.onresize=callResize;
 
 ';
     if ($context eq 'docs') {
-        $output .= '
-var activeTab;
-';
+        if ($env{'form.active'}) {
+            $output .= "\nvar activeTab = '$env{'form.active'}$tid';\n";
+        } else {
+            $output .= "\nvar activeTab = '';\n";
+        }
     }
     $output .=  <<"FIRST";
 
@@ -2755,6 +3254,7 @@ function resize_scrollbox(scrollboxname,
     var scrolltableid = 'table_'+scrollboxname;
     var scrollbox;
     var scrolltable;
+    var ismobile = '$env{'browser.mobile'}';
 
     if (document.getElementById("$names{'boxw'}") == null) {
         return;
@@ -2791,6 +3291,7 @@ FIRST
     }
     $output .= <<"SECOND";
     var listwchange;
+    var scrollchange;
     if (chkw == 1) {
         var boxw = document.getElementById("$names{'boxw'}").offsetWidth;
         var itemw;
@@ -2802,6 +3303,7 @@ FIRST
 
         var scrollboxw = scrollbox.offsetWidth;
         var scrollboxscrollw = scrollbox.scrollWidth;
+        var scrollstart = scrollboxw;
 
         var offsetw = parseInt(vpw * $offsetwfrac);
         var paddingw = parseInt(vpw * $paddingwfrac);
@@ -2876,36 +3378,24 @@ PARAMSONE
             }
         }
 
+        if (newscrollboxw != scrollboxw) {
+            scrollchange = 1;
+        }
+
         if (itemid.offsetWidth != itemwstart) {
             listwchange = 1;
         }
-THIRD
-    if ($context eq 'docs') {
-        $output .= <<"DOCSTWO";
-        if (activeTab == 'cc1') {
-            if (document.getElementById('cc_hrule') != null) {
-                document.getElementById('cc_hrule').style.width=actabw+"px";
-            }
-        } else {
-            if (activeTab == 'bb1') {
-                if (document.getElementById('bb_hrule') != null) {
-                    document.getElementById('bb_hrule').style.width=actabw+"px";
-                }
-            } else {
-                if (activeTab == 'ee2') {
-                    if (document.getElementById('ee_hrule') != null) {
-                        document.getElementById('ee_hrule').style.width=actabw+"px";
-                    }
-                }
-            }
-        }
-DOCSTWO
-    }
-    $output .= <<"FOURTH";
     }
     if ((chkh == 1) || (listwchange)) {
+        var itemid = document.getElementById("$names{'item'}");
+        if (itemid != null) {
+            itemh = itemid.offsetHeight;
+        }
         var primaryheight = document.getElementById('LC_nav_bar').offsetHeight;
-        var secondaryheight = document.getElementById('LC_secondary_menu').offsetHeight;
+        var secondaryheight;
+        if (document.getElementById('LC_secondary_menu') != null) { 
+            secondaryheight = document.getElementById('LC_secondary_menu').offsetHeight;
+        }
         var crumbsheight = document.getElementById('LC_breadcrumbs').offsetHeight;
         var dccidheight = 0;
         if (document.getElementById('dccid') != null) {
@@ -2921,6 +3411,7 @@ DOCSTWO
 
         var scrollboxheight = scrollbox.offsetHeight;
         var scrollboxscrollheight = scrollbox.scrollHeight;
+        var scrollboxh = scrollboxheight;
 
         var minvscrollbox = $minv;
         var offsetv = $offsetv;
@@ -2951,6 +3442,13 @@ DOCSTWO
                 scrollbox.style.height = newscrollheight+"px";
             }
         }
+        var newscrollboxh = scrollbox.offsetHeight;
+        if (scrollboxh != newscrollboxh) {
+            scrollchange = 1;
+        }
+    }
+    if (ismobile && scrollchange) {
+        \$("#div_$names{'scroll'}").getNiceScroll().onResize();
     }
     return;
 }
@@ -2961,10 +3459,110 @@ function callResize() {
     timer=setTimeout('resize_scrollbox("$names{'scroll'}","1","1")',500);
 }
 
-FOURTH
+THIRD
     return $output;
 }
 
+##############################################
+##############################################
+
+sub javascript_jumpto_resource {
+    my $confirm_switch = &mt("Editing requires switching to the resource's home server.")."\n".
+                         &mt('Switch server?');
+    &js_escape(\$confirm_switch);
+    return (<<ENDUTILITY)
+
+function go(url) {
+   if (url!='' && url!= null) {
+       currentURL = null;
+       currentSymb= null;
+       window.location.href=url;
+   }
+}
+
+function need_switchserver(url) {
+    if (url!='' && url!= null) {
+        if (confirm("$confirm_switch")) {
+            go(url);
+        }
+    }
+    return;
+}
+
+ENDUTILITY
+
+}
+
+sub jump_to_editres {
+    my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$folderpath,
+        $title,$idx,$suppurl,$todocs,$suppanchor) = @_;
+    my ($jscall,$anchor);
+    if ($switchserver) {
+        if ($home) {
+            $cfile = '/adm/switchserver?otherserver='.$home.'&amp;role='.
+                     &HTML::Entities::encode($env{'request.role'},'"<>&');
+            if ($symb) {
+                $cfile .= '&amp;symb='.&HTML::Entities::encode($symb,'"<>&');
+            } 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)."');";
+        }
+    } else {
+        unless ($cfile =~ m{^/priv/}) {
+            if ($cfile =~ m{^(/adm/wrapper/ext/[^#]+)#([^#]+)$}) {
+                $cfile = $1;
+                $anchor = $2;
+            }
+            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 ($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;
+}
 
 ##############################################
 ##############################################
@@ -3028,7 +3626,7 @@ END
 sub htmltag{
     return
         qq|<$_[0]|
-        . join( '', map { qq| $_="${$_[2]}{$_}"| if ${$_[2]}{$_} } keys %{ $_[2] } )
+        . join( '', map { qq| $_="${$_[2]}{$_}"| if ${$_[2]}{$_} } keys(%{ $_[2] }) )
         . ($_[1] ? qq|>$_[1]</$_[0]>| : qq|/>|). "\n";
 };
 
@@ -3090,7 +3688,9 @@ sub scripttag {
 
 Constructs a XHTML list from \@array.
 
-input: 
+=over
+
+=item input: 
 
 =over
 
@@ -3104,7 +3704,9 @@ Attributes for <ul> and <li> passed in a
 See htmltag() for more details.
 
 =back
- 
+
+=back
+
 returns: XHTML list as String. 
 
 =cut   
@@ -3223,13 +3825,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
 
@@ -3256,10 +3864,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
@@ -3282,15 +3898,19 @@ 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: ./.
 
 Returns: HTML code with function list end
+
 =cut
 
 sub end_funclist {
@@ -3317,7 +3937,7 @@ A string that's used as visually highlig
 it's value evaluates to false.
 
 =back
- 
+
 returns: XHTML list as string. 
 
 =back
@@ -3330,7 +3950,43 @@ sub funclist_from_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;