Merge branch 'seeddms-5.1.x' into seeddms-6.0.x

This commit is contained in:
Uwe Steinmann 2023-01-09 15:40:41 +01:00
commit 927e5acdc2
11 changed files with 513 additions and 35 deletions

View File

@ -145,6 +145,7 @@ class SeedDMS_Lucene_IndexedDocument extends Zend_Search_Lucene_Document {
if($document->isType('document')) {
$this->addField(Zend_Search_Lucene_Field::Keyword('document_id', 'D'.$document->getID()));
$this->addField(Zend_Search_Lucene_Field::Keyword('record_type', 'document'));
$version = $document->getLatestContent();
if($version) {
$this->addField(Zend_Search_Lucene_Field::Keyword('mimetype', $version->getMimeType()));
@ -232,7 +233,9 @@ class SeedDMS_Lucene_IndexedDocument extends Zend_Search_Lucene_Document {
}
} elseif($document->isType('folder')) {
$this->addField(Zend_Search_Lucene_Field::Keyword('document_id', 'F'.$document->getID()));
$this->addField(Zend_Search_Lucene_Field::Keyword('record_type', 'folder'));
$this->addField(Zend_Search_Lucene_Field::UnIndexed('created', $document->getDate()));
$this->addField(Zend_Search_Lucene_Field::UnIndexed('indexed', time()));
}
} /* }}} */

View File

@ -29,10 +29,23 @@ class SeedDMS_Lucene_Indexer {
*/
protected $indexname;
/**
* @var string $index lucene index
* @access protected
*/
protected $index;
public function __construct($index) {
$this->index = $index;
}
static function open($conf) { /* {{{ */
try {
$index = Zend_Search_Lucene::open($conf['indexdir']);
return($index);
if($index)
return new self($index);
else
return null;
} catch (Exception $e) {
return null;
}
@ -41,7 +54,10 @@ class SeedDMS_Lucene_Indexer {
static function create($conf) { /* {{{ */
try {
$index = Zend_Search_Lucene::create($conf['indexdir']);
return($index);
if($index)
return new self($index);
else
return null;
} catch (Exception $e) {
return null;
}
@ -62,6 +78,131 @@ class SeedDMS_Lucene_Indexer {
Zend_Search_Lucene_Analysis_Analyzer::setDefault($analyzer);
} /* }}} */
/**
* Add document to index
*
* @param object $doc indexed document of class
* SeedDMS_Lucene_IndexedDocument
* @return boolean false in case of an error, otherwise true
*/
function addDocument($doc) { /* {{{ */
if(!$this->index)
return false;
return $this->index->addDocument($doc);
} /* }}} */
/**
* Remove document from index
*
* @param object $id internal id of document
* @return boolean false in case of an error, otherwise true
*/
public function delete($id) { /* {{{ */
if(!$this->index)
return false;
return $this->index->delete($id);
} /* }}} */
/**
* Check if document was deleted
*
* @param object $id internal id of document
* @return boolean true if document was deleted
*/
public function isDeleted($id) { /* {{{ */
if(!$this->index)
return false;
return $this->index->isDeleted($id);
} /* }}} */
/**
* Search in index
*
* @param string $query
* @return array result
*/
public function find($query) { /* {{{ */
if(!$this->index)
return false;
return $this->index->find($query);
} /* }}} */
/**
* Get a single document from index
*
* @param string $id id of document
* @return boolean false in case of an error, otherwise true
*/
public function findById($id) { /* {{{ */
if(!$this->index)
return false;
return $this->index->findById($id);
} /* }}} */
/**
* Get a single document from index
*
* @param integer $id id of index record
* @return boolean false in case of an error, otherwise true
*/
public function getDocument($id, $content=true) { /* {{{ */
if(!$this->index)
return false;
return $this->index->getDocument($id);
} /* }}} */
/**
* Return list of terms in index
*
* @return array list of Zend_Lucene_Term
*/
public function terms($prefix='', $col='') { /* {{{ */
if(!$this->index)
return false;
return $this->index->terms();
} /* }}} */
/**
* Return number of documents in index
*
* @return interger number of documents
*/
public function count() { /* {{{ */
if(!$this->index)
return false;
return $this->index->count();
} /* }}} */
/**
* Commit changes
*
* This function does nothing!
*/
function commit() { /* {{{ */
if(!$this->index)
return false;
return $this->index->commit();
} /* }}} */
/**
* Optimize index
*
* This function does nothing!
*/
function optimize() { /* {{{ */
if(!$this->index)
return false;
return $this->index->optimize();
} /* }}} */
}
?>

View File

@ -89,6 +89,13 @@ class SeedDMS_Lucene_Search {
$querystr .= '")';
}
}
if(!empty($fields['record_type'])) {
if($querystr)
$querystr .= ' && ';
$querystr .= '(record_type:';
$querystr .= implode(' || record_type:', $fields['record_type']);
$querystr .= ')';
}
if(!empty($fields['category'])) {
if($querystr)
$querystr .= ' && ';
@ -137,7 +144,7 @@ class SeedDMS_Lucene_Search {
$recs = array();
$c = 0;
foreach($hits as $hit) {
if($c >= $limit['offset'] && ($c-$limit['offset'] < $limit))
if($c >= $limit['offset'] && ($c-$limit['offset'] < $limit['limit']))
$recs[] = array('id'=>$hit->id, 'document_id'=>$hit->document_id);
$c++;
}

View File

@ -0,0 +1,306 @@
<?php
/**
* Implementation of text preview documents
*
* @category DMS
* @package SeedDMS_Preview
* @license GPL 2
* @version @version@
* @author Uwe Steinmann <uwe@steinmann.cx>
* @copyright Copyright (C) 2010, Uwe Steinmann
* @version Release: @package_version@
*/
/**
* Class for managing creation of text preview for documents.
*
* @category DMS
* @package SeedDMS_Preview
* @version @version@
* @author Uwe Steinmann <uwe@steinmann.cx>
* @copyright Copyright (C) 2011, Uwe Steinmann
* @version Release: @package_version@
*/
class SeedDMS_Preview_TxtPreviewer extends SeedDMS_Preview_Base {
function __construct($previewDir, $timeout=5, $xsendfile=true) { /* {{{ */
parent::__construct($previewDir.DIRECTORY_SEPARATOR.'txt', $timeout, $xsendfile);
$this->converters = array(
);
} /* }}} */
/**
* Return the physical filename of the preview image on disc
* including the path
*
* @param object $object document content or document file
* @return string file name of preview image
*/
public function getFileName($object) { /* {{{ */
if(!$object)
return false;
$document = $object->getDocument();
$dms = $document->_dms;
$dir = $this->previewDir.DIRECTORY_SEPARATOR.$document->getDir();
switch(get_class($object)) {
case $dms->getClassname('documentcontent'):
$target = $dir.'t'.$object->getVersion();
break;
default:
return false;
}
return $target;
} /* }}} */
/**
* Check if converter for a given mimetype is set
*
* @param string $mimetype from mimetype
*
* @return boolean true if converter exists, otherwise false
*/
function hasConverter($from, $to='') { /* {{{ */
return parent::hasConverter($from, 'text/plain');
} /* }}} */
/**
* Create a text preview for a given file
*
* This method creates a preview in text format for a regular file
* in the file system and stores the result in the directory $dir relative
* to the configured preview directory. The filename of the resulting preview
* image is either $target.text (if set) or md5($infile).text.
* The $mimetype is used to select the propper conversion programm.
* An already existing text preview is replaced.
*
* @param string $infile name of input file including full path
* @param string $dir directory relative to $this->previewDir
* @param string $mimetype MimeType of input file
* @param string $target optional name of preview image (without extension)
* @return boolean true on success, false on failure
*/
public function createRawPreview($infile, $dir, $mimetype, $target='') { /* {{{ */
if(!self::hasConverter($mimetype))
return true;
if(!$this->previewDir)
return false;
if(!is_dir($this->previewDir.DIRECTORY_SEPARATOR.$dir)) {
if (!SeedDMS_Core_File::makeDir($this->previewDir.DIRECTORY_SEPARATOR.$dir)) {
return false;
}
}
if(!file_exists($infile))
return false;
if(!$target)
$target = $this->previewDir.$dir.md5($infile);
$this->lastpreviewfile = $target.'.txt';
if($target != '' && (!file_exists($target.'.txt') || filectime($target.'.txt') < filectime($infile))) {
if($this->conversionmgr) {
if(!$this->conversionmgr->convert($infile, $mimetype, 'text/plain', $target.'.txt')) {
$this->lastpreviewfile = '';
return false;
}
$new = true;
} else {
$cmd = '';
$mimeparts = explode('/', $mimetype, 2);
if(isset($this->converters[$mimetype])) {
$cmd = str_replace(array('%f', '%o', '%m'), array($infile, $target.'.txt', $mimetype), $this->converters[$mimetype]);
} elseif(isset($this->converters[$mimeparts[0].'/*'])) {
$cmd = str_replace(array('%f', '%o', '%m'), array($infile, $target.'.txt', $mimetype), $this->converters[$mimeparts[0].'/*']);
} elseif(isset($this->converters['*'])) {
$cmd = str_replace(array('%f', '%o', '%m'), array($infile, $target.'.txt', $mimetype), $this->converters['*']);
}
if($cmd) {
try {
self::execWithTimeout($cmd, $this->timeout);
$new = true;
} catch(Exception $e) {
$this->lastpreviewfile = '';
return false;
}
}
}
return true;
}
$new = false;
return true;
} /* }}} */
/**
* Create preview image
*
* This function creates a preview image for the given document
* content or document file. It internally uses
* {@link SeedDMS_Preview::createRawPreview()}. The filename of the
* preview image is created by {@link SeedDMS_Preview_Previewer::getFileName()}
*
* @param object $object instance of SeedDMS_Core_DocumentContent
* or SeedDMS_Core_DocumentFile
* @return boolean true on success, false on failure
*/
public function createPreview($object) { /* {{{ */
if(!$object)
return false;
$document = $object->getDocument();
$file = $document->_dms->contentDir.$object->getPath();
$target = $this->getFileName($object);
return $this->createRawPreview($file, $document->getDir(), $object->getMimeType(), $target);
} /* }}} */
/**
* Check if a preview image already exists.
*
* This function is a companion to {@link SeedDMS_Preview_Previewer::createRawPreview()}.
*
* @param string $infile name of input file including full path
* @param string $dir directory relative to $this->previewDir
* @return boolean true if preview exists, otherwise false
*/
public function hasRawPreview($infile, $dir, $target='') { /* {{{ */
if(!$this->previewDir)
return false;
if(!$target)
$target = $this->previewDir.$dir.md5($infile);
if($target !== false && file_exists($target.'.txt') && filectime($target.'.txt') >= filectime($infile)) {
return true;
}
return false;
} /* }}} */
/**
* Check if a preview txt already exists.
*
* This function is a companion to {@link SeedDMS_Preview_Previewer::createPreview()}.
*
* @param object $object instance of SeedDMS_Core_DocumentContent
* or SeedDMS_Core_DocumentFile
* @return boolean true if preview exists, otherwise false
*/
public function hasPreview($object) { /* {{{ */
if(!$object)
return false;
if(!$this->previewDir)
return false;
$target = $this->getFileName($object);
if($target !== false && file_exists($target.'.txt') && filectime($target.'.txt') >= $object->getDate()) {
return true;
}
return false;
} /* }}} */
/**
* Return a preview image.
*
* This function returns the content of a preview image if it exists..
*
* @param string $infile name of input file including full path
* @param string $dir directory relative to $this->previewDir
* @return boolean/string image content if preview exists, otherwise false
*/
public function getRawPreview($infile, $dir, $target='') { /* {{{ */
if(!$this->previewDir)
return false;
if(!$target)
$target = $this->previewDir.$dir.md5($infile);
if($target && file_exists($target.'.txt')) {
$this->sendFile($target.'.txt');
}
} /* }}} */
/**
* Return a preview image.
*
* This function returns the content of a preview image if it exists..
*
* @param object $object instance of SeedDMS_Core_DocumentContent
* or SeedDMS_Core_DocumentFile
* @return boolean/string image content if preview exists, otherwise false
*/
public function getPreview($object) { /* {{{ */
if(!$this->previewDir)
return false;
$target = $this->getFileName($object);
if($target && file_exists($target.'.txt')) {
$this->sendFile($target.'.txt');
}
} /* }}} */
/**
* Return file size preview image.
*
* @param object $object instance of SeedDMS_Core_DocumentContent
* or SeedDMS_Core_DocumentFile
* @return boolean/integer size of preview image or false if image
* does not exist
*/
public function getFilesize($object) { /* {{{ */
$target = $this->getFileName($object);
if($target && file_exists($target.'.txt')) {
return(filesize($target.'.txt'));
} else {
return false;
}
} /* }}} */
/**
* Delete preview image.
*
* @param object $object instance of SeedDMS_Core_DocumentContent
* or SeedDMS_Core_DocumentFile
* @return boolean true if deletion succeded or false if file does not exist
*/
public function deletePreview($object) { /* {{{ */
if(!$this->previewDir)
return false;
$target = $this->getFileName($object);
if($target && file_exists($target.'.txt')) {
return(unlink($target.'.txt'));
} else {
return false;
}
} /* }}} */
static function recurseRmdir($dir) {
$files = array_diff(scandir($dir), array('.','..'));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? SeedDMS_Preview_Previewer::recurseRmdir("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}
/**
* Delete all preview text belonging to a document
*
* This function removes the preview text of all versions and
* files of a document including the directory. It actually just
* removes the directory for the document in the cache.
*
* @param object $document instance of SeedDMS_Core_Document
* @return boolean true if deletion succeded or false if file does not exist
*/
public function deleteDocumentPreviews($document) { /* {{{ */
if(!$this->previewDir)
return false;
$dir = $this->previewDir.DIRECTORY_SEPARATOR.$document->getDir();
if(file_exists($dir) && is_dir($dir)) {
return SeedDMS_Preview_Previewer::recurseRmdir($dir);
} else {
return false;
}
} /* }}} */
}
?>

View File

@ -54,7 +54,7 @@ class SeedDMS_SQLiteFTS_Indexer {
$words = preg_split('/[^-\w\']+/u', $str, -1, PREG_SPLIT_NO_EMPTY);
// 2.) if we have at least 2 words, remove stopwords
if(count($words) > 1) {
if(!empty($words)) {
$stopwords = $this->_stop_words;
$words = array_filter($words, function ($w) use (&$stopwords) {
return ((mb_strlen($w, 'utf-8') > 2) && !isset($stopwords[mb_strtolower($w, "utf-8")]));
@ -90,7 +90,7 @@ class SeedDMS_SQLiteFTS_Indexer {
if(file_exists($conf['indexdir'].'/index.db')) {
return new SeedDMS_SQLiteFTS_Indexer($conf['indexdir']);
} else
return self::create($conf);
return static::create($conf);
} /* }}} */
/**
@ -183,8 +183,7 @@ class SeedDMS_SQLiteFTS_Indexer {
/**
* Remove document from index
*
* @param object $doc indexed document of class
* SeedDMS_SQLiteFTS_IndexedDocument
* @param object $id internal id of document
* @return boolean false in case of an error, otherwise true
*/
public function delete($id) { /* {{{ */
@ -215,7 +214,7 @@ class SeedDMS_SQLiteFTS_Indexer {
* @return boolean false in case of an error, otherwise array with elements
* 'count', 'hits', 'facets'. 'hits' is an array of SeedDMS_SQLiteFTS_QueryHit
*/
public function find($query, $filter, $limit=array(), $order=array()) { /* {{{ */
public function find($query, $filter='', $limit=array(), $order=array()) { /* {{{ */
if(!$this->_conn)
return false;
@ -392,16 +391,16 @@ class SeedDMS_SQLiteFTS_Indexer {
*
* @return array list of SeedDMS_SQLiteFTS_Term
*/
public function terms($query='', $col='') { /* {{{ */
public function terms($prefix='', $col='') { /* {{{ */
if(!$this->_conn)
return false;
if($this->_ftstype == 'fts5') {
$sql = "SELECT term, col, doc as occurrences FROM docs_terms";
if($query || $col) {
if($prefix || $col) {
$sql .= " WHERE";
if($query) {
$sql .= " term like '".$query."%'";
if($prefix) {
$sql .= " term like '".$prefix."%'";
if($col)
$sql .= " AND";
}
@ -411,8 +410,8 @@ class SeedDMS_SQLiteFTS_Indexer {
$sql .= " ORDER BY col, occurrences desc";
} else {
$sql = "SELECT term, col, occurrences FROM docs_terms WHERE col!='*'";
if($query)
$sql .= " AND term like '".$query."%'";
if($prefix)
$sql .= " AND term like '".$prefix."%'";
if($col)
$sql .= " AND col = '".$col."'";
$sql .= " ORDER BY col, occurrences desc";

View File

@ -33,8 +33,10 @@ if($settings->_enableFullSearch) {
$indexconf = null;
if(isset($GLOBALS['SEEDDMS_HOOKS']['initFulltext'])) {
foreach($GLOBALS['SEEDDMS_HOOKS']['initFulltext'] as $hookObj) {
if (method_exists($hookObj, 'initFulltextService')) {
$indexconf = $hookObj->initFulltextService(array('engine'=>$settings->_fullSearchEngine, 'dms'=>$dms, 'settings'=>$settings));
if (method_exists($hookObj, 'isFulltextService') && $hookObj->isFulltextService($settings->_fullSearchEngine)) {
if (method_exists($hookObj, 'initFulltextService')) {
$indexconf = $hookObj->initFulltextService(array('engine'=>$settings->_fullSearchEngine, 'dms'=>$dms, 'settings'=>$settings));
}
}
}
}

View File

@ -66,11 +66,15 @@ if (isset($_GET["removecategory"]) && is_numeric($_GET["removecategory"]) && $_G
$removecategory = (int) $_GET['removecategory'];
}
$terms = [];
$limit = (isset($_GET["limit"]) && is_numeric($_GET["limit"])) ? (int) $_GET['limit'] : 20;
$fullsearch = ((!isset($_GET["fullsearch"]) && $settings->_defaultSearchMethod == 'fulltext') || !empty($_GET["fullsearch"])) && $settings->_enableFullSearch;
if($fullsearch) {
// Search in Fulltext {{{
if (isset($_GET["query"]) && is_string($_GET["query"])) {
$query = $_GET["query"];
if(isset($_GET['action']) && ($_GET['action'] == 'typeahead'))
$query .= '*';
}
else {
$query = "";
@ -183,10 +187,11 @@ if($fullsearch) {
$searchTime = 0;
} else {
$startTime = getTime();
$limit = 20;
// $limit = 20;
$total = 0;
$index = $fulltextservice->Indexer();
if($index) {
// $terms = $index->terms($_GET['query']);
$lucenesearch = $fulltextservice->Search();
$searchresult = $lucenesearch->search($query, array('record_type'=>$record_type, 'owner'=>$ownernames, 'status'=>$status, 'category'=>$categorynames, 'user'=>$user->isAdmin() ? [] : [$user->getLogin()], 'mimetype'=>$mimetype, 'startFolder'=>$startFolder, 'rootFolder'=>$rootFolder), ($pageNumber == 'all' ? array() : array('limit'=>$limit, 'offset'=>$limit * ($pageNumber-1))));
if($searchresult === false) {
@ -505,7 +510,7 @@ if($fullsearch) {
//
// Default page to display is always one.
$pageNumber=1;
$limit = 15;
// $limit = 15;
if (isset($_GET["pg"])) {
if (is_numeric($_GET["pg"]) && $_GET["pg"]>0) {
$pageNumber = (int) $_GET["pg"];
@ -609,6 +614,7 @@ if($settings->_showSingleSearchHit && count($entries) == 1) {
$view->setParam('changecategory', $changecategory);
$view->setParam('removecategory', $removecategory);
$view->setParam('searchhits', $entries);
$view->setParam('terms', $terms);
$view->setParam('totalpages', $totalPages);
$view->setParam('pagenumber', $pageNumber);
$view->setParam('limit', $limit);

View File

@ -192,6 +192,7 @@ $stats['folder']['update'] = 0;
$stats['document']['add'] = 0;
$stats['document']['unchanged'] = 0;
$stats['document']['update'] = 0;
$stats['time']['total'] = time();
$numdocs = $fulltextservice->Indexer()->count();
$folder = $dms->getFolder($settings->_rootFolderID);
/* if numdocs is 0, then there is no need to check if a document/folder is already
@ -201,8 +202,10 @@ tree($dms, $fulltextservice, $folder,'', $numdocs);
$index->commit();
$index->optimize();
$stats['time']['total'] = time()-$stats['time']['total'];
echo PHP_EOL;
echo $themes->black("Total Time: ".$stats['time']['total'].' sec.').PHP_EOL;
echo $themes->black("Documents").PHP_EOL;
echo $themes->black(" added: ".$stats['document']['add']).PHP_EOL;
echo $themes->black(" updated: ".$stats['document']['update']).PHP_EOL;

View File

@ -362,19 +362,22 @@ function typeahead() { /* {{{ */
$user = $this->params['user'];
$query = $this->params['query'];
$entries = $this->params['searchhits'];
$terms = $this->params['terms'];
$recs = array();
$recs[] = array('type'=>'S', 'name'=>$query, 'occurences'=>'');
if($terms) {
foreach($terms as $term)
$recs[] = array('type'=>'S', 'name'=>$term->text, 'occurences'=>$term->_occurrence);
}
if($entries) {
foreach ($entries as $entry) {
if($entry->isType('document')) {
// $recs[] = 'D'.$entry->getName();
$recs[] = array('type'=>'D', 'id'=>$entry->getId(), 'name'=>$entry->getName(), 'path'=>$entry->getParent()->getFolderPathPlain(true, '/'));
$recs[] = array('type'=>'D', 'id'=>$entry->getId(), 'name'=>htmlspecialchars($entry->getName()), 'path'=>htmlspecialchars($entry->getParent()->getFolderPathPlain(true, '/')));
} elseif($entry->isType('folder')) {
// $recs[] = 'F'.$entry->getName();
$recs[] = array('type'=>'F', 'id'=>$entry->getId(), 'name'=>$entry->getName(), 'path'=>$entry->getParent()->getFolderPathPlain(true, '/'));
$recs[] = array('type'=>'F', 'id'=>$entry->getId(), 'name'=>htmlspecialchars($entry->getName()), 'path'=>htmlspecialchars($entry->getParent()->getFolderPathPlain(true, '/')));
}
}
}
array_unshift($recs, array('type'=>'S', 'name'=>$query));
header('Content-Type: application/json');
echo json_encode($recs);
} /* }}} */
@ -849,6 +852,22 @@ function typeahead() { /* {{{ */
)
);
}
if(!isset($facets['record_type'])) {
$options = array();
$options[] = array('document', getMLText('document'), in_array('document', $record_type));
$options[] = array('folder', getMLText('folder'), in_array('folder', $record_type));
$this->formField(
getMLText("record_type"),
array(
'element'=>'select',
'class'=>'chzn-select',
'name'=>'record_type[]',
'multiple'=>true,
'attributes'=>array(array('data-placeholder', getMLText('select_record_type'))),
'options'=>$options
)
);
}
if($facets) {
foreach($facets as $facetname=>$values) {

View File

@ -96,13 +96,9 @@ function initMost() {
d.setFullYear(pastYear);
// console.log(d.toISOString().split('T')[0]);
// $.get('../restapi/index.php/search', { query: query, limit: 8, mode: 'typeahead' }, function(data) {
var data = {
query: query,
limit: 18,
// fullsearch: 1,
// creationdate: 1,
// createstart: d.toISOString().split('T')[0],
limit: 15,
action: 'typeahead'
};
/* Return a list of json objects, each containing
@ -152,7 +148,7 @@ function initMost() {
else if(item.type.charAt(0) == 'F')
return '<i class="fa fa-folder-o"></i> ' + item.name.replace(/</g, '&lt;') + '<br /><span class="path">' + item.path + '</span>';
else
return '<i class="fa fa-search"></i> ' + item.name.replace(/</g, '&lt;');
return '<i class="fa fa-search"></i> ' + item.name.replace(/</g, '&lt;') + (item.occurences > 0 ? ' (' + item.occurences + ')' : '');
},
/* This only works with a modified version of bootstrap typeahead located
* in boostrap-typeahead.js Search for 'render'

View File

@ -101,13 +101,9 @@ function initMost() {
d.setFullYear(pastYear);
// console.log(d.toISOString().split('T')[0]);
// $.get('../restapi/index.php/search', { query: query, limit: 8, mode: 'typeahead' }, function(data) {
var data = {
query: query,
limit: 18,
// fullsearch: 1,
// creationdate: 1,
// createstart: d.toISOString().split('T')[0],
limit: 15,
action: 'typeahead'
};
/* Return a list of json objects, each containing
@ -157,7 +153,7 @@ function initMost() {
else if(item.type.charAt(0) == 'F')
return '<i class="fa fa-folder-o"></i> ' + item.name.replace(/</g, '&lt;') + '<br /><span class="path">' + item.path + '</span>';
else
return '<i class="fa fa-search"></i> ' + item.name.replace(/</g, '&lt;');
return '<i class="fa fa-search"></i> ' + item.name.replace(/</g, '&lt;') + (item.occurences > 0 ? ' (' + item.occurences + ')' : '');
},
/* This only works with a modified version of bootstrap typeahead located
* in boostrap-typeahead.js Search for 'render'