Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>tags/v20.0.0beta1
@@ -7,7 +7,6 @@ import './commentstabview' | |||
import './commentsmodifymenu' | |||
import './filesplugin' | |||
import './activitytabviewplugin' | |||
import './search' | |||
import './vendor/Caret.js/dist/jquery.caret.min' | |||
import './vendor/At.js/dist/js/jquery.atwho.min' |
@@ -1,137 +0,0 @@ | |||
/* eslint-disable */ | |||
/* | |||
* Copyright (c) 2014 | |||
* | |||
* This file is licensed under the Affero General Public License version 3 | |||
* or later. | |||
* | |||
* See the COPYING-README file. | |||
* | |||
*/ | |||
(function(OC, OCA, $) { | |||
'use strict' | |||
/** | |||
* Construct a new FileActions instance | |||
* @constructs Files | |||
*/ | |||
var Comment = function() { | |||
this.initialize() | |||
} | |||
Comment.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) { | |||
return false | |||
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 hideNoFilterResults() { | |||
var $nofilterresults = $('.nofilterresults') | |||
if (!$nofilterresults.hasClass('hidden')) { | |||
$nofilterresults.addClass('hidden') | |||
} | |||
} | |||
/** | |||
* | |||
* @param {jQuery} $row | |||
* @param {Object} result | |||
* @param {int} result.id | |||
* @param {string} result.comment | |||
* @param {string} result.authorId | |||
* @param {string} result.authorName | |||
* @param {string} result.link | |||
* @param {string} result.fileName | |||
* @param {string} result.path | |||
* @returns {*} | |||
*/ | |||
this.renderCommentResult = 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>').addClass('path').text(result.path) | |||
var $avatar = $('<div>') | |||
$avatar.addClass('avatar') | |||
.css('display', 'inline-block') | |||
.css('vertical-align', 'middle') | |||
.css('margin', '0 5px 2px 3px') | |||
if (result.authorName) { | |||
$avatar.avatar(result.authorId, 21, undefined, false, undefined, result.authorName) | |||
} else { | |||
$avatar.avatar(result.authorId, 21) | |||
} | |||
$row.find('td.info div.name').after($pathDiv).text(result.comment).prepend($('<span>').addClass('path').css('margin-right', '5px').text(result.authorName)).prepend($avatar) | |||
$row.find('td.result a').attr('href', result.link) | |||
$row.find('td.icon') | |||
.css('background-image', 'url(' + OC.imagePath('core', 'actions/comment') + ')') | |||
.css('opacity', '.4') | |||
var dir = OC.dirname(result.path) | |||
// "result.path" does not include a leading "/", so "OC.dirname" | |||
// returns the path itself for files or folders in the root. | |||
if (dir === result.path) { | |||
dir = '/' | |||
} | |||
$row.find('td.info a').attr('href', | |||
OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', { dir: dir, scrollto: result.fileName }) | |||
) | |||
return $row | |||
} | |||
this.handleCommentClick = function($row, result, event) { | |||
if (self.fileAppLoaded() && self.fileList.id === 'files') { | |||
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.Core', this) | |||
}, | |||
attach: function(search) { | |||
search.setRenderer('comment', this.renderCommentResult.bind(this)) | |||
search.setHandler('comment', this.handleCommentClick.bind(this)) | |||
} | |||
} | |||
OCA.Search.comment = new Comment() | |||
})(OC, OCA, $) |
@@ -383,8 +383,6 @@ | |||
} | |||
}); | |||
this.updateSearch(); | |||
this.$fileList.on('click','td.filename>a.name, td.filesize, td.date', _.bind(this._onClickFile, this)); | |||
this.$fileList.on("droppedOnFavorites", function (event, file) { | |||
@@ -1126,7 +1124,6 @@ | |||
if ($targetDir !== undefined && e.which === 1) { | |||
e.preventDefault(); | |||
this.changeDirectory($targetDir, true, true); | |||
this.updateSearch(); | |||
} | |||
}, | |||
@@ -3260,17 +3257,7 @@ | |||
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 | |||
*/ |
@@ -24,7 +24,6 @@ | |||
"operationprogressbar.js", | |||
"recentfilelist.js", | |||
"recentplugin.js", | |||
"search.js", | |||
"semaphore.js", | |||
"sidebarpreviewmanager.js", | |||
"sidebarpreviewtext.js", |
@@ -1,176 +0,0 @@ | |||
/* | |||
* 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.substr(1, result.path.lastIndexOf("/"))); | |||
$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.MimeType.getIconUrl(result.mime) + ')'); | |||
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.substr(1, result.path.lastIndexOf("/"))); | |||
$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.MimeType.getIconUrl(result.mime) + ')'); | |||
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.handleFolderClick = function($row, result, event) { | |||
// open folder | |||
if (self.fileAppLoaded() && self.fileList.id === 'files') { | |||
self.fileList.changeDirectory(result.path); | |||
return false; | |||
} else { | |||
return true; | |||
} | |||
}; | |||
this.handleFileClick = function($row, result, event) { | |||
if (self.fileAppLoaded() && self.fileList.id === 'files') { | |||
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.Core', this); | |||
}, | |||
attach: function(search) { | |||
var self = this; | |||
search.setFilter('files', function (query) { | |||
if (self.fileAppLoaded()) { | |||
self.fileList.setFilter(query); | |||
if (query.length > 1) { | |||
//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('image', this.renderFileResult.bind(this)); | |||
search.setRenderer('audio', this.renderFileResult.bind(this)); | |||
search.setHandler('folder', this.handleFolderClick.bind(this)); | |||
search.setHandler(['file', 'audio', 'image'], this.handleFileClick.bind(this)); | |||
if (self.fileAppLoaded()) { | |||
// hide results when switching directory outside of search results | |||
$('#app-content').delegate('>div', 'changeDirectory', function() { | |||
search.clear(); | |||
}); | |||
} | |||
} | |||
}; | |||
OCA.Search.Files = Files; | |||
OCA.Search.files = new Files(); | |||
})(); |
@@ -1,103 +0,0 @@ | |||
/* | |||
* @copyright Copyright (c) 2018 Jan-Christoph Borchardt <hey@jancborchardt.net> | |||
* | |||
* @author John Molakvoæ <skjnldsv@protonmail.com> | |||
* @author Jan-Christoph Borchardt <hey@jancborchardt.net> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
#searchresults { | |||
overflow-x: hidden; | |||
text-overflow: ellipsis; | |||
padding-top: 51px; /* table row is 51px height */ | |||
box-sizing: border-box; | |||
z-index: 75; | |||
/* account for margin-bottom in files list */ | |||
margin-top: -250px; | |||
table { | |||
border-spacing: 0; | |||
padding-left: 41px; | |||
table-layout: fixed; | |||
top: 0; | |||
width: 100%; | |||
} | |||
tr { | |||
&.result { | |||
border-bottom: 1px solid var(--color-border); | |||
* { | |||
cursor: pointer; | |||
} | |||
} | |||
&.template { | |||
display: none; | |||
} | |||
&:hover, | |||
&.current { | |||
background-color: var(--color-primary-light); | |||
} | |||
td { | |||
padding: 5px 9px; | |||
font-style: normal; | |||
vertical-align: middle; | |||
border-bottom: none; | |||
&.icon { | |||
text-align: right; | |||
width: 40px; | |||
height: 40px; | |||
padding: 5px 0; | |||
background-position: right center; | |||
background-repeat: no-repeat; | |||
} | |||
.has-selection:not(.hidden) ~ &.icon { | |||
width: 50px; | |||
padding-left: 41px; | |||
background-size: 32px; | |||
} | |||
} | |||
.name, | |||
.text, | |||
.path { | |||
white-space: nowrap; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
.text { | |||
white-space: normal; | |||
color: var(--color-primary-text); | |||
} | |||
.path { | |||
opacity: 0.5; | |||
} | |||
.text em { | |||
color: var(--color-primary-text); | |||
font-weight: bold; | |||
opacity: 1; | |||
} | |||
} | |||
.hidden { | |||
display: none; | |||
} | |||
&.filter-empty { | |||
/* remove whitespace on bottom when no search results, to fix layout */ | |||
margin-top: 0 !important; | |||
} | |||
.status.summary .info { | |||
margin-left: 100px; | |||
} | |||
} |
@@ -1,197 +0,0 @@ | |||
/** | |||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com> | |||
* | |||
* @author John Molakvoæ <skjnldsv@protonmail.com> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
(function() { | |||
'use strict'; | |||
/** | |||
* @class Search | |||
* @memberOf OCA | |||
* | |||
* The Search class manages a search | |||
* | |||
* This is a simple method. Register a new search with your function as references. | |||
* The events will forward the search or reset directly | |||
* | |||
* @param {Function} searchCallback the function to run on a query search | |||
* @param {Function} resetCallback the function to run when the user reset the form | |||
*/ | |||
var Search = function(searchCallback, resetCallback) { | |||
this.initialize(searchCallback, resetCallback); | |||
}; | |||
/** | |||
* @memberof OC | |||
*/ | |||
Search.prototype = { | |||
/** | |||
* Initialize the search box | |||
* | |||
* @param {Function} searchCallback the function to run on a query search | |||
* @param {Function} resetCallback the function to run when the user reset the form | |||
*/ | |||
initialize: function(searchCallback, resetCallback) { | |||
var self = this; | |||
if (typeof searchCallback !== 'function') { | |||
throw new Error('searchCallback must be a function'); | |||
} | |||
if (typeof resetCallback !== 'function') { | |||
throw new Error('resetCallback must be a function'); | |||
} | |||
if (!document.getElementById('searchbox')) { | |||
throw new Error('searchBox not available'); | |||
} | |||
this.searchCallback = searchCallback; | |||
this.resetCallback = resetCallback; | |||
console.debug('New search handler registered'); | |||
/** | |||
* Search | |||
*/ | |||
this._search = function(event) { | |||
event.preventDefault(); | |||
var query = document.getElementById('searchbox').value; | |||
self.searchCallback(query); | |||
}; | |||
/** | |||
* Reset form | |||
*/ | |||
this._reset = function(event) { | |||
event.preventDefault(); | |||
document.getElementById('searchbox').value = ''; | |||
self.resetCallback(); | |||
}; | |||
/** | |||
* Submit event handler | |||
*/ | |||
this._submitHandler = function(event) { | |||
// Avoid form submit | |||
event.preventDefault(); | |||
_.debounce(self.search, 500); | |||
} | |||
/** | |||
* keyUp event handler for the escape key | |||
*/ | |||
this._keyUpHandler = function(event) { | |||
if (event.defaultPrevented) { | |||
return; | |||
} | |||
var key = event.key || event.keyCode; | |||
if ( | |||
document.getElementById('searchbox') === document.activeElement && | |||
document.getElementById('searchbox').value === '' | |||
) { | |||
if (key === 'Escape' || key === 'Esc' || key === 27) { | |||
document.activeElement.blur(); // remove focus on searchbox | |||
_.debounce(self.reset, 500); | |||
} | |||
} | |||
} | |||
this._searchDebounced = _.debounce(self._search, 500) | |||
this._resetDebounced = _.debounce(self._reset, 500) | |||
/** | |||
* keyDown event handler for the ctrl+F shortcut | |||
*/ | |||
this._keyDownHandler = function(event) { | |||
if (event.defaultPrevented) { | |||
return; | |||
} | |||
var key = event.key || event.keyCode; | |||
if (document.getElementById('searchbox') !== document.activeElement) { | |||
if ( | |||
(event.ctrlKey || event.metaKey) && // CTRL or mac CMD | |||
!event.shiftKey && // Not SHIFT | |||
(key === 'f' || key === 70) // F | |||
) { | |||
event.preventDefault(); | |||
document.getElementById('searchbox').focus(); | |||
document.getElementById('searchbox').select(); | |||
} | |||
} | |||
} | |||
// Show search | |||
document.getElementById('searchbox').style.display = 'block'; | |||
// Register input event | |||
document | |||
.getElementById('searchbox') | |||
.addEventListener('input', this._searchDebounced, true); | |||
document | |||
.querySelector('form.searchbox') | |||
.addEventListener('submit', this._submitHandler, true); | |||
// Register reset | |||
document | |||
.querySelector('form.searchbox') | |||
.addEventListener('reset', this._resetDebounced, true); | |||
// Register esc key shortcut reset if focused | |||
document.addEventListener('keyup', this._keyUpHandler); | |||
// Register ctrl+F key shortcut to focus | |||
document.addEventListener('keydown', this._keyDownHandler); | |||
}, | |||
unregister: function() { | |||
// Hide search | |||
document.getElementById('searchbox').style.display = 'none'; | |||
// Reset search | |||
document.getElementById('searchbox').value = ''; | |||
this.resetCallback(); | |||
// Unregister input event | |||
document | |||
.getElementById('searchbox') | |||
.removeEventListener('input', this._searchDebounced, true); | |||
document | |||
.querySelector('form.searchbox') | |||
.removeEventListener('submit', this._submitHandler, true); | |||
// Unregister reset | |||
document | |||
.querySelector('form.searchbox') | |||
.removeEventListener('reset', this._resetDebounced, true); | |||
// Unregister esc key shortcut reset if focused | |||
document.removeEventListener('keyup', this._keyUpHandler); | |||
// Unregister ctrl+F key shortcut to focus | |||
document.removeEventListener('keydown', this._keyDownHandler); | |||
console.debug('Search handler unregistered'); | |||
} | |||
}; | |||
OCA.Search = Search; | |||
})(); |
@@ -1,455 +0,0 @@ | |||
/* | |||
* @copyright Copyright (c) 2014 Jörn Friedrich Dreyer <jfd@owncloud.com> | |||
* | |||
* @author Jörn Friedrich Dreyer <jfd@owncloud.com> | |||
* @author John Molakvoæ <skjnldsv@protonmail.com> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
(function() { | |||
/** | |||
* @class OCA.Search.Core | |||
* @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) { | |||
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 emptycontent'); | |||
$status.html('<div class="icon-loading"></div><h2>' + t('core', 'Searching other places') + '</h2>'); | |||
// 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('<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.addClass('emptycontent').removeClass('status'); | |||
$status.html(''); | |||
$status.append($('<div>').addClass('icon-search')); | |||
var error = t('core', 'No search results in other folders for {tag}{filter}{endtag}', { filter: lastQuery }); | |||
$status.append($('<h2>').html(error.replace('{tag}', '<strong>').replace('{endtag}', '</strong>'))); | |||
} else { | |||
$status.removeClass('emptycontent').addClass('status summary'); | |||
$status.text(n('core','{count} search result in another folder','{count} search results in other folders', count,{ count: count })); | |||
$status.html('<span class="info">' + n( 'core', '{count} search result in another folder', '{count} search results in other folders', count, { count: count } ) + '</span>'); | |||
} | |||
} | |||
function renderCurrent() { | |||
var result = $searchResults.find('tr.result')[currentResult]; | |||
if (result) { | |||
var $result = $(result); | |||
var currentOffset = $(window).scrollTop(); | |||
$(window).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 | |||
) { | |||
if ($(window).scrollTop() + $(window).height() > $searchResults.height() - 300) { | |||
self.search(lastQuery, lastInApps, lastPage + 1); | |||
} | |||
placeStatus(); | |||
} | |||
} | |||
$(window).on('scroll', _.bind(onScroll, this)); // For desktop browser | |||
$("body").on('scroll', _.bind(onScroll, this)); // For mobile browser | |||
/** | |||
* scrolls the search results to the top | |||
*/ | |||
function scrollToResults() { | |||
setTimeout(function() { | |||
if (isStatusOffScreen()) { | |||
var newScrollTop = $(window).prop('scrollHeight') - $searchResults.height(); | |||
console.log('scrolling to ' + newScrollTop); | |||
$(window).animate( | |||
{ | |||
scrollTop: newScrollTop | |||
}, | |||
{ | |||
duration: 100, | |||
complete: function() { | |||
scrollToResults(); | |||
} | |||
} | |||
); | |||
} | |||
}, 150); | |||
} | |||
$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(); | |||
} | |||
} | |||
}); | |||
$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.Core', this); | |||
// Finally use default Search registration | |||
return new OCA.Search( | |||
// Search handler | |||
function(query) { | |||
if (lastQuery !== query) { | |||
currentResult = -1; | |||
if (query.length > 1) { | |||
self.search(query); | |||
} else { | |||
self.hideResults(); | |||
} | |||
if (self.hasFilter(getCurrentApp())) { | |||
self.getFilter(getCurrentApp())(query); | |||
} | |||
} | |||
}, | |||
// Reset handler | |||
function() { | |||
if ($searchBox.val() === '') { | |||
if (self.hasFilter(getCurrentApp())) { | |||
self.getFilter(getCurrentApp())(''); | |||
} | |||
self.hideResults(); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
OCA.Search.Core = Search; | |||
})(); | |||
window.addEventListener('DOMContentLoaded', function() { | |||
var $searchResults = $('#searchresults'); | |||
var $searchBox = $('#searchbox'); | |||
if ($searchResults.length > 0 && $searchBox.length > 0) { | |||
$searchResults.addClass('hidden'); | |||
$searchResults.html('<table>\n' + | |||
'\t<tbody>\n' + | |||
'\t\t<tr class="template">\n' + | |||
'\t\t\t<td class="icon"></td>\n' + | |||
'\t\t\t<td class="info">\n' + | |||
'\t\t\t\t<a class="link">\n' + | |||
'\t\t\t\t\t<div class="name"></div>\n' + | |||
'\t\t\t\t</a>\n' + | |||
'\t\t\t</td>\n' + | |||
'\t\t</tr>\n' + | |||
'\t</tbody>\n' + | |||
'</table>\n' + | |||
'<div id="status"><span></span></div>'); | |||
OC.Search = new OCA.Search.Core( | |||
$searchBox, | |||
$searchResults | |||
); | |||
} else { | |||
// check again later | |||
_.defer(function() { | |||
if ($searchResults.length > 0 && $searchBox.length > 0) { | |||
OC.Search = new OCA.Search.Core( | |||
$searchBox, | |||
$searchResults | |||
); | |||
} | |||
}); | |||
} | |||
}); | |||
/** | |||
* @deprecated use get/setRenderer() instead | |||
*/ | |||
OC.search.customResults = {}; | |||
/** | |||
* @deprecated use get/setRenderer() instead | |||
*/ | |||
OC.search.resultTypes = {}; |
@@ -103,7 +103,6 @@ import msg from './msg' | |||
import Notification from './notification' | |||
import PasswordConfirmation from './password-confirmation' | |||
import Plugins from './plugins' | |||
import search from './search' | |||
import { theme } from './theme' | |||
import Util from './util' | |||
import { debug } from './debug' | |||
@@ -249,7 +248,6 @@ export default { | |||
Notification, | |||
PasswordConfirmation, | |||
Plugins, | |||
search, | |||
theme, | |||
Util, | |||
debug, |
@@ -1,41 +0,0 @@ | |||
/** | |||
* @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program 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 program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
import OC from './index' | |||
/** | |||
* Do a search query and display the results | |||
* @param {string} query the search query | |||
*/ | |||
const search = function(query) { | |||
OC.Search.search(query, null, 0, 30) | |||
} | |||
/** | |||
* @namespace OC.search | |||
*/ | |||
search.customResults = {} | |||
/** | |||
* @deprecated use get/setFormatter() instead | |||
*/ | |||
search.resultTypes = {} | |||
export default search |
@@ -32,6 +32,18 @@ __webpack_nonce__ = btoa(getRequestToken()) | |||
// eslint-disable-next-line camelcase | |||
__webpack_public_path__ = generateFilePath('core', '', 'js/') | |||
// TODO: remove with nc22 | |||
if (!OCA.Search) { | |||
class Search { | |||
constructor() { | |||
console.warn('OCA.Search is deprecated. Please use the unified search API instead') | |||
} | |||
} | |||
OCA.Search = Search | |||
} | |||
Vue.mixin({ | |||
methods: { | |||
t, |
@@ -115,9 +115,6 @@ class OC_Template extends \OC\Template\Base { | |||
OC_Util::addTranslations('core', null, true); | |||
if (\OC::$server->getSystemConfig()->getValue('installed', false)) { | |||
OC_Util::addStyle('search', 'results'); | |||
OC_Util::addScript('search', 'search', true); | |||
OC_Util::addScript('search', 'searchprovider'); | |||
OC_Util::addScript('merged-template-prepend', null, true); | |||
OC_Util::addScript('dist/files_client', null, true); | |||
OC_Util::addScript('dist/files_fileinfo', null, true); |