diff options
author | Morris Jobke <hey@morrisjobke.de> | 2015-01-13 16:27:05 +0100 |
---|---|---|
committer | Morris Jobke <hey@morrisjobke.de> | 2015-01-13 16:27:05 +0100 |
commit | a83647928722eef039302971e36fd58e3238de25 (patch) | |
tree | ffdd866797224538625b5920c22f00d46e6e179b /core/search | |
parent | 55672280e060241aed453748f6072ef6ed91994d (diff) | |
download | nextcloud-server-a83647928722eef039302971e36fd58e3238de25.tar.gz nextcloud-server-a83647928722eef039302971e36fd58e3238de25.zip |
move search folder into core/
Diffstat (limited to 'core/search')
-rw-r--r-- | core/search/ajax/search.php | 58 | ||||
-rw-r--r-- | core/search/css/results.css | 100 | ||||
-rw-r--r-- | core/search/js/search.js | 379 | ||||
-rw-r--r-- | core/search/templates/part.results.html | 13 |
4 files changed, 550 insertions, 0 deletions
diff --git a/core/search/ajax/search.php b/core/search/ajax/search.php new file mode 100644 index 00000000000..2bafe65302b --- /dev/null +++ b/core/search/ajax/search.php @@ -0,0 +1,58 @@ +<?php + +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2010 Robin Appelman icewind1991@gmail.com +* +* 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/>. +* +*/ + +// Check if we are a user +\OCP\JSON::checkLoggedIn(); +\OCP\JSON::callCheck(); +\OC::$server->getSession()->close(); + +if (isset($_GET['query'])) { + $query = $_GET['query']; +} else { + $query = ''; +} +if (isset($_GET['inApps'])) { + $inApps = $_GET['inApps']; + if (is_string($inApps)) { + $inApps = array($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()->searchPaged($query, $inApps, $page, $size); + OC_JSON::encodedPrint($result); +} +else { + echo 'false'; +} diff --git a/core/search/css/results.css b/core/search/css/results.css new file mode 100644 index 00000000000..04f7b6dcb99 --- /dev/null +++ b/core/search/css/results.css @@ -0,0 +1,100 @@ +/* Copyright (c) 2011, Jan-Christoph Borchardt, http://jancborchardt.net + This file is licensed under the Affero General Public License version 3 or later. + See the COPYING-README file. */ + +#searchresults { + background-color:#fff; + overflow-x:hidden; + text-overflow:ellipsis; + padding-top: 65px; + box-sizing: border-box; + z-index:75; +} + +#searchresults.hidden { + display: none; +} +#searchresults * { + box-sizing: content-box; +} + +#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 #status .spinner { + height: 16px; + width: 16px; + vertical-align: middle; + margin-left: 10px; +} +#searchresults table { + border-spacing:0; + table-layout:fixed; + top:0; + width:100%; +} + +#searchresults td { + 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 .path { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +#searchresults .text { + 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 tr.result * { + cursor:pointer; +} + +#searchresults tr.current { + background-color:#ddd; +} diff --git a/core/search/js/search.js b/core/search/js/search.js new file mode 100644 index 00000000000..57de07bf7fd --- /dev/null +++ b/core/search/js/search.js @@ -0,0 +1,379 @@ +/** + * 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'); + // 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('<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 = {};
\ No newline at end of file diff --git a/core/search/templates/part.results.html b/core/search/templates/part.results.html new file mode 100644 index 00000000000..612d02c18f8 --- /dev/null +++ b/core/search/templates/part.results.html @@ -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> |