/** * 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 * @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 * @param inApps * @param page * @param size */ this.search = function(query, inApps, page, size) { if (query) { OC.addStyle('core/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.addClass('status'); $status.html(t('core', 'Searching other places')+'search in progress'); // do the actual search query $.getJSON(OC.generateUrl('core/search'), {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'); // summaryAndStatusHeight is a constant var 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('
'); $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.addClass('emptycontent').removeClass('status'); $status.html(''); $status.append($('
').addClass('icon-search')); var error = t('core', 'No search results in other folders for {tag}{filter}{endtag}', {filter:lastQuery}); $status.append($('

').html(error.replace('{tag}', '').replace('{endtag}', ''))); } else { $status.removeClass('emptycontent').addClass('status'); $status.text(n('core', '{count} search result in another folder', '{count} search results in other folders', 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() { 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 () { 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(); } }); $(document).keydown(function(event) { if ((event.ctrlKey || event.metaKey) && // Ctrl or Command (OSX) !event.shiftKey && event.keyCode === 70 && // F self.hasFilter(getCurrentApp()) && // Search is enabled !$searchBox.is(':focus') // if searchbox is already focused do nothing (fallback to browser default) ) { $searchBox.focus(); $searchBox.select(); event.preventDefault(); } }); $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, item, 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); // hide search file if search is not enabled if(self.hasFilter(getCurrentApp())) { return; } if ($searchResults.length === 0) { $searchBox.hide(); } } }; OCA.Search = Search; })(); $(document).ready(function() { var $searchResults = $('#searchresults'); if ($searchResults.length > 0) { $searchResults.addClass('hidden'); $('#app-content') .find('.viewcontainer').css('min-height', 'initial'); $searchResults.load(OC.webroot + '/core/search/templates/part.results.html', function () { OC.Search = new OCA.Search($('#searchbox'), $('#searchresults')); }); } else { _.defer(function() { OC.Search = new OCA.Search($('#searchbox'), $('#searchresults')); }); } $('#searchbox + .icon-close-white').click(function() { OC.Search.clear(); $('#searchbox').focus(); }); }); /** * @deprecated use get/setRenderer() instead */ OC.search.customResults = {}; /** * @deprecated use get/setRenderer() instead */ OC.search.resultTypes = {};