nextcloud /
server
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | /** |
||
| 2 | * ownCloud - core |
||
| 3 | * |
||
| 4 | * This file is licensed under the Affero General Public License version 3 or |
||
| 5 | * later. See the COPYING file. |
||
| 6 | * |
||
| 7 | * @author Jörn Friedrich Dreyer <[email protected]> |
||
| 8 | * @copyright Jörn Friedrich Dreyer 2014 |
||
| 9 | */ |
||
| 10 | |||
| 11 | (function () { |
||
| 12 | /** |
||
| 13 | * @class OCA.Search |
||
| 14 | * @classdesc |
||
| 15 | * |
||
| 16 | * The Search class manages a search queries and their results |
||
| 17 | * |
||
| 18 | * @param $searchBox container element with existing markup for the #searchbox form |
||
| 19 | * @param $searchResults container element for results und status message |
||
| 20 | */ |
||
| 21 | var Search = function($searchBox, $searchResults) { |
||
| 22 | this.initialize($searchBox, $searchResults); |
||
| 23 | }; |
||
| 24 | /** |
||
| 25 | * @memberof OC |
||
| 26 | */ |
||
| 27 | Search.prototype = { |
||
| 28 | |||
| 29 | /** |
||
| 30 | * Initialize the search box |
||
| 31 | * |
||
| 32 | * @param $searchBox container element with existing markup for the #searchbox form |
||
| 33 | * @param $searchResults container element for results und status message |
||
| 34 | * @private |
||
| 35 | */ |
||
| 36 | initialize: function($searchBox, $searchResults) { |
||
| 37 | |||
| 38 | var self = this; |
||
| 39 | |||
| 40 | /** |
||
| 41 | * contains closures that are called to filter the current content |
||
| 42 | */ |
||
| 43 | var filters = {}; |
||
| 44 | this.setFilter = function(type, filter) { |
||
| 45 | filters[type] = filter; |
||
| 46 | }; |
||
| 47 | this.hasFilter = function(type) { |
||
| 48 | return typeof filters[type] !== 'undefined'; |
||
| 49 | }; |
||
| 50 | this.getFilter = function(type) { |
||
| 51 | return filters[type]; |
||
| 52 | }; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * contains closures that are called to render search results |
||
| 56 | */ |
||
| 57 | var renderers = {}; |
||
| 58 | this.setRenderer = function(type, renderer) { |
||
| 59 | renderers[type] = renderer; |
||
| 60 | }; |
||
| 61 | this.hasRenderer = function(type) { |
||
| 62 | return typeof renderers[type] !== 'undefined'; |
||
| 63 | }; |
||
| 64 | this.getRenderer = function(type) { |
||
| 65 | return renderers[type]; |
||
| 66 | }; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * contains closures that are called when a search result has been clicked |
||
| 70 | */ |
||
| 71 | var handlers = {}; |
||
| 72 | this.setHandler = function(type, handler) { |
||
| 73 | handlers[type] = handler; |
||
| 74 | }; |
||
| 75 | this.hasHandler = function(type) { |
||
| 76 | return typeof handlers[type] !== 'undefined'; |
||
| 77 | }; |
||
| 78 | this.getHandler = function(type) { |
||
| 79 | return handlers[type]; |
||
| 80 | }; |
||
| 81 | |||
| 82 | var currentResult = -1; |
||
| 83 | var lastQuery = ''; |
||
| 84 | var lastInApps = []; |
||
| 85 | var lastPage = 0; |
||
| 86 | var lastSize = 30; |
||
| 87 | var lastResults = []; |
||
| 88 | var timeoutID = null; |
||
| 89 | |||
| 90 | this.getLastQuery = function() { |
||
| 91 | return lastQuery; |
||
| 92 | }; |
||
| 93 | |||
| 94 | /** |
||
| 95 | * Do a search query and display the results |
||
| 96 | * @param {string} query the search query |
||
| 97 | * @param inApps |
||
| 98 | * @param page |
||
| 99 | * @param size |
||
| 100 | */ |
||
| 101 | this.search = function(query, inApps, page, size) { |
||
| 102 | if (query) { |
||
| 103 | OC.addStyle('core/search','results'); |
||
| 104 | if (typeof page !== 'number') { |
||
| 105 | page = 1; |
||
| 106 | } |
||
| 107 | if (typeof size !== 'number') { |
||
| 108 | size = 30; |
||
| 109 | } |
||
| 110 | if (typeof inApps !== 'object') { |
||
| 111 | var currentApp = getCurrentApp(); |
||
| 112 | if(currentApp) { |
||
| 113 | inApps = [currentApp]; |
||
| 114 | } else { |
||
| 115 | inApps = []; |
||
| 116 | } |
||
| 117 | } |
||
| 118 | // prevent double pages |
||
| 119 | if ($searchResults && query === lastQuery && page === lastPage && size === lastSize) { |
||
| 120 | return; |
||
| 121 | } |
||
| 122 | window.clearTimeout(timeoutID); |
||
| 123 | timeoutID = window.setTimeout(function() { |
||
| 124 | lastQuery = query; |
||
| 125 | lastInApps = inApps; |
||
| 126 | lastPage = page; |
||
| 127 | lastSize = size; |
||
| 128 | |||
| 129 | //show spinner |
||
| 130 | $searchResults.removeClass('hidden'); |
||
| 131 | $status.addClass('status'); |
||
| 132 | $status.html(t('core', 'Searching other places')+'<img class="spinner" alt="search in progress" src="'+OC.webroot+'/core/img/loading.gif" />'); |
||
|
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
|
|||
| 133 | |||
| 134 | // do the actual search query |
||
| 135 | $.getJSON(OC.generateUrl('core/search'), {query:query, inApps:inApps, page:page, size:size }, function(results) { |
||
|
0 ignored issues
–
show
|
|||
| 136 | lastResults = results; |
||
| 137 | if (page === 1) { |
||
| 138 | showResults(results); |
||
| 139 | } else { |
||
| 140 | addResults(results); |
||
| 141 | } |
||
| 142 | }); |
||
| 143 | }, 500); |
||
| 144 | } |
||
| 145 | }; |
||
| 146 | |||
| 147 | //TODO should be a core method, see https://github.com/owncloud/core/issues/12557 |
||
| 148 | function getCurrentApp() { |
||
| 149 | var content = document.getElementById('content'); |
||
| 150 | if (content) { |
||
| 151 | var classList = document.getElementById('content').className.split(/\s+/); |
||
| 152 | for (var i = 0; i < classList.length; i++) { |
||
| 153 | if (classList[i].indexOf('app-') === 0) { |
||
| 154 | return classList[i].substr(4); |
||
| 155 | } |
||
| 156 | } |
||
| 157 | } |
||
| 158 | return false; |
||
| 159 | } |
||
| 160 | |||
| 161 | var $status = $searchResults.find('#status'); |
||
| 162 | // summaryAndStatusHeight is a constant |
||
| 163 | var summaryAndStatusHeight = 118; |
||
| 164 | |||
| 165 | function isStatusOffScreen() { |
||
| 166 | return $searchResults.position() && |
||
| 167 | ($searchResults.position().top + summaryAndStatusHeight > window.innerHeight); |
||
| 168 | } |
||
| 169 | |||
| 170 | function placeStatus() { |
||
| 171 | if (isStatusOffScreen()) { |
||
| 172 | $status.addClass('fixed'); |
||
| 173 | } else { |
||
| 174 | $status.removeClass('fixed'); |
||
| 175 | } |
||
| 176 | } |
||
| 177 | function showResults(results) { |
||
| 178 | lastResults = results; |
||
| 179 | $searchResults.find('tr.result').remove(); |
||
| 180 | $searchResults.removeClass('hidden'); |
||
| 181 | addResults(results); |
||
| 182 | } |
||
| 183 | function addResults(results) { |
||
| 184 | var $template = $searchResults.find('tr.template'); |
||
| 185 | jQuery.each(results, function (i, result) { |
||
| 186 | var $row = $template.clone(); |
||
| 187 | $row.removeClass('template'); |
||
| 188 | $row.addClass('result'); |
||
| 189 | |||
| 190 | $row.data('result', result); |
||
| 191 | |||
| 192 | // generic results only have four attributes |
||
| 193 | $row.find('td.info div.name').text(result.name); |
||
| 194 | $row.find('td.info a').attr('href', result.link); |
||
| 195 | |||
| 196 | /** |
||
| 197 | * Give plugins the ability to customize the search results. see result.js for examples |
||
| 198 | */ |
||
| 199 | if (self.hasRenderer(result.type)) { |
||
| 200 | $row = self.getRenderer(result.type)($row, result); |
||
| 201 | } else { |
||
| 202 | // for backward compatibility add text div |
||
| 203 | $row.find('td.info div.name').addClass('result'); |
||
| 204 | $row.find('td.result div.name').after('<div class="text"></div>'); |
||
| 205 | $row.find('td.result div.text').text(result.name); |
||
| 206 | if (OC.search.customResults && OC.search.customResults[result.type]) { |
||
| 207 | OC.search.customResults[result.type]($row, result); |
||
| 208 | } |
||
| 209 | } |
||
| 210 | if ($row) { |
||
| 211 | $searchResults.find('tbody').append($row); |
||
| 212 | } |
||
| 213 | }); |
||
| 214 | var count = $searchResults.find('tr.result').length; |
||
| 215 | $status.data('count', count); |
||
| 216 | if (count === 0) { |
||
| 217 | $status.addClass('emptycontent').removeClass('status'); |
||
| 218 | $status.html(''); |
||
| 219 | $status.append('<div class="icon-search"></div>'); |
||
| 220 | $status.append('<h2>' + t('core', 'No search results in other folders') + '</h2>'); |
||
| 221 | } else { |
||
| 222 | $status.removeClass('emptycontent').addClass('status'); |
||
| 223 | $status.text(n('core', '{count} search result in another folder', '{count} search results in other folders', count, {count:count})); |
||
|
0 ignored issues
–
show
|
|||
| 224 | } |
||
| 225 | } |
||
| 226 | function renderCurrent() { |
||
| 227 | var result = $searchResults.find('tr.result')[currentResult]; |
||
| 228 | if (result) { |
||
| 229 | var $result = $(result); |
||
| 230 | var currentOffset = $('#app-content').scrollTop(); |
||
| 231 | $('#app-content').animate({ |
||
| 232 | // Scrolling to the top of the new result |
||
| 233 | scrollTop: currentOffset + $result.offset().top - $result.height() * 2 |
||
| 234 | }, { |
||
| 235 | duration: 100 |
||
| 236 | }); |
||
| 237 | $searchResults.find('tr.result.current').removeClass('current'); |
||
| 238 | $result.addClass('current'); |
||
| 239 | } |
||
| 240 | } |
||
| 241 | this.hideResults = function() { |
||
| 242 | $searchResults.addClass('hidden'); |
||
| 243 | $searchResults.find('tr.result').remove(); |
||
| 244 | lastQuery = false; |
||
| 245 | }; |
||
| 246 | this.clear = function() { |
||
| 247 | self.hideResults(); |
||
| 248 | if(self.hasFilter(getCurrentApp())) { |
||
| 249 | self.getFilter(getCurrentApp())(''); |
||
| 250 | } |
||
| 251 | $searchBox.val(''); |
||
| 252 | $searchBox.blur(); |
||
| 253 | }; |
||
| 254 | |||
| 255 | /** |
||
| 256 | * Event handler for when scrolling the list container. |
||
| 257 | * This appends/renders the next page of entries when reaching the bottom. |
||
| 258 | */ |
||
| 259 | function onScroll() { |
||
| 260 | if ($searchResults && lastQuery !== false && lastResults.length > 0) { |
||
| 261 | var resultsBottom = $searchResults.offset().top + $searchResults.height(); |
||
| 262 | var containerBottom = $searchResults.offsetParent().offset().top + |
||
| 263 | $searchResults.offsetParent().height(); |
||
| 264 | if ( resultsBottom < containerBottom * 1.2 ) { |
||
| 265 | self.search(lastQuery, lastInApps, lastPage + 1); |
||
| 266 | } |
||
| 267 | placeStatus(); |
||
| 268 | } |
||
| 269 | } |
||
| 270 | |||
| 271 | $('#app-content').on('scroll', _.bind(onScroll, this)); |
||
| 272 | |||
| 273 | /** |
||
| 274 | * scrolls the search results to the top |
||
| 275 | */ |
||
| 276 | function scrollToResults() { |
||
| 277 | setTimeout(function() { |
||
| 278 | if (isStatusOffScreen()) { |
||
| 279 | var newScrollTop = $('#app-content').prop('scrollHeight') - $searchResults.height(); |
||
| 280 | console.log('scrolling to ' + newScrollTop); |
||
| 281 | $('#app-content').animate({ |
||
| 282 | scrollTop: newScrollTop |
||
| 283 | }, { |
||
| 284 | duration: 100, |
||
| 285 | complete: function () { |
||
| 286 | scrollToResults(); |
||
| 287 | } |
||
| 288 | }); |
||
| 289 | } |
||
| 290 | }, 150); |
||
| 291 | } |
||
| 292 | |||
| 293 | $('form.searchbox').submit(function(event) { |
||
| 294 | event.preventDefault(); |
||
| 295 | }); |
||
| 296 | |||
| 297 | $searchBox.on('search', function () { |
||
| 298 | if($searchBox.val() === '') { |
||
| 299 | if(self.hasFilter(getCurrentApp())) { |
||
| 300 | self.getFilter(getCurrentApp())(''); |
||
| 301 | } |
||
| 302 | self.hideResults(); |
||
| 303 | } |
||
| 304 | }); |
||
| 305 | $searchBox.keyup(function(event) { |
||
| 306 | if (event.keyCode === 13) { //enter |
||
| 307 | if(currentResult > -1) { |
||
| 308 | var result = $searchResults.find('tr.result a')[currentResult]; |
||
| 309 | window.location = $(result).attr('href'); |
||
| 310 | } |
||
| 311 | } else if(event.keyCode === 38) { //up |
||
| 312 | if(currentResult > 0) { |
||
| 313 | currentResult--; |
||
| 314 | renderCurrent(); |
||
| 315 | } |
||
| 316 | } else if(event.keyCode === 40) { //down |
||
| 317 | if(lastResults.length > currentResult + 1){ |
||
| 318 | currentResult++; |
||
| 319 | renderCurrent(); |
||
| 320 | } |
||
| 321 | } else { |
||
| 322 | var query = $searchBox.val(); |
||
| 323 | if (lastQuery !== query) { |
||
| 324 | currentResult = -1; |
||
| 325 | if (query.length > 2) { |
||
| 326 | self.search(query); |
||
| 327 | } else { |
||
| 328 | self.hideResults(); |
||
| 329 | } |
||
| 330 | if(self.hasFilter(getCurrentApp())) { |
||
| 331 | self.getFilter(getCurrentApp())(query); |
||
| 332 | } |
||
| 333 | } |
||
| 334 | } |
||
| 335 | }); |
||
| 336 | $(document).keyup(function(event) { |
||
| 337 | if(event.keyCode === 27) { //esc |
||
| 338 | $searchBox.val(''); |
||
| 339 | if(self.hasFilter(getCurrentApp())) { |
||
| 340 | self.getFilter(getCurrentApp())(''); |
||
| 341 | } |
||
| 342 | self.hideResults(); |
||
| 343 | } |
||
| 344 | }); |
||
| 345 | |||
| 346 | $(document).keydown(function(event) { |
||
| 347 | if ((event.ctrlKey || event.metaKey) && // Ctrl or Command (OSX) |
||
| 348 | !event.shiftKey && |
||
| 349 | event.keyCode === 70 && // F |
||
| 350 | self.hasFilter(getCurrentApp()) && // Search is enabled |
||
| 351 | !$searchBox.is(':focus') // if searchbox is already focused do nothing (fallback to browser default) |
||
| 352 | ) { |
||
| 353 | $searchBox.focus(); |
||
| 354 | event.preventDefault(); |
||
| 355 | } |
||
| 356 | }); |
||
| 357 | |||
| 358 | $searchResults.on('click', 'tr.result', function (event) { |
||
| 359 | var $row = $(this); |
||
| 360 | var item = $row.data('result'); |
||
| 361 | if(self.hasHandler(item.type)){ |
||
| 362 | var result = self.getHandler(item.type)($row, item, event); |
||
| 363 | $searchBox.val(''); |
||
| 364 | if(self.hasFilter(getCurrentApp())) { |
||
| 365 | self.getFilter(getCurrentApp())(''); |
||
| 366 | } |
||
| 367 | self.hideResults(); |
||
| 368 | return result; |
||
| 369 | } |
||
| 370 | }); |
||
| 371 | $searchResults.on('click', '#status', function (event) { |
||
| 372 | event.preventDefault(); |
||
| 373 | scrollToResults(); |
||
| 374 | return false; |
||
| 375 | }); |
||
| 376 | placeStatus(); |
||
| 377 | |||
| 378 | OC.Plugins.attach('OCA.Search', this); |
||
| 379 | |||
| 380 | // hide search file if search is not enabled |
||
| 381 | if(self.hasFilter(getCurrentApp())) { |
||
| 382 | return; |
||
| 383 | } |
||
| 384 | if ($searchResults.length === 0) { |
||
| 385 | $searchBox.hide(); |
||
| 386 | } |
||
| 387 | } |
||
| 388 | }; |
||
| 389 | OCA.Search = Search; |
||
| 390 | })(); |
||
| 391 | |||
| 392 | $(document).ready(function() { |
||
| 393 | var $searchResults = $('#searchresults'); |
||
| 394 | if ($searchResults.length > 0) { |
||
| 395 | $searchResults.addClass('hidden'); |
||
| 396 | $('#app-content') |
||
| 397 | .find('.viewcontainer').css('min-height', 'initial'); |
||
| 398 | $searchResults.load(OC.webroot + '/core/search/templates/part.results.html', function () { |
||
| 399 | OC.Search = new OCA.Search($('#searchbox'), $('#searchresults')); |
||
| 400 | }); |
||
| 401 | } else { |
||
| 402 | _.defer(function() { |
||
| 403 | OC.Search = new OCA.Search($('#searchbox'), $('#searchresults')); |
||
| 404 | }); |
||
| 405 | } |
||
| 406 | }); |
||
| 407 | |||
| 408 | /** |
||
| 409 | * @deprecated use get/setRenderer() instead |
||
| 410 | */ |
||
| 411 | OC.search.customResults = {}; |
||
| 412 | /** |
||
| 413 | * @deprecated use get/setRenderer() instead |
||
| 414 | */ |
||
| 415 | OC.search.resultTypes = {}; |
||
| 416 |