repo-switcher: new unified search box for filtering/accessing users, repos and repo groups.

- mainly driven by customer feedback
- two filter boxes on main page was confusing, and people constantly asked if grid filter can search recursive
- now we use one unified search on main nave accessible on every page
- it now also can search for users
This commit is contained in:
Marcin Kuzminski 2018-05-30 12:54:50 +02:00
parent 998cfa7aaa
commit 7c46071c51
14 changed files with 362 additions and 238 deletions

View file

@ -19,22 +19,44 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
def assert_and_get_content(result):
def assert_and_get_main_filter_content(result):
repos = []
groups = []
commits = []
users = []
for data_item in result:
assert data_item['id']
assert data_item['value']
assert data_item['value_display']
assert data_item['url']
if data_item['type'] == 'search':
assert data_item['value_display'].startswith('Full text search for:')
elif data_item['type'] == 'repo':
repos.append(data_item)
elif data_item['type'] == 'repo_group':
groups.append(data_item)
elif data_item['type'] == 'user':
users.append(data_item)
elif data_item['type'] == 'commit':
commits.append(data_item)
else:
raise Exception('invalid type `%s`' % data_item['type'])
return repos, groups, users, commits
def assert_and_get_repo_list_content(result):
repos = []
for data in result:
for data_item in data['children']:
assert data_item['id']
assert data_item['text']
assert data_item['url']
if data_item['type'] == 'repo':
repos.append(data_item)
elif data_item['type'] == 'group':
groups.append(data_item)
elif data_item['type'] == 'commit':
commits.append(data_item)
else:
raise Exception('invalid type %s' % data_item['type'])
return repos, groups, commits
return repos

View file

@ -22,7 +22,7 @@ import json
import pytest
from . import assert_and_get_content
from . import assert_and_get_main_filter_content
from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
from rhodecode.tests.fixture import Fixture
@ -103,19 +103,15 @@ class TestGotoSwitcherData(TestController):
RepoGroupModel().delete(el, force_delete=True)
Session().commit()
def test_returns_list_of_repos_and_groups(self, xhr_header):
def test_empty_query(self, xhr_header):
self.log_user()
response = self.app.get(
route_path('goto_switcher_data'),
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
result = json.loads(response.body)['suggestions']
repos, groups, commits = assert_and_get_content(result)
assert len(repos) == len(Repository.get_all())
assert len(groups) == len(RepoGroup.get_all())
assert len(commits) == 0
assert result == []
def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
self.log_user()
@ -124,14 +120,47 @@ class TestGotoSwitcherData(TestController):
route_path('goto_switcher_data'),
params={'query': 'abc'},
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
result = json.loads(response.body)['suggestions']
repos, groups, commits = assert_and_get_content(result)
repos, groups, users, commits = assert_and_get_main_filter_content(result)
assert len(repos) == 13
assert len(groups) == 5
assert len(users) == 0
assert len(commits) == 0
def test_returns_list_of_users_filtered(self, xhr_header):
self.log_user()
response = self.app.get(
route_path('goto_switcher_data'),
params={'query': 'user:admin'},
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['suggestions']
repos, groups, users, commits = assert_and_get_main_filter_content(result)
assert len(repos) == 0
assert len(groups) == 0
assert len(users) == 1
assert len(commits) == 0
def test_returns_list_of_commits_filtered(self, xhr_header):
self.log_user()
response = self.app.get(
route_path('goto_switcher_data'),
params={'query': 'commit:e8'},
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['suggestions']
repos, groups, users, commits = assert_and_get_main_filter_content(result)
assert len(repos) == 0
assert len(groups) == 0
assert len(users) == 0
assert len(commits) == 5
def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
self.log_user()
@ -139,13 +168,13 @@ class TestGotoSwitcherData(TestController):
route_path('goto_switcher_data'),
params={'query': 'abc'},
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
result = json.loads(response.body)['suggestions']
repos, groups, commits = assert_and_get_content(result)
repos, groups, users, commits = assert_and_get_main_filter_content(result)
test_repos = [x['text'] for x in repos[:4]]
test_repos = [x['value_display'] for x in repos[:4]]
assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
test_groups = [x['text'] for x in groups[:4]]
test_groups = [x['value_display'] for x in groups[:4]]
assert ['abc_repos', 'repos_abc',
'forked-abc', 'forked-abc/a'] == test_groups

View file

@ -20,7 +20,7 @@
import json
from . import assert_and_get_content
from . import assert_and_get_repo_list_content
from rhodecode.tests import TestController
from rhodecode.tests.fixture import Fixture
from rhodecode.model.db import Repository
@ -50,11 +50,9 @@ class TestRepoListData(TestController):
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
repos, groups, commits = assert_and_get_content(result)
repos = assert_and_get_repo_list_content(result)
assert len(repos) == len(Repository.get_all())
assert len(groups) == 0
assert len(commits) == 0
def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
self.log_user()
@ -65,12 +63,10 @@ class TestRepoListData(TestController):
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
repos, groups, commits = assert_and_get_content(result)
repos = assert_and_get_repo_list_content(result)
assert len(repos) == len(Repository.query().filter(
Repository.repo_name.ilike('%vcs_test_git%')).all())
assert len(groups) == 0
assert len(commits) == 0
def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header):
self.log_user()
@ -81,12 +77,10 @@ class TestRepoListData(TestController):
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
repos, groups, commits = assert_and_get_content(result)
repos = assert_and_get_repo_list_content(result)
assert len(repos) == len(Repository.query().filter(
Repository.repo_name.ilike('%vcs_test_git%')).all())
assert len(groups) == 0
assert len(commits) == 0
def test_returns_list_of_repos_non_ascii_query(self, xhr_header):
self.log_user()
@ -96,8 +90,6 @@ class TestRepoListData(TestController):
extra_environ=xhr_header, status=200)
result = json.loads(response.body)['results']
repos, groups, commits = assert_and_get_content(result)
repos = assert_and_get_repo_list_content(result)
assert len(repos) == 0
assert len(groups) == 0
assert len(commits) == 0

View file

@ -20,6 +20,7 @@
import re
import logging
import collections
from pyramid.view import view_config
@ -31,7 +32,7 @@ from rhodecode.lib.index import searcher_from_config
from rhodecode.lib.utils2 import safe_unicode, str2bool
from rhodecode.lib.ext_json import json
from rhodecode.model.db import (
func, or_, in_filter_generator, Repository, RepoGroup)
func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
from rhodecode.model.repo import RepoModel
from rhodecode.model.repo_group import RepoGroupModel
from rhodecode.model.scm import RepoGroupList, RepoList
@ -104,6 +105,7 @@ class HomeView(BaseAppView):
return {'suggestions': _user_groups}
def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
org_query = name_contains
allowed_ids = self._rhodecode_user.repo_acl_ids(
['repository.read', 'repository.write', 'repository.admin'],
cache=False, name_filter=name_contains) or [-1]
@ -125,20 +127,24 @@ class HomeView(BaseAppView):
Repository.repo_name.ilike(ilike_expression))
query = query.limit(limit)
acl_repo_iter = query
acl_iter = query
return [
{
'id': obj.repo_name,
'value': org_query,
'value_display': obj.repo_name,
'text': obj.repo_name,
'type': 'repo',
'obj': {'repo_type': obj.repo_type, 'private': obj.private,
'repo_id': obj.repo_id},
'repo_id': obj.repo_id,
'repo_type': obj.repo_type,
'private': obj.private,
'url': h.route_path('repo_summary', repo_name=obj.repo_name)
}
for obj in acl_repo_iter]
for obj in acl_iter]
def _get_repo_group_list(self, name_contains=None, limit=20):
org_query = name_contains
allowed_ids = self._rhodecode_user.repo_group_acl_ids(
['group.read', 'group.write', 'group.admin'],
cache=False, name_filter=name_contains) or [-1]
@ -157,20 +163,56 @@ class HomeView(BaseAppView):
RepoGroup.group_name.ilike(ilike_expression))
query = query.limit(limit)
acl_repo_iter = query
acl_iter = query
return [
{
'id': obj.group_name,
'text': obj.group_name,
'type': 'group',
'obj': {},
'value': org_query,
'value_display': obj.group_name,
'type': 'repo_group',
'url': h.route_path(
'repo_group_home', repo_group_name=obj.group_name)
}
for obj in acl_repo_iter]
for obj in acl_iter]
def _get_hash_commit_list(self, auth_user, query=None):
def _get_user_list(self, name_contains=None, limit=20):
org_query = name_contains
if not name_contains:
return []
name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
if len(name_contains) != 1:
return []
name_contains = name_contains[0]
query = User.query()\
.order_by(func.length(User.username))\
.order_by(User.username) \
.filter(User.username != User.DEFAULT_USER)
if name_contains:
ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
query = query.filter(
User.username.ilike(ilike_expression))
query = query.limit(limit)
acl_iter = query
return [
{
'id': obj.user_id,
'value': org_query,
'value_display': obj.username,
'type': 'user',
'icon_link': h.gravatar_url(obj.email, 30),
'url': h.route_path(
'user_profile', username=obj.username)
}
for obj in acl_iter]
def _get_hash_commit_list(self, auth_user, query):
org_query = query
if not query or len(query) < 3:
return []
@ -178,20 +220,21 @@ class HomeView(BaseAppView):
if len(commit_hashes) != 1:
return []
commit_hash_prefix = commit_hashes[0]
commit_hash = commit_hashes[0]
searcher = searcher_from_config(self.request.registry.settings)
result = searcher.search(
'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
'commit_id:%s*' % commit_hash, 'commit', auth_user,
raise_on_exc=False)
return [
{
'id': entry['commit_id'],
'text': entry['commit_id'],
'value': org_query,
'value_display': 'repo `{}` commit: {}'.format(
entry['repository'], entry['commit_id']),
'type': 'commit',
'obj': {'repo': entry['repository']},
'repo': entry['repository'],
'url': h.route_path(
'repo_commit',
repo_name=entry['repository'], commit_id=entry['commit_id'])
@ -235,41 +278,47 @@ class HomeView(BaseAppView):
_ = self.request.translate
query = self.request.GET.get('query')
log.debug('generating goto switcher list, query %s', query)
log.debug('generating main filter data, query %s', query)
default_search_val = 'Full text search for: `{}`'.format(query)
res = []
if not query:
return {'suggestions': res}
res.append({
'id': -1,
'value': query,
'value_display': default_search_val,
'type': 'search',
'url': h.route_path(
'search', _query={'q': query})
})
repo_groups = self._get_repo_group_list(query)
if repo_groups:
res.append({
'text': _('Groups'),
'children': repo_groups
})
for serialized_repo_group in repo_groups:
res.append(serialized_repo_group)
repos = self._get_repo_list(query)
if repos:
res.append({
'text': _('Repositories'),
'children': repos
})
for serialized_repo in repos:
res.append(serialized_repo)
# TODO(marcink): permissions for that ?
users = self._get_user_list(query)
for serialized_user in users:
res.append(serialized_user)
commits = self._get_hash_commit_list(c.auth_user, query)
if commits:
unique_repos = {}
unique_repos = collections.OrderedDict()
for commit in commits:
unique_repos.setdefault(commit['obj']['repo'], []
).append(commit)
repo_name = commit['repo']
unique_repos.setdefault(repo_name, []).append(commit)
for repo in unique_repos:
res.append({
'text': _('Commits in %(repo)s') % {'repo': repo},
'children': unique_repos[repo]
})
for repo, commits in unique_repos.items():
for commit in commits:
res.append(commit)
data = {
'more': False,
'results': res
}
return data
return {'suggestions': res}
def _get_groups_and_repos(self, repo_group_id=None):
# repo groups groups

View file

@ -766,7 +766,10 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
'id': obj['name'],
'text': obj['name'],
'type': 'repo',
'obj': obj['dbrepo']
'repo_id': obj['dbrepo']['repo_id'],
'repo_type': obj['dbrepo']['repo_type'],
'private': obj['dbrepo']['private'],
})
data = {

View file

@ -281,11 +281,9 @@
}
.navigation li.open {
.submenu,
.repo_switcher {
display: block;
}
.submenu {
display: block;
}
}
.navigation li:last-child .submenu {
@ -642,3 +640,45 @@ ul#context-pages {
}
}
.main_filter_help_box {
padding: 7px 7px;
border-top: 1px solid @grey4;
border-right: 1px solid @grey4;
border-bottom: 1px solid @grey4;
display: inline-block;
vertical-align: top;
margin-left: -5px;
background: @grey3;
}
.main_filter_input_box {
display: inline-block;
}
.main_filter_box {
margin: 9px 0 0 0;
}
#main_filter_help {
background: @grey3;
border: 1px solid black;
position: absolute;
white-space: pre-wrap;
z-index: 9999;
color: @nav-grey;
margin: 1px 7px;
padding: 0 2px;
}
.main_filter_input {
padding: 6px;
min-width: 220px;
color: @nav-grey;
background: @grey3;
}
.main_filter_input::placeholder {
color: @nav-grey;
opacity: 1;
}

View file

@ -228,7 +228,7 @@ mark,
clear: both;
float: left;
width: 100%;
margin: @pagepadding 0 @pagepadding;
margin: @pagepadding/2 0 @pagepadding;
.breadcrumbs{
float: left;

View file

@ -20,7 +20,7 @@ function setRCMouseBindings(repoName, repoLandingRev) {
// / open the quick filter
Mousetrap.bind(['/'], function(e) {
$('#repo_switcher').select2('open');
$('#main_filter').get(0).focus();
// return false to prevent default browser behavior
// and stop event from bubbling

View file

@ -131,7 +131,7 @@ var repoFilter = function(data) {
$.each(data.results[0].children, function() {
// replace name to ID for submision
this.id = this.obj.repo_id;
this.id = this.repo_id;
results.push(this);
});
@ -154,7 +154,7 @@ $("#scope_repo_id").select2({
dropdownAutoWidth: true,
containerCssClass: "drop-menu",
dropdownCssClass: "drop-menu-dropdown",
formatResult: formatResult,
formatResult: formatRepoResult,
query: $.debounce(250, function(query){
self = this;
var cacheKey = query.term;

View file

@ -165,8 +165,8 @@ var repoTypeFilter = function(data) {
$.each(data.results[0].children, function() {
// filter out the SAME repo, it cannot be used as fork of itself
if (this.obj.repo_id != currentRepoId) {
this.id = this.obj.repo_id;
if (this.repo_id != currentRepoId) {
this.id = this.repo_id;
results.push(this)
}
});
@ -181,7 +181,7 @@ $("#id_fork_of").select2({
dropdownAutoWidth: true,
containerCssClass: "drop-menu",
dropdownCssClass: "drop-menu-dropdown",
formatResult: formatResult,
formatResult: formatRepoResult,
query: $.debounce(250, function(query){
self = this;
var cacheKey = query.term;

View file

@ -129,7 +129,7 @@ var repoFilter = function(data) {
$.each(data.results[0].children, function() {
// replace name to ID for submision
this.id = this.obj.repo_id;
this.id = this.repo_id;
results.push(this);
});
@ -152,7 +152,7 @@ $("#scope_repo_id").select2({
dropdownAutoWidth: true,
containerCssClass: "drop-menu",
dropdownCssClass: "drop-menu-dropdown",
formatResult: formatResult,
formatResult: formatRepoResult,
query: $.debounce(250, function(query){
self = this;
var cacheKey = query.term;

View file

@ -226,7 +226,7 @@
<!--- CONTEXT BAR -->
<div id="context-bar">
<div class="wrapper">
<ul id="context-pages" class="horizontal-list navigation">
<ul id="context-pages" class="navigation horizontal-list">
<li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
<li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
<li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
@ -386,10 +386,30 @@
return ""
%>
<ul id="quick" class="main_nav navigation horizontal-list">
<!-- repo switcher -->
<li class="${is_active('repositories')} repo_switcher_li has_select2">
<input id="repo_switcher" name="repo_switcher" type="hidden">
</li>
## Main filter
<li>
<div class="menulabel main_filter_box">
<div class="main_filter_input_box">
<input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
</div>
<div class="main_filter_help_box">
<a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
</div>
</div>
<div id="main_filter_help" style="display: none">
Use '/' key to quickly access this field.
Enter name of repository, or repository group for quick search.
Prefix query to allow special search:
For usernames, e.g user:admin
For commit hash/id, e.g commit:efced4
</div>
</li>
## ROOT MENU
%if c.rhodecode_user.username != h.DEFAULT_USER:
@ -432,6 +452,9 @@
c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
</li>
% endif
## render extra user menu
${usermenu(active=(active=='my_account'))}
% if c.debug_style:
<li class="${is_active('debug_style')}">
<a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
@ -439,102 +462,127 @@
</a>
</li>
% endif
## render extra user menu
${usermenu(active=(active=='my_account'))}
</ul>
<script type="text/javascript">
var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
/*format the look of items in the list*/
var format = function(state, escapeMarkup){
if (!state.id){
return state.text; // optgroup
}
var obj_dict = state.obj;
var tmpl = '';
var formatRepoResult = function(result, container, query, escapeMarkup) {
return function(data, escapeMarkup) {
if (!data.repo_id){
return data.text; // optgroup text Repositories
}
if(obj_dict && state.type == 'repo'){
if(obj_dict['repo_type'] === 'hg'){
tmpl += '<i class="icon-hg"></i> ';
var tmpl = '';
var repoType = data['repo_type'];
var repoName = data['text'];
if(data && data.type == 'repo'){
if(repoType === 'hg'){
tmpl += '<i class="icon-hg"></i> ';
}
else if(repoType === 'git'){
tmpl += '<i class="icon-git"></i> ';
}
else if(repoType === 'svn'){
tmpl += '<i class="icon-svn"></i> ';
}
if(data['private']){
tmpl += '<i class="icon-lock" ></i> ';
}
else if(visualShowPublicIcon){
tmpl += '<i class="icon-unlock-alt"></i> ';
}
}
else if(obj_dict['repo_type'] === 'git'){
tmpl += '<i class="icon-git"></i> ';
}
else if(obj_dict['repo_type'] === 'svn'){
tmpl += '<i class="icon-svn"></i> ';
}
if(obj_dict['private']){
tmpl += '<i class="icon-lock" ></i> ';
}
else if(visual_show_public_icon){
tmpl += '<i class="icon-unlock-alt"></i> ';
}
}
if(obj_dict && state.type == 'commit') {
tmpl += '<i class="icon-tag"></i>';
}
if(obj_dict && state.type == 'group'){
tmpl += '<i class="icon-folder-close"></i> ';
}
tmpl += escapeMarkup(state.text);
return tmpl;
tmpl += escapeMarkup(repoName);
return tmpl;
}(result, escapeMarkup);
};
var formatResult = function(result, container, query, escapeMarkup) {
return format(result, escapeMarkup);
var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
if (value.split(':').length === 2) {
value = value.split(':')[1]
}
var searchType = data['type'];
var valueDisplay = data['value_display'];
var escapeRegExChars = function (value) {
return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};
var pattern = '(' + escapeRegExChars(value) + ')';
// highlight match
valueDisplay = Select2.util.escapeMarkup(valueDisplay);
valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
var icon = '';
if (searchType === 'search') {
icon += '<i class="icon-more"></i> ';
}
else if (searchType === 'repo') {
if (data['repo_type'] === 'hg') {
icon += '<i class="icon-hg"></i> ';
}
else if (data['repo_type'] === 'git') {
icon += '<i class="icon-git"></i> ';
}
else if (data['repo_type'] === 'svn') {
icon += '<i class="icon-svn"></i> ';
}
if (data['private']) {
icon += '<i class="icon-lock" ></i> ';
}
else if (visualShowPublicIcon) {
icon += '<i class="icon-unlock-alt"></i> ';
}
}
else if (searchType === 'repo_group') {
icon += '<i class="icon-folder-close"></i> ';
}
else if (searchType === 'user') {
icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
}
else if (searchType === 'commit') {
icon += '<i class="icon-tag"></i>';
}
var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
return tmpl.format(icon, valueDisplay);
};
var formatSelection = function(data, container, escapeMarkup) {
return format(data, escapeMarkup);
var handleSelect = function(element, suggestion) {
window.location = suggestion['url'];
};
var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
if (queryLowerCase.split(':').length === 2) {
queryLowerCase = queryLowerCase.split(':')[1]
}
return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
};
$("#repo_switcher").select2({
cachedDataSource: {},
minimumInputLength: 2,
placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
dropdownAutoWidth: true,
formatResult: formatResult,
formatSelection: formatSelection,
containerCssClass: "repo-switcher",
dropdownCssClass: "repo-switcher-dropdown",
escapeMarkup: function(m){
// don't escape our custom placeholder
if(m.substr(0,23) == '<div class="menulabel">'){
return m;
}
return Select2.util.escapeMarkup(m);
},
query: $.debounce(250, function(query){
self = this;
var cacheKey = query.term;
var cachedData = self.cachedDataSource[cacheKey];
if (cachedData) {
query.callback({results: cachedData.results});
} else {
$.ajax({
url: pyroutes.url('goto_switcher_data'),
data: {'query': query.term},
dataType: 'json',
type: 'GET',
success: function(data) {
self.cachedDataSource[cacheKey] = data;
query.callback({results: data.results});
},
error: function(data, textStatus, errorThrown) {
alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
}
})
}
})
$('#main_filter').autocomplete({
serviceUrl: pyroutes.url('goto_switcher_data'),
minChars:2,
maxHeight:400,
deferRequestBy: 300, //miliseconds
tabDisabled: true,
autoSelectFirst: true,
formatResult: autocompleteMainFilterFormatResult,
lookupFilter: autocompleteMainFilterResult,
onSelect: function(element, suggestion){
handleSelect(element, suggestion);
return false;
}
});
$("#repo_switcher").on('select2-selecting', function(e){
e.preventDefault();
window.location = e.choice.url;
});
showMainFilterBox = function () {
$('#main_filter_help').toggle();
}
</script>
<script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
@ -557,7 +605,7 @@
</tr>
<%
elems = [
('/', 'Open quick search box'),
('/', 'Use quick search box'),
('g h', 'Goto home page'),
('g g', 'Goto my private gists page'),
('g G', 'Goto my public gists page'),
@ -611,3 +659,4 @@
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View file

@ -5,9 +5,8 @@
<!-- box / title -->
<div class="title">
<div class="block-left breadcrumbs">
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
${self.breadcrumbs()}
<span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
<span id="match_container" style="display:none"><span id="match_count">0</span> ${_('matches')}</span>
</div>
%if c.rhodecode_user.username != h.DEFAULT_USER:
<div class="block-right">
@ -137,65 +136,6 @@
});
% endif
var getDatatableCount = function() {
var reposCount = 0;
var reposCountTotal = 0;
% if c.repos_data != '[]':
var pageInfo = $('#repo_list_table').dataTable().api().page.info();
var reposCount = pageInfo.recordsDisplay;
var reposCountTotal = pageInfo.recordsTotal;
% endif
var repoGroupsCount = 0;
var repoGroupsCountTotal = 0;
% if c.repo_groups_data != '[]':
var pageInfo = $('#group_list_table').dataTable().api().page.info();
var repoGroupsCount = pageInfo.recordsDisplay;
var repoGroupsCountTotal = pageInfo.recordsTotal;
% endif
if (repoGroupsCount !== repoGroupsCountTotal) {
$('#match_count').text(reposCount + repoGroupsCount);
}
if (reposCount !== reposCountTotal) {
$('#match_container').show();
}
if ($('#q_filter').val() === '') {
$('#match_container').hide();
}
};
// update the counter when doing search
$('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
getDatatableCount();
});
// filter, filter both grids
$('#q_filter').on( 'keyup', function () {
% if c.repo_groups_data != '[]':
var repo_group_api = $('#group_list_table').dataTable().api();
repo_group_api
.columns( 0 )
.search( this.value )
.draw();
% endif
% if c.repos_data != '[]':
var repo_api = $('#repo_list_table').dataTable().api();
repo_api
.columns( 0 )
.search( this.value )
.draw();
% endif
});
// refilter table if page load via back button
$("#q_filter").trigger('keyup');
});
</script>
</%def>

View file

@ -465,7 +465,7 @@ $(function(){
query: $.debounce(250, function(query) {
queryTargetRepo(this, query);
}),
formatResult: formatResult
formatResult: formatRepoResult
});
sourceRefSelect2.initRef();