--- loncom/homework/structuretags.pm 2012/09/12 05:14:09 1.508 +++ loncom/homework/structuretags.pm 2015/01/19 15:35:53 1.527 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # definition of tags that give a structure to a document # -# $Id: structuretags.pm,v 1.508 2012/09/12 05:14:09 raeburn Exp $ +# $Id: structuretags.pm,v 1.527 2015/01/19 15:35:53 goltermann Exp $ # # Copyright Michigan State University Board of Trustees # @@ -225,6 +225,7 @@ sub end_tex { sub homework_js { return &Apache::loncommon::resize_textarea_js(). + &Apache::loncommon::colorfuleditor_js(). &setmode_javascript(). <<'JS'; JS @@ -370,6 +420,9 @@ sub page_start { "if (typeof swmenu != 'undefined') {swmenu.currentURL=null;}\n". &Apache::loncommon::browser_and_searcher_javascript(). "\n\n"; + if ($target eq 'edit') { + $extra_head .= &Apache::edit::js_update_linknum(); + } } } @@ -379,10 +432,10 @@ sub page_start { } elsif (!defined($found{'body'}) && $env{'request.state'} eq 'construct') { if ($target eq 'web' || $target eq 'edit') { - # Breadcrumbs for Construction Space + # Breadcrumbs for Authoring Space &Apache::lonhtmlcommon::clear_breadcrumbs(); &Apache::lonhtmlcommon::add_breadcrumb({ - 'text' => 'Construction Space', + 'text' => 'Authoring Space', 'href' => &Apache::loncommon::authorspace($env{'request.uri'}), }); # breadcrumbs (and tools) will be created @@ -414,6 +467,8 @@ sub page_start { $body_args{'add_entries'} = \%add_entries; if ( $env{'request.state'} eq 'construct') { $body_args{'only_body'} = 1; + } elsif ($target eq 'web') { + $body_args{'print_suppress'} = 1; } } $body_args{'no_auto_mt_title'} = 1; @@ -453,7 +508,6 @@ sub page_start { } $form_tag_start.='>'."\n"; - my $symb=&Apache::lonnet::symbread(); if ($symb =~ /\S/) { $symb= &HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb)); @@ -625,28 +679,259 @@ sub problem_edit_buttons { return $result; } +sub insert_menu_datastructure { + + my $template_menu = &template_dropdown_datastructure(); + my $responseblock_menu = &responseblock_dropdown_datastructure(); + my $conditional_scripting = &conditional_scripting_datastructure(); + my $misc = &misc_datastructure(); + + my @menu = ($template_menu, $responseblock_menu, $conditional_scripting, $misc); + return \@menu; + +} + +sub template_dropdown_datastructure { + # gathering the all templates and their path, title, category and help topic + my @templates = &Apache::lonhomework::get_template_list('problem'); + # template category => title + my %tmplthash = (); + # template title => path + my %tmpltcontent = (); + + foreach my $template (@templates){ + # put in hash if the template is not empty + unless ($template->[1] eq ''){ + push(@{$tmplthash{$template->[2]}}, $template->[1]); + push(@{$tmpltcontent{$template->[1]}},$template->[0]); + } + } + + my $catList = []; + foreach my $cat (sort keys %tmplthash) { + my $catItems = []; + foreach my $title (sort @{$tmplthash{$cat}}) { + my $path = $tmpltcontent{$title}->[0]; + my $code; + open(FH, "<$path"); + while(){ + $code.= $_ unless $_ =~ /()|(<\/problem>)/; + } + close(FH); + + if ($code ne '') { + my $href = 'javascript:insertText(\'' . &convert_for_js(&HTML::Entities::encode($code)) . '\')'; + my $currItem = [$href, $title, undef]; + push @{$catItems}, $currItem; + } + } + push @{$catList}, [$catItems, $cat, undef]; + } + + my $templDropdown = [$catList, &mt("Complete Problem Templates"), undef]; + return $templDropdown; +} + +sub responseblock_dropdown_datastructure { + + my $mathCat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_formularesponse())) . "\')", &mt("Formula Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_functionplotresponse())) . "\')", &mt("Function Plot Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_mathresponse())) . "\')", &mt("Math Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_numericalresponse())) . "\')", &mt("Numerical Response"), undef] + ], + &mt("Math"), + undef + ]; + + my $miscCat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_imageresponse())) . "\')", &mt("Click on Image"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_customresponse())) . "\')", &mt("Custom Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_externalresponse())) . "\')", &mt("External Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_matchresponse())) . "\')", &mt("Match Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_radiobuttonresponse())) . "\')", &mt("One out of N Statement"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_optionresponse())) . "\')", &mt("Optionresponse"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_rankresponse())) . "\')", &mt("Rank Values"), undef] + ], + &mt("Misc"), + undef + ]; + + my $chemCat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_reactionresponse())) . "\')", &mt("Chemical Reaction"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_organicresponse())) . "\')", &mt("Organic Structure"), undef] + ], + &mt("Chemical"), + undef + ]; + + my $textCat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_stringresponse())) . "\')", &mt("String Response"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_essayresponse())) . "\')", &mt("Essay"), undef] + ], + &mt("Text"), + undef + ]; + + my $cats = [[$mathCat, $miscCat, $chemCat, $textCat], &mt("Response Types"), undef]; + return $cats; +} + + +sub conditional_scripting_datastructure { +# TODO: corresponding routines should be used for the javascript:insertText parts +# instead of the placeholder routine default_xml_tag with the tags +# e.g. &default_xml_tag("postanswerdate") should be replaced with a routine which +# returns the corresponding content for this case + +#TODO translated is currently temporarily here, another solution should be found where the +# needed string can be retrieved + + my $translatedTag = ' + + + +'; + + my $cat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode($translatedTag)) . "\')", &mt("Translated Tag"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("block"))) . "\')", &mt("Conditional Block"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("postanswerdate"))) . "\')", &mt("After Answer Block"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("preduedate"))) . "\')", &mt("Before Due Date Block"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("solved"))) . "\')", &mt("Block For After Solved"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("notsolved"))) . "\')", &mt("Block For When Not Solved"), undef] + ], + &mt("Contitional Scripting"), + undef + ]; + + return $cat; +} + +sub misc_datastructure { + + my $graphicalCat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_img())) . "\')", &mt("Image"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::lonplot::insert_gnuplot())) . "\')", &mt("GNU Plot"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_organicstructure())) . "\')", &mt("Organic Structure"), undef] + ], + "Graphical", + undef + ]; + + my $advancedCat = [ + [ + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::edit::insert_script())) . "\')", &mt("Script Block"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("allow"))) . "\')", &mt("File Dependencies"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&default_xml_tag("import"))) . "\')", &mt("Import a File"), undef], + ["javascript:insertText(\'" . &convert_for_js(&HTML::Entities::encode(&Apache::londefdef::insert_meta())) . "\')", &mt("Custom Metadata"), undef] + ], + "advanced", + undef + ]; + + my $cats = [[$graphicalCat, $advancedCat], &mt("misc"), undef]; + return $cats; +} + +# helper routine for the datastructure building subroutines +sub default_xml_tag { + my ($tag) = @_; + return "\n<$tag>"; +} + + +sub helpmenu_datastructure { + + my $width = 500; + my $height = 600; + + my $helpers = [ + ['Problem_LON-CAPA_Functions', &mt('Script Functions')], + ['Greek_Symbols', &mt('Greek Symbols')], + ['Other_Symbols', &mt('Other Symbols')], + ['Authoring_Output_Tags', &mt('Output Tags')], + ['Authoring_Multilingual_Problems', + &mt('How to create problems in different languages')] + ]; + + my $help_structure = []; + + foreach my $count (0..(scalar(@{$helpers})-1)) { + my $filename = $helpers->[$count]->[0]; + my $title = $helpers->[$count]->[1]; + my $href = &HTML::Entities::encode("javascript:openMyModal('/adm/help/$filename.hlp',$width,$height,'yes');"); + push @{$help_structure}, [$href, $title, undef]; + } + + return $help_structure; +} + +# we need substitution to not break javascript code +sub convert_for_js { + my $return = shift; + $return =~ s|script|ESCAPEDSCRIPT|g; + $return =~ s|\n|\\r\\n|g; + $return =~ s|'|\\'|g; + $return =~ s|'|\\'|g; + return $return; +} + sub problem_edit_header { - return ''. + my ($mode)=@_; + my $return = ''. &remember_problem_state('edit').' -
-
-'.&mt('Problem Editing').&Apache::loncommon::help_open_menu('Problem Editing','Problem_Editor_XML_Index',5,'Authoring').' -
'. -''. -&problem_edit_buttons().' -
-'.&Apache::lonxml::message_location().' -
-'. - '" + $result.=&Apache::edit::start_table($token)."" .'' - .'' + .'' .'' - .'
'; +
+
+ '.&mt('Problem Editing').$mode.&Apache::loncommon::help_open_menu('Problem Editing','Problem_Editor_XML_Index',5,'Authoring').' +
'. + ''. + &problem_edit_buttons(); + + $return.='
+
' + .&Apache::lonxml::message_location(); + $return .= ' + '; + + $return .= ' + +
'; + return $return; } sub problem_edit_footer { + my $resource = $env{'request.ambiguous'}; return '

'. "\n\n".&Apache::loncommon::end_page(); @@ -711,7 +996,7 @@ sub problem_web_to_edit_header { ".&mt("Problem Type:")." ".&option('yes','problemstatus').&mt("Show Feedback")." - ".&option('no', 'problemstatus').&mt("Don't Show Incorect/Correct Feedback")." + ".&option('no', 'problemstatus').&mt("Don't Show Incorrect/Correct Feedback")." ".&option('no_feedback_ever', 'problemstatus').&mt("Don't Show Any Feedback")." @@ -831,7 +1116,7 @@ sub initialize_storage { } %Apache::lonhomework::history= &Apache::lonnet::tmprestore($namespace,'',$domain,$name); - my ($temp)=keys %Apache::lonhomework::history ; + my ($temp)=keys(%Apache::lonhomework::history) ; &Apache::lonxml::debug("Return message of $temp"); } else { %Apache::lonhomework::history= @@ -839,7 +1124,7 @@ sub initialize_storage { } #ignore error conditions - my ($temp)=keys %Apache::lonhomework::history ; + my ($temp)=keys(%Apache::lonhomework::history); if ($temp =~ m/^error:.*/) { %Apache::lonhomework::history=(); } } @@ -847,12 +1132,37 @@ sub initialize_storage { =item finalize_storage() - Stores away the result has to a student's environment - checks form.grade_ for specific values, other wises stores - to the running users environment - Will increment totals for attempts, students, and corrects - if running user has student role. - + Stores away the result hash to a student's environment; + checks form.grade_ for specific values, otherwise stores + to the running user's environment. + + &check_correctness_changes() is called in two circumstances + in which the results hash is to be stored permanently, for + grading triggered by a student's submission, where feedback on + correctness is to be provided to the student. + + 1. Immediately prior to storing the results hash + + To handle the case where a student's submission (and award) were + stored after history was retrieved in &initialize_storage(), e.g., + if a student submitted answers in quick succession (e.g., from + multiple tabs). &Apache::inputtags::hidealldata() is called for + any parts with out-of-order storage (i.e., correct then incorrect, + where awarded >= 1 when correct). + + 2. Immediately after storing the results hash + + To handle the case where lond on the student's homeserver returns + delay:N -- where N is the number of transactions between the last + retrieved in &initialize_storage() and the last stored immediately + before permanent storage of the current transaction via + lond::store_handler(). &Apache::grades::makehidden() is called + for any parts with out-of-order storage (i.e., correct then incorrect, + where awarded >= 1 when correct). + + Will call &store_aggregates() to increment totals for attempts, + students, and corrects, if running user has student role. + =cut @@ -873,8 +1183,92 @@ sub finalize_storage { $namespace,'',$domain,$name); &Apache::lonxml::debug('Construct Store return message:'.$result); } else { + my ($laststore,$checkedparts,@parts,%postcorrect); + if (($env{'user.name'} eq $name) && ($env{'user.domain'} eq $domain) && + (!$Apache::lonhomework::scantronmode) && (!defined($env{'form.grade_symb'})) && + (!defined($env{'form.grade_courseid'}))) { + if ($Apache::lonhomework::history{'version'}) { + $laststore = $Apache::lonhomework::history{'version'}.'='. + $Apache::lonhomework::history{'timestamp'}; + } else { + $laststore = '0=0'; + } + my %record = &Apache::lonnet::restore($symb,$courseid,$domain,$name); + if ($record{'version'}) { + my ($newversion,$oldversion,$oldtimestamp); + if ($Apache::lonhomework::history{'version'}) { + $oldversion = $Apache::lonhomework::history{'version'}; + $oldtimestamp = $Apache::lonhomework::history{'timestamp'}; + } else { + $oldversion = 0; + $oldtimestamp = 0; + } + if ($record{'version'} > $oldversion) { + if ($record{'timestamp'} >= $oldtimestamp) { + $laststore = $record{'version'}.'='.$record{'timestamp'}; + $newversion = $record{'version'} + 1; + $checkedparts = 1; + foreach my $key (keys(%Apache::lonhomework::results)) { + if ($key =~ /^resource\.([^\.]+)\.solved$/) { + my $part = $1; + if ($Apache::lonhomework::results{$key} eq 'incorrect_attempted') { + push(@parts,$part); + } + } + } + if (@parts) { + my @parts_to_hide = &check_correctness_changes($symb,$courseid,$domain,$name, + \%record,\@parts,$newversion, + $oldversion); + if (@parts_to_hide) { + foreach my $part (@parts_to_hide) { + $postcorrect{$part} = 1; + &Apache::inputtags::hidealldata($part); + } + } + } + } + } + } + } $result=&Apache::lonnet::cstore(\%Apache::lonhomework::results, - $symb,$courseid,$domain,$name); + $symb,$courseid,$domain,$name,$laststore); + if ($result =~ /^delay\:(\d+)$/) { + my $numtrans = $1; + my ($oldversion) = split(/=/,$laststore); + if ($numtrans) { + my $newversion = $oldversion + 1 + $numtrans; + my @possparts; + if ($checkedparts) { + foreach my $part (@parts) { + unless ($postcorrect{$part}) { + push(@possparts,$part); + } + } + } else { + foreach my $key (keys(%Apache::lonhomework::results)) { + if ($key =~ /^resource\.([^\.]+)\.solved$/) { + my $part = $1; + unless ($postcorrect{$part}) { + if ($Apache::lonhomework::results{$key} eq 'incorrect_attempted') { + push(@possparts,$part); + } + } + } + } + } + if (@possparts) { + my %newrecord = &Apache::lonnet::restore($symb,$courseid,$domain,$name); + my @parts_to_hide = &check_correctness_changes($symb,$courseid,$domain,$name, + \%newrecord,\@possparts,$newversion, + $oldversion); + if (@parts_to_hide) { + my $partslist = join(',',@parts_to_hide); + &Apache::grades::makehidden($newversion,$partslist,\%newrecord,$symb,$domain,$name,1); + } + } + } + } &Apache::lonxml::debug('Store return message:'.$result); &store_aggregates($symb,$courseid); } @@ -886,6 +1280,62 @@ sub finalize_storage { =pod +=item check_correctness_changes() + + For all parts for which current results contain a solved status + of "incorrect_attempted", check if there was a transaction in which + solved was set to "correct_by_student" in the time since the last + transaction (retrieved when &initialize_storage() was called i.e., + when &start_problem() was called), unless: + (a) questiontype parameter is set to survey or anonymous survey (+/- credit) + (b) problemstatus is set to no or no_feedback_ever + If such a transaction exists, and did not occur after "reset status" + by a user with grading privileges, then the current transaction is an + example of an out-of-order transaction (i.e., incorrect occurring after + correct). Accordingly, the current transaction should be hidden. + +=cut + + +sub check_correctness_changes { + my ($symb,$courseid,$domain,$name,$record,$parts,$newversion,$oldversion) = @_; + my @parts_to_hide; + unless ((ref($record) eq 'HASH') && (ref($parts) eq 'ARRAY')) { + return @parts_to_hide; + } + if (@{$parts}) { + my $usec; + if (($env{'user.name'} eq $name) && ($env{'user.domain'} eq $domain) && + ($env{'request.course.id'} eq $courseid)) { + $usec = $env{'request.course.sec'}; + } else { + $usec = &Apache::lonnet::getsection($domain,$name,$courseid); + } + foreach my $id (@{$parts}) { + next if (($Apache::lonhomework::results{'resource.'.$id.'.type'} =~ /survey/) || + (&Apache::lonnet::EXT("resource.$id.problemstatus",$symb, + $domain,$name,$usec,undef,$courseid) =~ /^no/)); + my $reset; + for (my $i=$newversion-1; $i>=$oldversion; $i--) { + if (($record->{$i.':resource.'.$id.'.regrader'}) && + ($record->{$i.':resource.'.$id.'.tries'} eq '') && + ($record->{$i.':resource.'.$id.'.award'} eq '')) { + $reset = 1; + } elsif (($record->{$i.":resource.$id.solved"} eq 'correct_by_student') && + ($record->{$i.":resource.$id.awarded"} >= 1)) { + unless ($reset) { + push(@parts_to_hide,$id); + last; + } + } + } + } + } + return @parts_to_hide; +} + +=pod + item store_aggregates() Sends hash of values to be incremented in nohist_resourcetracker.db @@ -948,7 +1398,7 @@ sub store_aggregates { } } } - if (keys (%aggregate) > 0) { + if (keys(%aggregate) > 0) { &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate, $cdomain,$cname); } @@ -967,8 +1417,8 @@ sub checkout_msg { 'resource'=>'The resource needs to be checked out', 'id_expln'=>'As a resource gets checked out, a unique timestamped ID is given to it, and a permanent record is left in the system.', 'warning'=>'Checking out resources is subject to course policies, and may exclude future credit even if done erroneously.', - 'checkout'=>'Check out Exam for Viewing', - 'checkout?'=>'Check out Exam?'); + 'checkout'=>'Check out Bubblesheet Exam for Viewing', + 'checkout?'=>'Check out Bubblesheet Exam?'); my $uri = &Apache::lonenc::check_encrypt($env{'request.uri'}); return (<$lt{'resource'} @@ -976,7 +1426,7 @@ sub checkout_msg {

$lt{'warning'}

- +
ENDCHECKOUT } @@ -1010,7 +1460,7 @@ sub firstaccess_msg { $result .= (< - + ENDCHECKOUT return $result; @@ -1169,7 +1619,7 @@ sub start_problem { $target eq 'tex') { if ($env{'form.markaccess'}) { my @interval=&Apache::lonnet::EXT("resource.0.interval"); - &Apache::lonnet::set_first_access($interval[1]); + &Apache::lonnet::set_first_access($interval[1],$interval[0]); } ($status,$accessmsg,$slot_name,$slot) = @@ -1399,7 +1849,7 @@ sub end_problem { my $id = $Apache::inputtags::part; my $weight = &Apache::lonnet::EXT("resource.$id.weight"); my $packages=&Apache::lonnet::metadata($env{'request.uri'},'packages'); - my @packages = split /,/,$packages; + my @packages = split(/,/,$packages); my $allow_print_points = 0; foreach my $partial_key (@packages) { if ($partial_key=~m/^part_0$/) { @@ -2058,9 +2508,15 @@ sub end_while { } $return = &Apache::run::run($code,$safeeval); } - if ($error) { - &Apache::lonxml::error('
'.&mt('Code ran too long. It ran for more than').' '.$Apache::lonnet::perlvar{'lonScriptTimeout'}.' '.&mt('seconds occurred while running <while> on line').' '.$line.'
'); - } + if ($error) { + &Apache::lonxml::error( + '
'
+               .&mt('Code ran too long. It ran for more than [_1] seconds.',
+                        $Apache::lonnet::perlvar{'lonScriptTimeout'})
+               .&mt('This occurred while running <while> on line [_1].',
+                        $line)
+               .'
'); + } } elsif ($target eq "edit") { $result.= &Apache::edit::tag_end($target,$token,''); } @@ -2127,8 +2583,8 @@ sub start_randomlist { } } } - for(0 .. $show) { - $bodytext .= "$randomlist[ $idx_arr[$_] ]"; + for my $i (0 .. $show) { + $bodytext .= "$randomlist[ $idx_arr[$i] ]"; } &Apache::lonxml::newparser($parser,\$bodytext); } @@ -2292,7 +2748,7 @@ sub start_part { } my $weight = &Apache::lonnet::EXT("resource.$id.weight"); my $allkeys=&Apache::lonnet::metadata($env{'request.uri'},'packages'); - my @allkeys = split /,/,$allkeys; + my @allkeys = split(/,/,$allkeys); my $allow_print_points = 0; foreach my $partial_key (@allkeys) { if ($partial_key=~m/^part_(.*)$/) { @@ -2497,7 +2953,7 @@ sub start_problemtype { ['hide','Hide']] ,$token); $result .=&Apache::edit::checked_arg('When used as type(s):','for', - [ ['exam','Exam/Quiz Problem'], + [ ['exam','Bubblesheet Exam/Quiz Problem'], ['survey','Survey'], ['surveycred','Survey (with credit)'], ['anonsurvey','Anonymous Survey'], @@ -2540,17 +2996,18 @@ sub end_startouttext { if ($target eq 'edit') { my $areaid = 'homework_edit_'.$Apache::lonxml::curdepth; $text=&Apache::lonxml::get_all_text("endouttext",$parser,$style); - $result.=&Apache::edit::start_table($token)."
".&mt('Text Block')."
".&Apache::loncommon::insert_folding_button() + ." ".&mt('Text Block')."'.&mt('Delete?').' ' .&Apache::edit::deletelist($target,$token) .'' + .'' .&Apache::lonhtmlcommon::dragmath_button($areaid,1) - .'' .&Apache::edit::insertlist($target,$token) .'' . + .'' . &Apache::loncommon::helpLatexCheatsheet(). &Apache::edit::end_row(). &Apache::edit::start_spanning_row()."\n".