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:
parent
998cfa7aaa
commit
7c46071c51
14 changed files with 362 additions and 238 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ mark,
|
|||
clear: both;
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: @pagepadding 0 @pagepadding;
|
||||
margin: @pagepadding/2 0 @pagepadding;
|
||||
|
||||
.breadcrumbs{
|
||||
float: left;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -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">» <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>
|
||||
|
|
|
|||
|
|
@ -465,7 +465,7 @@ $(function(){
|
|||
query: $.debounce(250, function(query) {
|
||||
queryTargetRepo(this, query);
|
||||
}),
|
||||
formatResult: formatResult
|
||||
formatResult: formatRepoResult
|
||||
});
|
||||
|
||||
sourceRefSelect2.initRef();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue