Merge pull request #12653 from owncloud/full_content_search_results
Full content width search results
This commit is contained in:
commit
fe4592937a
|
@ -540,7 +540,7 @@ a.action>img {
|
|||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
|
||||
filter: alpha(opacity=30);
|
||||
opacity: .3;
|
||||
height: 70px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.summary:hover,
|
||||
|
@ -551,8 +551,6 @@ table tr.summary td {
|
|||
}
|
||||
|
||||
.summary td {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 150px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.summary .info {
|
||||
|
@ -601,3 +599,26 @@ table.dragshadow td.size {
|
|||
.mask.transparent{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nofilterresults {
|
||||
font-size: 16px;
|
||||
color: #888;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 30%;
|
||||
width: 100%;
|
||||
}
|
||||
.nofilterresults h2 {
|
||||
font-size: 22px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.nofilterresults [class^="icon-"],
|
||||
.nofilterresults [class*=" icon-"] {
|
||||
background-size: 64px;
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
margin: 0 auto 15px;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
}
|
|
@ -38,6 +38,7 @@ OCP\Util::addscript('files', 'jquery-visibility');
|
|||
OCP\Util::addscript('files', 'filesummary');
|
||||
OCP\Util::addscript('files', 'breadcrumb');
|
||||
OCP\Util::addscript('files', 'filelist');
|
||||
OCP\Util::addscript('files', 'search');
|
||||
|
||||
\OCP\Util::addScript('files', 'favoritesfilelist');
|
||||
\OCP\Util::addScript('files', 'tagsplugin');
|
||||
|
|
|
@ -111,6 +111,12 @@
|
|||
*/
|
||||
_selectionSummary: null,
|
||||
|
||||
/**
|
||||
* If not empty, only files containing this string will be shown
|
||||
* @type String
|
||||
*/
|
||||
_filter: '',
|
||||
|
||||
/**
|
||||
* Sort attribute
|
||||
* @type String
|
||||
|
@ -208,6 +214,8 @@
|
|||
|
||||
this.$el.on('show', this._onResize);
|
||||
|
||||
this.updateSearch();
|
||||
|
||||
this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
|
||||
this.$fileList.on('change', 'td.filename>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
|
||||
this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
|
||||
|
@ -268,6 +276,8 @@
|
|||
containerWidth -= $('#app-navigation-toggle').width();
|
||||
|
||||
this.breadcrumb.setMaxWidth(containerWidth - actionsWidth - 10);
|
||||
|
||||
this.updateSearch();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -458,6 +468,7 @@
|
|||
e.preventDefault();
|
||||
this.changeDirectory($targetDir);
|
||||
}
|
||||
this.updateSearch();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -551,6 +562,7 @@
|
|||
_nextPage: function(animate) {
|
||||
var index = this.$fileList.children().length,
|
||||
count = this.pageSize(),
|
||||
hidden,
|
||||
tr,
|
||||
fileData,
|
||||
newTrs = [],
|
||||
|
@ -562,7 +574,12 @@
|
|||
|
||||
while (count > 0 && index < this.files.length) {
|
||||
fileData = this.files[index];
|
||||
tr = this._renderRow(fileData, {updateSummary: false, silent: true});
|
||||
if (this._filter) {
|
||||
hidden = fileData.name.toLowerCase().indexOf(this._filter.toLowerCase()) === -1;
|
||||
} else {
|
||||
hidden = false;
|
||||
}
|
||||
tr = this._renderRow(fileData, {updateSummary: false, silent: true, hidden: hidden});
|
||||
this.$fileList.append(tr);
|
||||
if (isAllSelected || this._selectedFiles[fileData.id]) {
|
||||
tr.addClass('selected');
|
||||
|
@ -1638,24 +1655,68 @@
|
|||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @deprecated use setFilter(filter)
|
||||
*/
|
||||
filter:function(query) {
|
||||
this.setFilter('');
|
||||
},
|
||||
/**
|
||||
* @deprecated use setFilter('')
|
||||
*/
|
||||
unfilter:function() {
|
||||
this.setFilter('');
|
||||
},
|
||||
/**
|
||||
* hide files matching the given filter
|
||||
* @param filter
|
||||
*/
|
||||
setFilter:function(filter) {
|
||||
this._filter = filter;
|
||||
this.fileSummary.setFilter(filter, this.files);
|
||||
this.hideIrrelevantUIWhenNoFilesMatch();
|
||||
var that = this;
|
||||
this.$fileList.find('tr').each(function(i,e) {
|
||||
if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
|
||||
$(e).addClass("searchresult");
|
||||
var $e = $(e);
|
||||
if ($e.data('file').toString().toLowerCase().indexOf(filter.toLowerCase()) === -1) {
|
||||
$e.addClass('hidden');
|
||||
that.$container.trigger('scroll');
|
||||
} else {
|
||||
$(e).removeClass("searchresult");
|
||||
$e.removeClass('hidden');
|
||||
}
|
||||
});
|
||||
//do not use scrollto to prevent removing searchresult css class
|
||||
var first = this.$fileList.find('tr.searchresult').first();
|
||||
if (first.exists()) {
|
||||
$(window).scrollTop(first.position().top);
|
||||
},
|
||||
hideIrrelevantUIWhenNoFilesMatch:function() {
|
||||
if (this._filter && this.fileSummary.summary.totalDirs + this.fileSummary.summary.totalFiles === 0) {
|
||||
this.$el.find('#filestable thead th').addClass('hidden');
|
||||
this.$el.find('#emptycontent').addClass('hidden');
|
||||
if ( $('#searchresults').length === 0 || $('#searchresults').hasClass('hidden')) {
|
||||
this.$el.find('.nofilterresults').removeClass('hidden').
|
||||
find('p').text(t('files', "No entries in this folder match '{filter}'", {filter:this._filter}));
|
||||
}
|
||||
} else {
|
||||
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
|
||||
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
|
||||
this.$el.find('.nofilterresults').addClass('hidden');
|
||||
}
|
||||
},
|
||||
unfilter:function() {
|
||||
this.$fileList.find('tr.searchresult').each(function(i,e) {
|
||||
$(e).removeClass("searchresult");
|
||||
});
|
||||
/**
|
||||
* get the current filter
|
||||
* @param filter
|
||||
*/
|
||||
getFilter:function(filter) {
|
||||
return this._filter;
|
||||
},
|
||||
/**
|
||||
* update the search object to use this filelist when filtering
|
||||
*/
|
||||
updateSearch:function() {
|
||||
if (OCA.Search.files) {
|
||||
OCA.Search.files.setFileList(this);
|
||||
}
|
||||
if (OC.Search) {
|
||||
OC.Search.clear();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update UI based on the current selection
|
||||
|
|
|
@ -39,7 +39,8 @@
|
|||
summary: {
|
||||
totalFiles: 0,
|
||||
totalDirs: 0,
|
||||
totalSize: 0
|
||||
totalSize: 0,
|
||||
filter:''
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -48,6 +49,9 @@
|
|||
* @param update whether to update the display
|
||||
*/
|
||||
add: function(file, update) {
|
||||
if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
|
||||
return;
|
||||
}
|
||||
if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
|
||||
this.summary.totalDirs++;
|
||||
}
|
||||
|
@ -65,6 +69,9 @@
|
|||
* @param update whether to update the display
|
||||
*/
|
||||
remove: function(file, update) {
|
||||
if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
|
||||
return;
|
||||
}
|
||||
if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
|
||||
this.summary.totalDirs--;
|
||||
}
|
||||
|
@ -76,6 +83,10 @@
|
|||
this.update();
|
||||
}
|
||||
},
|
||||
setFilter: function(filter, files){
|
||||
this.summary.filter = filter.toLowerCase();
|
||||
this.calculate(files);
|
||||
},
|
||||
/**
|
||||
* Returns the total of files and directories
|
||||
*/
|
||||
|
@ -91,11 +102,15 @@
|
|||
var summary = {
|
||||
totalDirs: 0,
|
||||
totalFiles: 0,
|
||||
totalSize: 0
|
||||
totalSize: 0,
|
||||
filter: this.summary.filter
|
||||
};
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
file = files[i];
|
||||
if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
|
||||
continue;
|
||||
}
|
||||
if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
|
||||
summary.totalDirs++;
|
||||
}
|
||||
|
@ -118,6 +133,9 @@
|
|||
*/
|
||||
setSummary: function(summary) {
|
||||
this.summary = summary;
|
||||
if (typeof this.summary.filter === 'undefined') {
|
||||
this.summary.filter = '';
|
||||
}
|
||||
this.update();
|
||||
},
|
||||
|
||||
|
@ -137,6 +155,7 @@
|
|||
var $dirInfo = this.$el.find('.dirinfo');
|
||||
var $fileInfo = this.$el.find('.fileinfo');
|
||||
var $connector = this.$el.find('.connector');
|
||||
var $filterInfo = this.$el.find('.filter');
|
||||
|
||||
// Substitute old content with new translations
|
||||
$dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
|
||||
|
@ -159,6 +178,13 @@
|
|||
if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
|
||||
$connector.removeClass('hidden');
|
||||
}
|
||||
if (this.summary.filter === '') {
|
||||
$filterInfo.html('');
|
||||
$filterInfo.addClass('hidden');
|
||||
} else {
|
||||
$filterInfo.html(n('files', ' matches \'{filter}\'', ' match \'{filter}\'', this.summary.totalDirs + this.summary.totalFiles, {filter: this.summary.filter}));
|
||||
$filterInfo.removeClass('hidden');
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
if (!this.$el) {
|
||||
|
@ -168,6 +194,11 @@
|
|||
var summary = this.summary;
|
||||
var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
|
||||
var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
|
||||
if (this.summary.filter === '') {
|
||||
var filterInfo = '';
|
||||
} else {
|
||||
var filterInfo = n('files', ' matches \'{filter}\'', ' match \'{filter}\'', summary.totalFiles + summary.totalDirs, {filter: summary.filter});
|
||||
}
|
||||
|
||||
var infoVars = {
|
||||
dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
|
||||
|
@ -182,7 +213,7 @@
|
|||
|
||||
var info = t('files', '{dirs} and {files}', infoVars);
|
||||
|
||||
var $summary = $('<td><span class="info">'+info+'</span></td>'+fileSize+'<td class="date"></td>');
|
||||
var $summary = $('<td><span class="info">'+info+'<span class="filter">'+filterInfo+'</span></span></td>'+fileSize+'<td class="date"></td>');
|
||||
|
||||
if (!this.summary.totalFiles && !this.summary.totalDirs) {
|
||||
this.$el.addClass('hidden');
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright (c) 2014
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* Construct a new FileActions instance
|
||||
* @constructs Files
|
||||
*/
|
||||
var Files = function() {
|
||||
this.initialize();
|
||||
};
|
||||
/**
|
||||
* @memberof OCA.Search
|
||||
*/
|
||||
Files.prototype = {
|
||||
|
||||
fileList: null,
|
||||
|
||||
/**
|
||||
* Initialize the file search
|
||||
*/
|
||||
initialize: function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
this.fileAppLoaded = function() {
|
||||
return !!OCA.Files && !!OCA.Files.App;
|
||||
};
|
||||
function inFileList($row, result) {
|
||||
if (! self.fileAppLoaded()) {
|
||||
return false;
|
||||
}
|
||||
var dir = self.fileList.getCurrentDirectory().replace(/\/+$/,'');
|
||||
var resultDir = OC.dirname(result.path);
|
||||
return dir === resultDir && self.fileList.inList(result.name);
|
||||
}
|
||||
function updateLegacyMimetype(result) {
|
||||
// backward compatibility:
|
||||
if (!result.mime && result.mime_type) {
|
||||
result.mime = result.mime_type;
|
||||
}
|
||||
}
|
||||
function hideNoFilterResults() {
|
||||
var $nofilterresults = $('.nofilterresults');
|
||||
if ( ! $nofilterresults.hasClass('hidden') ) {
|
||||
$nofilterresults.addClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
this.renderFolderResult = function($row, result) {
|
||||
if (inFileList($row, result)) {
|
||||
return null;
|
||||
}
|
||||
hideNoFilterResults();
|
||||
/*render folder icon, show path beneath filename,
|
||||
show size and last modified date on the right */
|
||||
this.updateLegacyMimetype(result);
|
||||
|
||||
var $pathDiv = $('<div class="path"></div>').text(result.path);
|
||||
$row.find('td.info div.name').after($pathDiv).text(result.name);
|
||||
|
||||
$row.find('td.result a').attr('href', result.link);
|
||||
$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/folder') + ')');
|
||||
return $row;
|
||||
};
|
||||
|
||||
this.renderFileResult = function($row, result) {
|
||||
if (inFileList($row, result)) {
|
||||
return null;
|
||||
}
|
||||
hideNoFilterResults();
|
||||
/*render preview icon, show path beneath filename,
|
||||
show size and last modified date on the right */
|
||||
this.updateLegacyMimetype(result);
|
||||
|
||||
var $pathDiv = $('<div class="path"></div>').text(result.path);
|
||||
$row.find('td.info div.name').after($pathDiv).text(result.name);
|
||||
|
||||
$row.find('td.result a').attr('href', result.link);
|
||||
|
||||
if (self.fileAppLoaded()) {
|
||||
self.fileList.lazyLoadPreview({
|
||||
path: result.path,
|
||||
mime: result.mime,
|
||||
callback: function (url) {
|
||||
$row.find('td.icon').css('background-image', 'url(' + url + ')');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// FIXME how to get mime icon if not in files app
|
||||
var mimeicon = result.mime.replace('/', '-');
|
||||
$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/' + mimeicon) + ')');
|
||||
var dir = OC.dirname(result.path);
|
||||
if (dir === '') {
|
||||
dir = '/';
|
||||
}
|
||||
$row.find('td.info a').attr('href',
|
||||
OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', {dir: dir, scrollto: result.name})
|
||||
);
|
||||
}
|
||||
return $row;
|
||||
};
|
||||
|
||||
this.renderAudioResult = function($row, result) {
|
||||
/*render preview icon, show path beneath filename,
|
||||
show size and last modified date on the right
|
||||
show Artist and Album */
|
||||
$row = this.renderFileResult($row, result);
|
||||
if ($row) {
|
||||
$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/audio') + ')');
|
||||
}
|
||||
return $row;
|
||||
};
|
||||
|
||||
this.renderImageResult = function($row, result) {
|
||||
/*render preview icon, show path beneath filename,
|
||||
show size and last modified date on the right
|
||||
show width and height */
|
||||
$row = this.renderFileResult($row, result);
|
||||
if ($row && !self.fileAppLoaded()) {
|
||||
$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/image') + ')');
|
||||
}
|
||||
return $row;
|
||||
};
|
||||
|
||||
|
||||
this.handleFolderClick = function($row, result, event) {
|
||||
// open folder
|
||||
if (self.fileAppLoaded()) {
|
||||
self.fileList.changeDirectory(result.path);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
this.handleFileClick = function($row, result, event) {
|
||||
if (self.fileAppLoaded()) {
|
||||
self.fileList.changeDirectory(OC.dirname(result.path));
|
||||
self.fileList.scrollTo(result.name);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
this.updateLegacyMimetype = function (result) {
|
||||
// backward compatibility:
|
||||
if (!result.mime && result.mime_type) {
|
||||
result.mime = result.mime_type;
|
||||
}
|
||||
};
|
||||
this.setFileList = function (fileList) {
|
||||
this.fileList = fileList;
|
||||
};
|
||||
|
||||
OC.Plugins.register('OCA.Search', this);
|
||||
},
|
||||
attach: function(search) {
|
||||
var self = this;
|
||||
search.setFilter('files', function (query) {
|
||||
if (self.fileAppLoaded()) {
|
||||
self.fileList.setFilter(query);
|
||||
if (query.length > 2) {
|
||||
//search is not started until 500msec have passed
|
||||
window.setTimeout(function() {
|
||||
$('.nofilterresults').addClass('hidden');
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
search.setRenderer('folder', this.renderFolderResult.bind(this));
|
||||
search.setRenderer('file', this.renderFileResult.bind(this));
|
||||
search.setRenderer('audio', this.renderAudioResult.bind(this));
|
||||
search.setRenderer('image', this.renderImageResult.bind(this));
|
||||
|
||||
search.setHandler('folder', this.handleFolderClick.bind(this));
|
||||
search.setHandler(['file', 'audio', 'image'], this.handleFileClick.bind(this));
|
||||
}
|
||||
};
|
||||
OCA.Search.Files = Files;
|
||||
OCA.Search.files = new Files();
|
||||
})();
|
|
@ -60,6 +60,12 @@
|
|||
<p><?php p($l->t('Upload some content or sync with your devices!')); ?></p>
|
||||
</div>
|
||||
|
||||
<div class="nofilterresults hidden">
|
||||
<div class="icon-search"></div>
|
||||
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
|
||||
<input type="hidden" name="dir" value="" id="dir">
|
||||
|
||||
<div class="nofilterresults hidden">
|
||||
<div class="icon-search"></div>
|
||||
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<table id="filestable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -85,4 +85,67 @@ describe('OCA.Files.FileSummary tests', function() {
|
|||
expect(s.summary.totalFiles).toEqual(1);
|
||||
expect(s.summary.totalSize).toEqual(127900);
|
||||
});
|
||||
|
||||
it('renders filtered summary as text', function() {
|
||||
var s = new FileSummary($container);
|
||||
s.setSummary({
|
||||
totalDirs: 5,
|
||||
totalFiles: 2,
|
||||
totalSize: 256000,
|
||||
filter: 'foo'
|
||||
});
|
||||
expect($container.hasClass('hidden')).toEqual(false);
|
||||
expect($container.find('.info').text()).toEqual('5 folders and 2 files match \'foo\'');
|
||||
expect($container.find('.filesize').text()).toEqual('250 kB');
|
||||
});
|
||||
it('hides filtered summary when no files or folders', function() {
|
||||
var s = new FileSummary($container);
|
||||
s.setSummary({
|
||||
totalDirs: 0,
|
||||
totalFiles: 0,
|
||||
totalSize: 0,
|
||||
filter: 'foo'
|
||||
});
|
||||
expect($container.hasClass('hidden')).toEqual(true);
|
||||
});
|
||||
it('increases filtered summary when adding files', function() {
|
||||
var s = new FileSummary($container);
|
||||
s.setSummary({
|
||||
totalDirs: 5,
|
||||
totalFiles: 2,
|
||||
totalSize: 256000,
|
||||
filter: 'foo'
|
||||
});
|
||||
s.add({name: 'bar.txt', type: 'file', size: 256000});
|
||||
s.add({name: 'foo.txt', type: 'file', size: 256001});
|
||||
s.add({name: 'bar', type: 'dir', size: 100});
|
||||
s.add({name: 'foo', type: 'dir', size: 102});
|
||||
s.update();
|
||||
expect($container.hasClass('hidden')).toEqual(false);
|
||||
expect($container.find('.info').text()).toEqual('6 folders and 3 files match \'foo\'');
|
||||
expect($container.find('.filesize').text()).toEqual('500 kB');
|
||||
expect(s.summary.totalDirs).toEqual(6);
|
||||
expect(s.summary.totalFiles).toEqual(3);
|
||||
expect(s.summary.totalSize).toEqual(512103);
|
||||
});
|
||||
it('decreases filtered summary when removing files', function() {
|
||||
var s = new FileSummary($container);
|
||||
s.setSummary({
|
||||
totalDirs: 5,
|
||||
totalFiles: 2,
|
||||
totalSize: 256000,
|
||||
filter: 'foo'
|
||||
});
|
||||
s.remove({name: 'bar.txt', type: 'file', size: 128000});
|
||||
s.remove({name: 'foo.txt', type: 'file', size: 127999});
|
||||
s.remove({name: 'bar', type: 'dir', size: 100});
|
||||
s.remove({name: 'foo', type: 'dir', size: 98});
|
||||
s.update();
|
||||
expect($container.hasClass('hidden')).toEqual(false);
|
||||
expect($container.find('.info').text()).toEqual('4 folders and 1 file match \'foo\'');
|
||||
expect($container.find('.filesize').text()).toEqual('125 kB');
|
||||
expect(s.summary.totalDirs).toEqual(4);
|
||||
expect(s.summary.totalFiles).toEqual(1);
|
||||
expect(s.summary.totalSize).toEqual(127903);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
<input type="hidden" name="dir" value="" id="dir">
|
||||
|
||||
<div class="nofilterresults hidden">
|
||||
<div class="icon-search"></div>
|
||||
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<table id="filestable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -12,6 +12,12 @@
|
|||
|
||||
<input type="hidden" name="dir" value="" id="dir">
|
||||
|
||||
<div class="nofilterresults hidden">
|
||||
<div class="icon-search"></div>
|
||||
<h2><?php p($l->t('No entries found in this folder')); ?></h2>
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<table id="filestable">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -40,9 +40,9 @@ try {
|
|||
$preview->setMaxY($maxY);
|
||||
$preview->setScalingUp($scalingUp);
|
||||
$preview->setKeepAspect($keepAspect);
|
||||
$preview->showPreview();
|
||||
}
|
||||
|
||||
$preview->showPreview();
|
||||
} catch (\Exception $e) {
|
||||
\OC_Response::setStatus(500);
|
||||
\OC_Log::write('core', $e->getmessage(), \OC_Log::DEBUG);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"eventsource.js",
|
||||
"config.js",
|
||||
"multiselect.js",
|
||||
"oc-requesttoken.js"
|
||||
"oc-requesttoken.js",
|
||||
"../../search/js/search.js"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -308,22 +308,9 @@ var OC={
|
|||
* Do a search query and display the results
|
||||
* @param {string} query the search query
|
||||
*/
|
||||
search: _.debounce(function(query){
|
||||
if(query){
|
||||
OC.addStyle('search','results');
|
||||
var classList = document.getElementById('content').className.split(/\s+/);
|
||||
var inApps = [];
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
if (classList[i].indexOf('app-') === 0) {
|
||||
var inApps = [classList[i].substr(4)];
|
||||
}
|
||||
}
|
||||
$.getJSON(OC.generateUrl('search/ajax/search.php'), {inApps:inApps, query:query}, function(results){
|
||||
OC.search.lastResults=results;
|
||||
OC.search.showResults(results);
|
||||
});
|
||||
}
|
||||
}, 500),
|
||||
search: function (query) {
|
||||
OC.Search.search(query, null, 0, 30);
|
||||
},
|
||||
/**
|
||||
* Dialog helper for jquery dialogs.
|
||||
*
|
||||
|
@ -608,10 +595,12 @@ OC.Plugins = {
|
|||
/**
|
||||
* @namespace OC.search
|
||||
*/
|
||||
OC.search.customResults={};
|
||||
OC.search.currentResult=-1;
|
||||
OC.search.lastQuery='';
|
||||
OC.search.lastResults={};
|
||||
OC.search.customResults = {};
|
||||
/**
|
||||
* @deprecated use get/setFormatter() instead
|
||||
*/
|
||||
OC.search.resultTypes = {};
|
||||
|
||||
OC.addStyle.loaded=[];
|
||||
OC.addScript.loaded=[];
|
||||
|
||||
|
@ -1038,48 +1027,6 @@ function initCore() {
|
|||
}else{
|
||||
SVGSupport.checkMimeType();
|
||||
}
|
||||
$('form.searchbox').submit(function(event){
|
||||
event.preventDefault();
|
||||
});
|
||||
$('#searchbox').keyup(function(event){
|
||||
if(event.keyCode===13){//enter
|
||||
if(OC.search.currentResult>-1){
|
||||
var result=$('#searchresults tr.result a')[OC.search.currentResult];
|
||||
window.location = $(result).attr('href');
|
||||
}
|
||||
}else if(event.keyCode===38){//up
|
||||
if(OC.search.currentResult>0){
|
||||
OC.search.currentResult--;
|
||||
OC.search.renderCurrent();
|
||||
}
|
||||
}else if(event.keyCode===40){//down
|
||||
if(OC.search.lastResults.length>OC.search.currentResult+1){
|
||||
OC.search.currentResult++;
|
||||
OC.search.renderCurrent();
|
||||
}
|
||||
}else if(event.keyCode===27){//esc
|
||||
OC.search.hide();
|
||||
if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
|
||||
FileList.unfilter();
|
||||
}
|
||||
}else{
|
||||
var query=$('#searchbox').val();
|
||||
if(OC.search.lastQuery!==query){
|
||||
OC.search.lastQuery=query;
|
||||
OC.search.currentResult=-1;
|
||||
if (FileList && typeof FileList.filter === 'function') { //TODO add hook system
|
||||
FileList.filter(query);
|
||||
}
|
||||
if(query.length>2){
|
||||
OC.search(query);
|
||||
}else{
|
||||
if(OC.search.hide){
|
||||
OC.search.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// user menu
|
||||
$('#settings #expand').keydown(function(event) {
|
||||
|
|
|
@ -362,7 +362,7 @@ class OC {
|
|||
OC_Util::addScript("eventsource");
|
||||
OC_Util::addScript("config");
|
||||
//OC_Util::addScript( "multiselect" );
|
||||
OC_Util::addScript('search', 'result');
|
||||
OC_Util::addScript('search', 'search');
|
||||
OC_Util::addScript("oc-requesttoken");
|
||||
OC_Util::addScript("apps");
|
||||
OC_Util::addVendorScript('snapjs/dist/latest/snap');
|
||||
|
|
|
@ -278,6 +278,7 @@ abstract class Common implements \OC\Files\Storage\Storage {
|
|||
}
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
*/
|
||||
|
||||
namespace OC;
|
||||
use OCP\Search\PagedProvider;
|
||||
use OCP\Search\Provider;
|
||||
use OCP\ISearch;
|
||||
|
||||
|
@ -39,12 +40,38 @@ class Search implements ISearch {
|
|||
* @return array An array of OC\Search\Result's
|
||||
*/
|
||||
public function search($query, array $inApps = array()) {
|
||||
// old apps might assume they get all results, so we set size 0
|
||||
return $this->searchPaged($query, $inApps, 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search all providers for $query
|
||||
* @param string $query
|
||||
* @param string[] $inApps optionally limit results to the given apps
|
||||
* @param int $page pages start at page 1
|
||||
* @param int $size, 0 = all
|
||||
* @return array An array of OC\Search\Result's
|
||||
*/
|
||||
public function searchPaged($query, array $inApps = array(), $page = 1, $size = 30) {
|
||||
$this->initProviders();
|
||||
$results = array();
|
||||
foreach($this->providers as $provider) {
|
||||
/** @var $provider Provider */
|
||||
if ($provider->providesResultsFor($inApps)) {
|
||||
$results = array_merge($results, $provider->search($query));
|
||||
if ( ! $provider->providesResultsFor($inApps) ) {
|
||||
continue;
|
||||
}
|
||||
if ($provider instanceof PagedProvider) {
|
||||
$results = array_merge($results, $provider->searchPaged($query, $page, $size));
|
||||
} else if ($provider instanceof Provider) {
|
||||
$providerResults = $provider->search($query);
|
||||
if ($size > 0) {
|
||||
$slicedResults = array_slice($providerResults, ($page - 1) * $size, $size);
|
||||
$results = array_merge($results, $slicedResults);
|
||||
} else {
|
||||
$results = array_merge($results, $providerResults);
|
||||
}
|
||||
} else {
|
||||
\OC::$server->getLogger()->warning('Ignoring Unknown search provider', array('provider' => $provider));
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
|
|
|
@ -83,7 +83,7 @@ class File extends \OCP\Search\Result {
|
|||
$this->path = $path;
|
||||
$this->size = $data->getSize();
|
||||
$this->modified = $data->getMtime();
|
||||
$this->mime_type = $data->getMimetype();
|
||||
$this->mime = $data->getMimetype();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,9 +34,20 @@ interface ISearch {
|
|||
* @param string $query
|
||||
* @param string[] $inApps optionally limit results to the given apps
|
||||
* @return array An array of OCP\Search\Result's
|
||||
* @deprecated use searchPaged() with page and size
|
||||
*/
|
||||
public function search($query, array $inApps = array());
|
||||
|
||||
/**
|
||||
* Search all providers for $query
|
||||
* @param string $query
|
||||
* @param string[] $inApps optionally limit results to the given apps
|
||||
* @param int $page pages start at page 1
|
||||
* @param int $size
|
||||
* @return array An array of OCP\Search\Result's
|
||||
*/
|
||||
public function searchPaged($query, array $inApps = array(), $page = 1, $size = 30);
|
||||
|
||||
/**
|
||||
* Register a new search provider to search with
|
||||
* @param string $class class name of a OCP\Search\Provider
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Search;
|
||||
|
||||
/**
|
||||
* Provides a template for search functionality throughout ownCloud;
|
||||
*/
|
||||
abstract class PagedProvider extends Provider {
|
||||
|
||||
/**
|
||||
* show all results
|
||||
*/
|
||||
const SIZE_ALL = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for $query
|
||||
* @param string $query
|
||||
* @return array An array of OCP\Search\Result's
|
||||
*/
|
||||
public function search($query) {
|
||||
// old apps might assume they get all results, so we use SIZE_ALL
|
||||
$this->searchPaged($query, 1, self::SIZE_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for $query
|
||||
* @param string $query
|
||||
* @param int $page pages start at page 1
|
||||
* @param int $size, 0 = SIZE_ALL
|
||||
* @return array An array of OCP\Search\Result's
|
||||
*/
|
||||
abstract public function searchPaged($query, $page, $size);
|
||||
}
|
|
@ -27,10 +27,10 @@ abstract class Provider {
|
|||
const OPTION_APPS = 'apps';
|
||||
|
||||
/**
|
||||
* List of options (currently unused)
|
||||
* List of options
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
|
|
@ -38,8 +38,18 @@ if (isset($_GET['inApps'])) {
|
|||
} else {
|
||||
$inApps = array();
|
||||
}
|
||||
if (isset($_GET['page'])) {
|
||||
$page = (int)$_GET['page'];
|
||||
} else {
|
||||
$page = 1;
|
||||
}
|
||||
if (isset($_GET['size'])) {
|
||||
$size = (int)$_GET['size'];
|
||||
} else {
|
||||
$size = 30;
|
||||
}
|
||||
if($query) {
|
||||
$result = \OC::$server->getSearch()->search($query, $inApps);
|
||||
$result = \OC::$server->getSearch()->searchPaged($query, $inApps, $page, $size);
|
||||
OC_JSON::encodedPrint($result);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -4,38 +4,42 @@
|
|||
|
||||
#searchresults {
|
||||
background-color:#fff;
|
||||
border-bottom-left-radius:11px;
|
||||
box-shadow:0 0 10px #000;
|
||||
list-style:none;
|
||||
max-height:80%;
|
||||
overflow-x:hidden;
|
||||
overflow-y: auto;
|
||||
padding-bottom:6px;
|
||||
position:fixed;
|
||||
right:0;
|
||||
text-overflow:ellipsis;
|
||||
top:45px;
|
||||
width:380px;
|
||||
max-width: 95%;
|
||||
padding-top: 65px;
|
||||
box-sizing: border-box;
|
||||
z-index:75;
|
||||
}
|
||||
|
||||
.ie8 #searchresults {
|
||||
border: 1px solid #666 !important;
|
||||
#searchresults.hidden {
|
||||
display: none;
|
||||
}
|
||||
#searchresults * {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
#searchresults li.resultHeader {
|
||||
background-color:#eee;
|
||||
border-bottom:solid 1px #CCC;
|
||||
font-size:1.2em;
|
||||
font-weight:700;
|
||||
padding:.2em;
|
||||
#searchresults #status {
|
||||
background-color: rgba(255, 255, 255, .85);
|
||||
height: 12px;
|
||||
padding: 28px 0 28px 56px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.has-favorites:not(.hidden) ~ #searchresults #status {
|
||||
padding-left: 102px;
|
||||
}
|
||||
#searchresults #status.fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#searchresults li.result {
|
||||
margin-left:2em;
|
||||
#searchresults #status .spinner {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#searchresults table {
|
||||
border-spacing:0;
|
||||
table-layout:fixed;
|
||||
|
@ -44,48 +48,53 @@
|
|||
}
|
||||
|
||||
#searchresults td {
|
||||
padding:0 .3em;
|
||||
height: 44px;
|
||||
padding: 5px 19px;
|
||||
font-style: normal;
|
||||
vertical-align: middle;
|
||||
border-bottom: none;
|
||||
}
|
||||
#searchresults td.icon {
|
||||
text-align: right;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 5px 0;
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.has-favorites:not(.hidden) ~ #searchresults td.icon {
|
||||
width: 86px;
|
||||
}
|
||||
|
||||
#searchresults tr.template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#searchresults .name,
|
||||
#searchresults .text {
|
||||
#searchresults .text,
|
||||
#searchresults .path {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#searchresults .text {
|
||||
padding-left: 16px;
|
||||
color: #999;
|
||||
white-space: normal;
|
||||
color: #545454;
|
||||
}
|
||||
#searchresults .path {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
}
|
||||
#searchresults .text em {
|
||||
color: #545454;
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#searchresults td.result * {
|
||||
#searchresults tr.result * {
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
#searchresults td.container {
|
||||
width:20px;
|
||||
}
|
||||
|
||||
#searchresults td.container img {
|
||||
vertical-align: middle;
|
||||
display:none;
|
||||
}
|
||||
#searchresults tr:hover td.container img {
|
||||
display:inline;
|
||||
}
|
||||
|
||||
#searchresults td.type {
|
||||
border-bottom:none;
|
||||
border-right:1px solid #aaa;
|
||||
font-weight:700;
|
||||
text-align:right;
|
||||
width:3.5em;
|
||||
}
|
||||
|
||||
#searchresults tr.current {
|
||||
background-color:#ddd;
|
||||
}
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2014
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
//translations for result type ids, can be extended by apps
|
||||
OC.search.resultTypes={
|
||||
file: t('core','File'),
|
||||
folder: t('core','Folder'),
|
||||
image: t('core','Image'),
|
||||
audio: t('core','Audio')
|
||||
};
|
||||
OC.search.catagorizeResults=function(results){
|
||||
var types={};
|
||||
for(var i=0;i<results.length;i++){
|
||||
var type=results[i].type;
|
||||
if(!types[type]){
|
||||
types[type]=[];
|
||||
}
|
||||
types[type].push(results[i]);
|
||||
}
|
||||
return types;
|
||||
};
|
||||
OC.search.hide=function(){
|
||||
$('#searchresults').hide();
|
||||
if($('#searchbox').val().length>2){
|
||||
$('#searchbox').val('');
|
||||
if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
|
||||
FileList.unfilter();
|
||||
}
|
||||
};
|
||||
if ($('#searchbox').val().length === 0) {
|
||||
if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
|
||||
FileList.unfilter();
|
||||
}
|
||||
}
|
||||
};
|
||||
OC.search.showResults=function(results){
|
||||
if(results.length === 0){
|
||||
return;
|
||||
}
|
||||
if(!OC.search.showResults.loaded){
|
||||
var parent=$('<div/>');
|
||||
$('body').append(parent);
|
||||
parent.load(OC.filePath('search','templates','part.results.php'),function(){
|
||||
OC.search.showResults.loaded=true;
|
||||
$('#searchresults').click(function(event){
|
||||
OC.search.hide();
|
||||
event.stopPropagation();
|
||||
});
|
||||
$(document).click(function(event){
|
||||
OC.search.hide();
|
||||
if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
|
||||
FileList.unfilter();
|
||||
}
|
||||
});
|
||||
OC.search.lastResults=results;
|
||||
OC.search.showResults(results);
|
||||
});
|
||||
}else{
|
||||
var types=OC.search.catagorizeResults(results);
|
||||
$('#searchresults').show();
|
||||
$('#searchresults tr.result').remove();
|
||||
var index=0;
|
||||
for(var typeid in types){
|
||||
var type=types[typeid];
|
||||
if(type.length>0){
|
||||
for(var i=0;i<type.length;i++){
|
||||
var row=$('#searchresults tr.template').clone();
|
||||
row.removeClass('template');
|
||||
row.addClass('result');
|
||||
|
||||
row.data('type', typeid);
|
||||
row.data('name', type[i].name);
|
||||
row.data('text', type[i].text);
|
||||
row.data('index',index);
|
||||
|
||||
if (i === 0){
|
||||
var typeName = OC.search.resultTypes[typeid];
|
||||
row.children('td.type').text(t('lib', typeName));
|
||||
}
|
||||
row.find('td.result div.name').text(type[i].name);
|
||||
row.find('td.result div.text').text(type[i].text);
|
||||
|
||||
if (type[i].path) {
|
||||
var parent = OC.dirname(type[i].path);
|
||||
if (parent === '') {
|
||||
parent = '/';
|
||||
}
|
||||
var containerName = OC.basename(parent);
|
||||
if (containerName === '') {
|
||||
containerName = '/';
|
||||
}
|
||||
var containerLink = OC.linkTo('files', 'index.php')
|
||||
+'/?dir='+encodeURIComponent(parent)
|
||||
+'&scrollto='+encodeURIComponent(type[i].name);
|
||||
row.find('td.result a')
|
||||
.attr('href', containerLink)
|
||||
.attr('title', t('core', 'Show in {folder}', {folder: containerName}));
|
||||
} else {
|
||||
row.find('td.result a').attr('href', type[i].link);
|
||||
}
|
||||
|
||||
index++;
|
||||
/**
|
||||
* Give plugins the ability to customize the search results. For example:
|
||||
* OC.search.customResults.file = function (row, item){
|
||||
* if(item.name.search('.json') >= 0) ...
|
||||
* };
|
||||
*/
|
||||
if(OC.search.customResults[typeid]){
|
||||
OC.search.customResults[typeid](row, type[i]);
|
||||
}
|
||||
$('#searchresults tbody').append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#searchresults').on('click', 'result', function () {
|
||||
if ($(this).data('type') === 'Files') {
|
||||
//FIXME use ajax to navigate to folder & highlight file
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
OC.search.showResults.loaded=false;
|
||||
|
||||
OC.search.renderCurrent=function(){
|
||||
if($('#searchresults tr.result')[OC.search.currentResult]){
|
||||
var result=$('#searchresults tr.result')[OC.search.currentResult];
|
||||
$('#searchresults tr.result').removeClass('current');
|
||||
$(result).addClass('current');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,378 @@
|
|||
/**
|
||||
* ownCloud - core
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*
|
||||
* @author Jörn Friedrich Dreyer <jfd@owncloud.com>
|
||||
* @copyright Jörn Friedrich Dreyer 2014
|
||||
*/
|
||||
|
||||
(function () {
|
||||
/**
|
||||
* @class OCA.Search
|
||||
* @classdesc
|
||||
*
|
||||
* The Search class manages a search queries and their results
|
||||
*
|
||||
* @param $searchBox container element with existing markup for the #searchbox form
|
||||
* @param $searchResults container element for results und status message
|
||||
*/
|
||||
var Search = function($searchBox, $searchResults) {
|
||||
this.initialize($searchBox, $searchResults);
|
||||
};
|
||||
/**
|
||||
* @memberof OC
|
||||
*/
|
||||
Search.prototype = {
|
||||
|
||||
/**
|
||||
* Initialize the search box
|
||||
*
|
||||
* @param $searchBox container element with existing markup for the #searchbox form
|
||||
* @param $searchResults container element for results und status message
|
||||
* @private
|
||||
*/
|
||||
initialize: function($searchBox, $searchResults) {
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* contains closures that are called to filter the current content
|
||||
*/
|
||||
var filters = {};
|
||||
this.setFilter = function(type, filter) {
|
||||
filters[type] = filter;
|
||||
};
|
||||
this.hasFilter = function(type) {
|
||||
return typeof filters[type] !== 'undefined';
|
||||
};
|
||||
this.getFilter = function(type) {
|
||||
return filters[type];
|
||||
};
|
||||
|
||||
/**
|
||||
* contains closures that are called to render search results
|
||||
*/
|
||||
var renderers = {};
|
||||
this.setRenderer = function(type, renderer) {
|
||||
renderers[type] = renderer;
|
||||
};
|
||||
this.hasRenderer = function(type) {
|
||||
return typeof renderers[type] !== 'undefined';
|
||||
};
|
||||
this.getRenderer = function(type) {
|
||||
return renderers[type];
|
||||
};
|
||||
|
||||
/**
|
||||
* contains closures that are called when a search result has been clicked
|
||||
*/
|
||||
var handlers = {};
|
||||
this.setHandler = function(type, handler) {
|
||||
handlers[type] = handler;
|
||||
};
|
||||
this.hasHandler = function(type) {
|
||||
return typeof handlers[type] !== 'undefined';
|
||||
};
|
||||
this.getHandler = function(type) {
|
||||
return handlers[type];
|
||||
};
|
||||
|
||||
var currentResult = -1;
|
||||
var lastQuery = '';
|
||||
var lastInApps = [];
|
||||
var lastPage = 0;
|
||||
var lastSize = 30;
|
||||
var lastResults = [];
|
||||
var timeoutID = null;
|
||||
|
||||
this.getLastQuery = function() {
|
||||
return lastQuery;
|
||||
};
|
||||
|
||||
/**
|
||||
* Do a search query and display the results
|
||||
* @param {string} query the search query
|
||||
*/
|
||||
this.search = function(query, inApps, page, size) {
|
||||
if (query) {
|
||||
OC.addStyle('search','results');
|
||||
if (typeof page !== 'number') {
|
||||
page = 1;
|
||||
}
|
||||
if (typeof size !== 'number') {
|
||||
size = 30;
|
||||
}
|
||||
if (typeof inApps !== 'object') {
|
||||
var currentApp = getCurrentApp();
|
||||
if(currentApp) {
|
||||
inApps = [currentApp];
|
||||
} else {
|
||||
inApps = [];
|
||||
}
|
||||
}
|
||||
// prevent double pages
|
||||
if ($searchResults && query === lastQuery && page === lastPage && size === lastSize) {
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(timeoutID);
|
||||
timeoutID = window.setTimeout(function() {
|
||||
lastQuery = query;
|
||||
lastInApps = inApps;
|
||||
lastPage = page;
|
||||
lastSize = size;
|
||||
|
||||
//show spinner
|
||||
$searchResults.removeClass('hidden');
|
||||
$status.html(t('core', 'Searching other places')+'<img class="spinner" alt="search in progress" src="'+OC.webroot+'/core/img/loading.gif" />');
|
||||
|
||||
// do the actual search query
|
||||
$.getJSON(OC.generateUrl('search/ajax/search.php'), {query:query, inApps:inApps, page:page, size:size }, function(results) {
|
||||
lastResults = results;
|
||||
if (page === 1) {
|
||||
showResults(results);
|
||||
} else {
|
||||
addResults(results);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
//TODO should be a core method, see https://github.com/owncloud/core/issues/12557
|
||||
function getCurrentApp() {
|
||||
var content = document.getElementById('content');
|
||||
if (content) {
|
||||
var classList = document.getElementById('content').className.split(/\s+/);
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
if (classList[i].indexOf('app-') === 0) {
|
||||
return classList[i].substr(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var $status = $searchResults.find('#status');
|
||||
const summaryAndStatusHeight = 118;
|
||||
|
||||
function isStatusOffScreen() {
|
||||
return $searchResults.position() && ($searchResults.position().top + summaryAndStatusHeight > window.innerHeight);
|
||||
}
|
||||
|
||||
function placeStatus() {
|
||||
if (isStatusOffScreen()) {
|
||||
$status.addClass('fixed');
|
||||
} else {
|
||||
$status.removeClass('fixed');
|
||||
}
|
||||
}
|
||||
function showResults(results) {
|
||||
lastResults = results;
|
||||
$searchResults.find('tr.result').remove();
|
||||
$searchResults.removeClass('hidden');
|
||||
addResults(results);
|
||||
}
|
||||
function addResults(results) {
|
||||
var $template = $searchResults.find('tr.template');
|
||||
jQuery.each(results, function (i, result) {
|
||||
var $row = $template.clone();
|
||||
$row.removeClass('template');
|
||||
$row.addClass('result');
|
||||
|
||||
$row.data('result', result);
|
||||
|
||||
// generic results only have four attributes
|
||||
$row.find('td.info div.name').text(result.name);
|
||||
$row.find('td.info a').attr('href', result.link);
|
||||
|
||||
/**
|
||||
* Give plugins the ability to customize the search results. see result.js for examples
|
||||
*/
|
||||
if (self.hasRenderer(result.type)) {
|
||||
$row = self.getRenderer(result.type)($row, result);
|
||||
} else {
|
||||
// for backward compatibility add text div
|
||||
$row.find('td.info div.name').addClass('result');
|
||||
$row.find('td.result div.name').after('<div class="text"></div>');
|
||||
$row.find('td.result div.text').text(result.name);
|
||||
if (OC.search.customResults && OC.search.customResults[result.type]) {
|
||||
OC.search.customResults[result.type]($row, result);
|
||||
}
|
||||
}
|
||||
if ($row) {
|
||||
$searchResults.find('tbody').append($row);
|
||||
}
|
||||
});
|
||||
var count = $searchResults.find('tr.result').length;
|
||||
$status.data('count', count);
|
||||
if (count === 0) {
|
||||
$status.text(t('core', 'No search result in other places'));
|
||||
} else {
|
||||
$status.text(n('core', '{count} search result in other places', '{count} search results in other places', count, {count:count}));
|
||||
}
|
||||
}
|
||||
function renderCurrent() {
|
||||
var result = $searchResults.find('tr.result')[currentResult];
|
||||
if (result) {
|
||||
var $result = $(result);
|
||||
var currentOffset = $('#app-content').scrollTop();
|
||||
$('#app-content').animate({
|
||||
// Scrolling to the top of the new result
|
||||
scrollTop: currentOffset + $result.offset().top - $result.height() * 2
|
||||
}, {
|
||||
duration: 100
|
||||
});
|
||||
$searchResults.find('tr.result.current').removeClass('current');
|
||||
$result.addClass('current');
|
||||
}
|
||||
}
|
||||
this.hideResults = function() {
|
||||
$searchResults.addClass('hidden');
|
||||
$searchResults.find('tr.result').remove();
|
||||
lastQuery = false;
|
||||
};
|
||||
this.clear = function() {
|
||||
self.hideResults();
|
||||
if(self.hasFilter(getCurrentApp())) {
|
||||
self.getFilter(getCurrentApp())('');
|
||||
}
|
||||
$searchBox.val('');
|
||||
$searchBox.blur();
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for when scrolling the list container.
|
||||
* This appends/renders the next page of entries when reaching the bottom.
|
||||
*/
|
||||
function onScroll(e) {
|
||||
if ($searchResults && lastQuery !== false && lastResults.length > 0) {
|
||||
var resultsBottom = $searchResults.offset().top + $searchResults.height();
|
||||
var containerBottom = $searchResults.offsetParent().offset().top + $searchResults.offsetParent().height();
|
||||
if ( resultsBottom < containerBottom * 1.2 ) {
|
||||
self.search(lastQuery, lastInApps, lastPage + 1);
|
||||
}
|
||||
placeStatus();
|
||||
}
|
||||
}
|
||||
|
||||
$('#app-content').on('scroll', _.bind(onScroll, this));
|
||||
|
||||
/**
|
||||
* scrolls the search results to the top
|
||||
*/
|
||||
function scrollToResults() {
|
||||
setTimeout(function() {
|
||||
if (isStatusOffScreen()) {
|
||||
var newScrollTop = $('#app-content').prop('scrollHeight') - $searchResults.height();
|
||||
console.log('scrolling to ' + newScrollTop);
|
||||
$('#app-content').animate({
|
||||
scrollTop: newScrollTop
|
||||
}, {
|
||||
duration: 100,
|
||||
complete: function () {
|
||||
scrollToResults();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
$('form.searchbox').submit(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
$searchBox.on('search', function (event) {
|
||||
if($searchBox.val() === '') {
|
||||
if(self.hasFilter(getCurrentApp())) {
|
||||
self.getFilter(getCurrentApp())('');
|
||||
}
|
||||
self.hideResults();
|
||||
}
|
||||
});
|
||||
$searchBox.keyup(function(event) {
|
||||
if (event.keyCode === 13) { //enter
|
||||
if(currentResult > -1) {
|
||||
var result = $searchResults.find('tr.result a')[currentResult];
|
||||
window.location = $(result).attr('href');
|
||||
}
|
||||
} else if(event.keyCode === 38) { //up
|
||||
if(currentResult > 0) {
|
||||
currentResult--;
|
||||
renderCurrent();
|
||||
}
|
||||
} else if(event.keyCode === 40) { //down
|
||||
if(lastResults.length > currentResult + 1){
|
||||
currentResult++;
|
||||
renderCurrent();
|
||||
}
|
||||
} else {
|
||||
var query = $searchBox.val();
|
||||
if (lastQuery !== query) {
|
||||
currentResult = -1;
|
||||
if (query.length > 2) {
|
||||
self.search(query);
|
||||
} else {
|
||||
self.hideResults();
|
||||
}
|
||||
if(self.hasFilter(getCurrentApp())) {
|
||||
self.getFilter(getCurrentApp())(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$(document).keyup(function(event) {
|
||||
if(event.keyCode === 27) { //esc
|
||||
$searchBox.val('');
|
||||
if(self.hasFilter(getCurrentApp())) {
|
||||
self.getFilter(getCurrentApp())('');
|
||||
}
|
||||
self.hideResults();
|
||||
}
|
||||
});
|
||||
|
||||
$searchResults.on('click', 'tr.result', function (event) {
|
||||
var $row = $(this);
|
||||
var item = $row.data('result');
|
||||
if(self.hasHandler(item.type)){
|
||||
var result = self.getHandler(item.type)($row, result, event);
|
||||
$searchBox.val('');
|
||||
if(self.hasFilter(getCurrentApp())) {
|
||||
self.getFilter(getCurrentApp())('');
|
||||
}
|
||||
self.hideResults();
|
||||
return result;
|
||||
}
|
||||
});
|
||||
$searchResults.on('click', '#status', function (event) {
|
||||
event.preventDefault();
|
||||
scrollToResults();
|
||||
return false;
|
||||
});
|
||||
placeStatus();
|
||||
|
||||
OC.Plugins.attach('OCA.Search', this);
|
||||
}
|
||||
};
|
||||
OCA.Search = Search;
|
||||
})();
|
||||
|
||||
$(document).ready(function() {
|
||||
var $searchResults = $('<div id="searchresults" class="hidden"/>');
|
||||
$('#app-content')
|
||||
.append($searchResults)
|
||||
.find('.viewcontainer').css('min-height', 'initial');
|
||||
$searchResults.load(OC.webroot + '/search/templates/part.results.html', function () {
|
||||
OC.Search = new OCA.Search($('#searchbox'), $('#searchresults'));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated use get/setRenderer() instead
|
||||
*/
|
||||
OC.search.customResults = {};
|
||||
/**
|
||||
* @deprecated use get/setRenderer() instead
|
||||
*/
|
||||
OC.search.resultTypes = {};
|
|
@ -0,0 +1,13 @@
|
|||
<div id="status"></div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="template">
|
||||
<td class="icon"></td>
|
||||
<td class="info">
|
||||
<a class="link">
|
||||
<div class="name"></div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -1,15 +0,0 @@
|
|||
<div id="searchresults">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="template">
|
||||
<td class="type"></td>
|
||||
<td class="result">
|
||||
<a>
|
||||
<div class="name"></div>
|
||||
<div class="text"></div>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
Loading…
Reference in New Issue