[LON-CAPA-cvs] cvs: loncom /html/adm/quicksearch quicksearch.js /localize/localize de.pm /publisher lonpubdir.pm doc/loncapafiles loncapafiles.lpml

goltermann goltermann at source.lon-capa.org
Mon Oct 13 10:50:42 EDT 2014


goltermann		Mon Oct 13 14:50:42 2014 EDT

  Added files:                 
    /loncom/html/adm/quicksearch	quicksearch.js 

  Modified files:              
    /doc/loncapafiles	loncapafiles.lpml 
    /loncom/localize/localize	de.pm 
    /loncom/publisher	lonpubdir.pm 
  Log:
  added search functionality to authoring space
  
  - users can now search their author space for files. the search functions searches in the file name and title. use of regular expressions and case sensitivity can be enabled. the content table of the current directory will shrink so that only matching entries are shown. a second table appears underneath to show results in other directories.
  - german translation has been added.
  
  
-------------- next part --------------
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.899 doc/loncapafiles/loncapafiles.lpml:1.900
--- doc/loncapafiles/loncapafiles.lpml:1.899	Fri Oct  3 12:51:15 2014
+++ doc/loncapafiles/loncapafiles.lpml	Mon Oct 13 14:50:29 2014
@@ -2,7 +2,7 @@
  "http://lpml.sourceforge.net/DTD/lpml.dtd">
 <!-- loncapafiles.lpml -->
 
-<!-- $Id: loncapafiles.lpml,v 1.899 2014/10/03 12:51:15 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.900 2014/10/13 14:50:29 goltermann Exp $ -->
 
 <!--
 
@@ -613,6 +613,12 @@
 </directory>
 <directory dist='default'>
   <protectionlevel>modest_delete</protectionlevel>
+  <targetdir dist='default'>home/httpd/html/adm/quicksearch</targetdir>
+  <categoryname>server readonly</categoryname>
+  <description>Author space quick search</description>
+</directory>
+<directory dist='default'>
+  <protectionlevel>modest_delete</protectionlevel>
   <targetdir dist='default'>home/httpd/html/adm/LC_math_editor </targetdir>
   <categoryname>server readonly</categoryname>
   <description>LON-CAPA math editor</description>
@@ -3850,6 +3856,19 @@
 unicover.txt;
   </filenames>
 </fileglob>
+
+<fileglob>
+    <glob>*.js</glob>
+    <sourcedir>loncom/html/adm/quicksearch/</sourcedir>
+    <targetdir dist='default'>home/httpd/html/adm/quicksearch/</targetdir>
+    <description>
+Search to enable the quicksearch function in the author space
+    </description>
+    <filenames>
+quicksearch.js
+    </filenames>
+</fileglob>
+
 <file>
 <source>loncom/html/adm/fonts/AUTHORS</source>
 <target dist='default'>home/httpd/html/adm/fonts/AUTHORS</target>
Index: loncom/localize/localize/de.pm
diff -u loncom/localize/localize/de.pm:1.597 loncom/localize/localize/de.pm:1.598
--- loncom/localize/localize/de.pm:1.597	Fri Sep 12 18:10:02 2014
+++ loncom/localize/localize/de.pm	Mon Oct 13 14:50:38 2014
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # German Localization Lexicon
 #
-# $Id: de.pm,v 1.597 2014/09/12 18:10:02 raeburn Exp $
+# $Id: de.pm,v 1.598 2014/10/13 14:50:38 goltermann Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -29660,6 +29660,12 @@
    'No active users logged in.'
 => 'Es sind keine aktiven Benutzer eingeloggt.',
 
+   'Results in other directories:'
+=> 'Ergebnisse in anderen Verzeichnissen:',
+
+   'Show results for keyword:'
+=> 'Zeige Ergebnisse für Suchwort:',
+
    'Content blocking'
 => 'Inhaltssperre',
 
@@ -29717,6 +29723,9 @@
    'other users'
 => 'andere Benutzer',
 
+   'Quick Search'
+=> 'Schnellsuche'
+
 #SYNCMARKER
 );
 1;
Index: loncom/publisher/lonpubdir.pm
diff -u loncom/publisher/lonpubdir.pm:1.160 loncom/publisher/lonpubdir.pm:1.161
--- loncom/publisher/lonpubdir.pm:1.160	Tue Aug  5 19:32:23 2014
+++ loncom/publisher/lonpubdir.pm	Mon Oct 13 14:50:42 2014
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Authoring Space Directory Lister
 #
-# $Id: lonpubdir.pm,v 1.160 2014/08/05 19:32:23 musolffc Exp $
+# $Id: lonpubdir.pm,v 1.161 2014/10/13 14:50:42 goltermann Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -192,7 +192,15 @@
             .$columns{$key}{text}.$arrows[$idx].'</a></th>' :
             '<th>'.$columns{$key}{text}.'</th>');
     }
-    $r->print(&Apache::loncommon::start_data_table()
+
+my $result = "<script>
+    sessionStorage.setItem('CSTRcache','".&prepareJsonData($uname,$udom,$thisdisfn)."');
+    localStorage.setItem('CSTRtrans', '".&prepareJsonTranslations()."');
+</script>";
+    $r->print($result);
+
+    $r->print('<div id="currentFolder">'.&Apache::loncommon::start_data_table()
+        .'<th colspan=8 id="searchtitle" style="display:none"></th>'
         .&Apache::loncommon::start_data_table_header_row() . $output
         .&Apache::loncommon::end_data_table_header_row()
     );
@@ -299,10 +307,22 @@
         }
     }
 
-    $r->print( &Apache::loncommon::end_data_table()
-        .&Apache::loncommon::end_page() );
-
-    return OK;
+  $r->print(&Apache::loncommon::end_data_table()
+           .'</div><div id="otherplaces" style="display:none">'
+           .&Apache::loncommon::start_data_table()
+           .'<th colspan=7>'.&mt('Results in other directories:').'</th>'
+           .'<tr class="LC_header_row" id="otherplacestable">'
+           .'<th>'.&mt('Type').'</th>'
+           .'<th>'.&mt('Directory').'</th>'
+           .'<th>'.&mt('Name').'</th>'
+           .'<th>'.&mt('Title').'</th>'
+           .'<th colspan="2">'.&mt('Status').'</th>'
+           .'<th>'.&mt('Last Modified').'</th>'
+           .'</tr>'
+           .'</div>'
+           .&Apache::loncommon::end_page()
+  );
+  return OK;  
 }
 
 
@@ -493,12 +513,15 @@
                                        go   => 'Go',
                                        prnt => 'Print contents of directory',
                                        crea => 'Create a new directory or LON-CAPA document',
+                                       qs   => 'Quick Search',
+                                       cs   => 'Case Sensitive',
+                                       re   => 'Regular Expression',
 				       acti => 'Actions for current directory',
 				       updc => 'Upload a new document',
 				       pick => 'Please select an action to perform using the new filename',
                                       );
     my $mytype = $lt{'type'}; # avoid conflict with " and ' in javascript
-    $r->print(<<END);
+    $r->printf(<<END,&Apache::loncommon::help_open_topic('Quicksearch'));
 <div class="LC_columnSection">
   <div>
     <form name="curractions" method="post" action="">
@@ -566,6 +589,20 @@
 		 </span>
       </fieldset>
     </form>
+    </div>
+    <div>
+        <form>
+          <fieldset style="display:inline">
+                <legend>$lt{'qs'}</legend>
+                    <script src="/adm/quicksearch/quicksearch.js"></script>
+                    <input type="text" id="quickfilter" placeholder="Enter search term" onkeyup="applyFilter()"/>
+                    <input type="button" value="Clear" onclick="document.getElementById(\'quickfilter\').value=\'\'; applyFilter()" />
+                    %s
+                    <br />
+                    <label><input type="checkbox" id="casesens" onchange="applyFilter()"/>$lt{'cs'}  </label>
+                    <label><input type="checkbox" id="regex" onchange="applyFilter()"/>$lt{'re'}  </label>
+            </fieldset>
+        </form>
   </div>
 </div>
 END
@@ -909,6 +946,134 @@
     return $versions;
 }
 
+sub prepareJsonTranslations {
+    my $json = 
+        '{"translations":{'.
+            '"edit":"'.&mt('Edit').'",'.
+            '"editxml":"'.&mt('EditXML').'",'.
+            '"editmeta":"'.&mt('Edit Metadata').'",'.
+            '"obsolete":"'.&mt('Obsolete').'",'.
+            '"modified":"'.&mt('Modified').'",'.
+            '"published":"'.&mt('Published').'",'.
+            '"unpublished":"'.&mt('Unpublished').'",'.
+            '"diff":"'.&mt('Diff').'",'.
+            '"retrieve":"'.&mt('Retrieve').'",'.
+            '"directory":"'.&mt('Directory').'",'.
+            '"results":"'.&mt('Show results for keyword:').'"'.
+        '}}';
+}
+
+# gathers all files in the working directory except the ones that are already on screen
+sub prepareJsonData {
+    my ($uname, $udom, $pathToSkip) = @_;
+    my $path = "/home/httpd/html/priv/$udom/$uname/";
+
+    # maximum number of entries, to limit workload and required storage space
+    my $entries = 100;
+    my $firstfile = 1;
+    my $firstdir = 1;
+
+    my $json = '{"resources":[';
+    $json .= &prepareJsonData_rec($path, \$entries, \$firstfile, \$firstdir, $pathToSkip);
+    $json .= ']}';
+
+    # if the json string is invalid the whole search breaks.
+    # so we want to make sure that the string is valid in any case.
+    $json =~ s/,\s*,/,/g;
+    $json =~ s/\}\s*\{/\},\{/g;
+    $json =~ s/\}\s*,\s*\]/\}\]/g;
+    return $json;
+}
+
+# recursive part of json file gathering
+sub prepareJsonData_rec {
+    my ($path, $entries, $firstfile, $firstdir, $pathToSkip) = @_;
+    my $json;
+    my $skipThisFolder = $path =~ m/$pathToSkip\/$/?1:0;
+
+    my @dirs;
+    my @resources;
+    my @ignored = qw(bak log meta save . ..);
+
+# Phase 1: Gathering
+    opendir(DIR,$path);
+    my @files=sort {uc($a) cmp uc($b)} (readdir(DIR));
+    foreach my $filename (@files) {
+        next if ($filename eq '.DS_Store');
+
+        # gather all resources
+        if ($filename !~ /\./) {
+            # its a folder
+            push(@dirs, $filename);
+        } else {
+            # only push files we dont want to ignore
+            next if ($skipThisFolder);
+
+            $filename =~ /\.(\w+?)$/;
+            unless (grep /$1/, @ignored) {
+                push(@resources, $filename);
+            }
+        }
+    }
+    closedir(DIR);
+    # nothing to do here if both lists are empty
+    return unless ( @dirs || @resources );
+    
+# Phase 2: Working
+    $$firstfile = 1;
+
+    foreach (@dirs) {
+        $json .= '{"name":"'.$_.'",'.
+                  '"path":"'.$path.$_.'",'.
+                  '"title":"",'.
+                  '"status":"",'.
+                  '"cmtime":""},';
+    }
+
+    foreach (@resources) {
+        last if ($$entries < 1);
+        my $title = &getTitleString($path.$_);
+
+        my $privpath = $path.$_;
+        my $respath = $privpath;
+        $respath =~ s/httpd\/html\/priv\//httpd\/html\/res\//;
+
+        my $cmtime = (stat($privpath))[9];
+        my $rmtime = (stat($respath))[9];
+
+        unless ($$firstfile) { $json .= ','; } else { $$firstfile = 0; }
+
+        my $status = 'unpublished';
+
+        # if a resource is published, the published version (/html/res/filepath) gets its own modification time
+        # this is newer or equal then the version in your authoring space (/html/priv/filepath)
+        if ($rmtime >= $cmtime) {
+            # obsolete
+            if (&Apache::lonnet::metadata($respath, 'obsolete')) {
+                $status = 'obsolete';
+            }else{
+                $status = 'published';
+            }
+        } else {
+            $status = 'modified';
+        }
+
+        $json .= '{"name":"'.$_.'",'.
+                  '"path":"'.$path.'",'.
+                  '"title":"'.$title.'",'.
+                  '"status":"'.$status.'",'.
+                  '"cmtime":"'.&Apache::lonlocal::locallocaltime($cmtime).'"}';
+        $$entries--;
+    }
+
+    foreach(@dirs) {
+        next if ($$entries < 1);
+        $json .= ',';
+        $json .= &prepareJsonData_rec
+                    ($path.$_.'/', $entries, $firstfile, $firstdir, $pathToSkip);
+    }
+    return $json;
+}
 1;
 __END__
 

Index: loncom/html/adm/quicksearch/quicksearch.js
+++ loncom/html/adm/quicksearch/quicksearch.js
function applyFilter(thisdir) {

    var pattern = prepareSearchPattern();
    var translations = jQuery.parseJSON(localStorage.getItem('CSTRtrans')).translations;

    updateDisplay(pattern, translations);
    filterTableElements(pattern);
    filterOtherElements(pattern, translations, thisdir);
}

// called everytime the user makes an input into the search field
function updateDisplay(pattern, translations) {
    var header1 = document.getElementById('searchtitle');

    if (pattern.source == '(?:)'){
        header1.style.display = 'none';
    } else {
        // remove escape symbol for better readability
        header1.innerHTML = translations.results+' '+pattern.source.replace('\\','');
        header1.style.display = '';
    }
}

// filters the contents table of the authoring space to only show matching resources in the current dir
function filterTableElements(pattern) {
    var list = document.getElementsByClassName('LC_data_table')[0].childNodes[1].children;

    // i=2 to skip searchtitle and header
    for (var i = 2; i < list.length; i++){
        // get filename: table column,     <span>,       <a>,       name
        var resname = list[i].children[2].children[0].children[0].innerHTML;
        var restitle;

        // check if resource has title and get it
        var titleColumn = list[i].children[3].children;

        if (titleColumn.length > 2){
            restitle = titleColumn[0].innerHTML;
        } else {
            restitle = '';
        }

        // match in filename OR resource title OR if search string is empty
        if ( pattern.test(resname) || pattern.test(restitle) || pattern.source == '' ){
            list[i].style.display = '';     // show element
        } else {
            list[i].style.display = 'none'; // hide element
        }
    }
}

// this is the search function for resources in other locations.
// pattern - the search pattern given by the user
// translations - loncapa generated translation object for the user interface
// thisdir - dir the user is currently in, used to generate relativ links for found files
function filterOtherElements (pattern, translations, thisdir) {

    var otherfiles = jQuery.parseJSON(sessionStorage.getItem('CSTRcache')).resources;
    if (pattern.source == '(?:)'){
        document.getElementById('otherplaces').style.display = 'none';
        return;
    }

    var hits = new Array();
    for (var i = 0; i < otherfiles.length; i++){
        if (otherfiles[i].title == '[untitled]'){
            otherfiles[i].title = '';
        }

        if ( pattern.test(otherfiles[i].name) || pattern.test(otherfiles[i].title) ){
            hits.push(otherfiles[i]);
        }
    }
    if (hits.length < 1){
        document.getElementById('otherplaces').style.display = 'none';
    } else {
        document.getElementById('otherplaces').style.display = '';
        var element = document.getElementsByName("otherplacescontent");

        // remove all old children
        for (index = element.length - 1; index >= 0; index--){
            element[index].parentNode.removeChild(element[index]);
        }

        // add new children
        for (var i = 0; i < hits.length; i ++){
            document.getElementById('otherplacestable').parentNode.appendChild(renderRow(hits[i], i, translations, thisdir));
        }
    }
}

// adds an row to the other places result table, used by filterOtherElements()
function renderRow(element, i, translations, thisdir) {
    var isdir = false;
    var row = '';
// icon
    row += '<td>';
    var extension = /\.(.+?)$/;

    if (element.name.match(extension)){
        var allowed = ['avi','cab','doc','dvi','eps','exam','htm','html','jar','jpeg','jpg','library','mov','mpeg','mpg','page','pdf','png','ppt','problem','ps','qt','quiz','sequence','spreadsheet','survey','task','tex','text','tth','txt','wav','wmv','xls','xml','zip'];
        var match = extension.exec(element.name)[1];
        if (allowed.indexOf(match) > -1) {
            // is supported file type
            row += '<img src="/adm/lonIcons/'+match+'.gif" alt="" />';
        } else {
            // is some other file we dont have an icon for
            row += '<img src="/adm/lonIcons/srvnull.gif" alt="" />';
        }
    } else {
        // is a directory
        isdir = true;
        row += '<img src="/adm/lonIcons/navmap.folder.closed.gif" alt="" />';
    }

    element.path = element.path.replace(/^\/home\/httpd\/html\//,'\/');

// directory
    var location = element.path.replace(/\/priv\//,'');
    if (location == ''){ location = '/'; }
    row += '</td><td>';
    if (isdir) {
        row += '<a href="/priv/'+location+'" target="_parent"></a>';
    } else {
        row += '<a href="/priv/'+location+'" target="_parent">'+location.replace(/(.+?)\/(.+?)\//,'/')+'</a>';
    }
    row += '</td><td>';

    if (isdir) {

    // file link
        row += '<span class="LC_filename"><a href="'+element.path+'/" target="_parent">'+element.name+'</a></span> <br />';
        row += '</td><td></td>';
    // status
        row += '<td class="LC_browser_file_'+element.status+'">  </td><td>'+translations.directory+'</td>';
        row += '<td>'+element.cmtime+'</td></tr>';
    } else {
    
    // file link
        row += '<span class="LC_filename"><a href="'+element.path+element.name+'" target="_parent">'+element.name+'</a></span> <br />';
    // editor
        row += '(<a href="'+element.path+element.name+'?editmode=Edit&problemmode=edit">'+translations.edit+'</a>)';
    // xml editor
        row += '(<a href="'+element.path+element.name+'?editmode=Edit&problemmode=editxml">'+translations.editxml+'</a>)';
        row += '</td><td>';
    // edit metadata
        row += '<a href="'+element.path+element.name+'.meta" target="cat">'+element.title+'</a><br />'
        row += '<a href="'+element.path+element.name+'.meta">'+translations.editmeta+'</a>';
        row += '</td>';
    // status
        row += '<td class="LC_browser_file_'+element.status+'">  </td><td>'+translations[element.status]+'</td>';
        row += '<td>'+element.cmtime+'</td></tr>';
    }

    var result = document.createElement('tr');
    result.innerHTML += row;
    result.setAttribute('name', 'otherplacescontent');
// set row style
    if (i % 2 == 1){
        result.setAttribute('class', 'LC_odd_row');
    } else {
        result.setAttribute('class', 'LC_even_row');
    }
    return result;
}

// escapes the search pattern for on screen display
function prepareSearchPattern() {
    var searchstring = document.getElementById('quickfilter').value;
    var regopt = document.getElementById('casesens').checked?'':'i';

    if (document.getElementById('regex').checked == false) {
        searchstring = searchstring.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    } else {
        // a single \ at the end would escape the closing / of the regex
        searchstring = searchstring.replace(/\\$/, '\\\\');
    }
    return new RegExp(searchstring, regopt);
}


More information about the LON-CAPA-cvs mailing list