##// END OF EJS Templates
repo-switcher: new unified search box for filtering/accessing users, repos and repo groups....
marcink -
r2774:a8ecef4e default
parent child Browse files
Show More
@@ -19,22 +19,44 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 def assert_and_get_content(result):
22 def assert_and_get_main_filter_content(result):
23 repos = []
23 repos = []
24 groups = []
24 groups = []
25 commits = []
25 commits = []
26 users = []
27 for data_item in result:
28 assert data_item['id']
29 assert data_item['value']
30 assert data_item['value_display']
31 assert data_item['url']
32
33 if data_item['type'] == 'search':
34 assert data_item['value_display'].startswith('Full text search for:')
35 elif data_item['type'] == 'repo':
36 repos.append(data_item)
37 elif data_item['type'] == 'repo_group':
38 groups.append(data_item)
39 elif data_item['type'] == 'user':
40 users.append(data_item)
41 elif data_item['type'] == 'commit':
42 commits.append(data_item)
43 else:
44 raise Exception('invalid type `%s`' % data_item['type'])
45
46 return repos, groups, users, commits
47
48
49 def assert_and_get_repo_list_content(result):
50 repos = []
26 for data in result:
51 for data in result:
27 for data_item in data['children']:
52 for data_item in data['children']:
28 assert data_item['id']
53 assert data_item['id']
29 assert data_item['text']
54 assert data_item['text']
30 assert data_item['url']
55 assert data_item['url']
56
31 if data_item['type'] == 'repo':
57 if data_item['type'] == 'repo':
32 repos.append(data_item)
58 repos.append(data_item)
33 elif data_item['type'] == 'group':
34 groups.append(data_item)
35 elif data_item['type'] == 'commit':
36 commits.append(data_item)
37 else:
59 else:
38 raise Exception('invalid type %s' % data_item['type'])
60 raise Exception('invalid type %s' % data_item['type'])
39
61
40 return repos, groups, commits No newline at end of file
62 return repos
@@ -22,7 +22,7 b' import json'
22
22
23 import pytest
23 import pytest
24
24
25 from . import assert_and_get_content
25 from . import assert_and_get_main_filter_content
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
@@ -103,19 +103,15 b' class TestGotoSwitcherData(TestControlle'
103 RepoGroupModel().delete(el, force_delete=True)
103 RepoGroupModel().delete(el, force_delete=True)
104 Session().commit()
104 Session().commit()
105
105
106 def test_returns_list_of_repos_and_groups(self, xhr_header):
106 def test_empty_query(self, xhr_header):
107 self.log_user()
107 self.log_user()
108
108
109 response = self.app.get(
109 response = self.app.get(
110 route_path('goto_switcher_data'),
110 route_path('goto_switcher_data'),
111 extra_environ=xhr_header, status=200)
111 extra_environ=xhr_header, status=200)
112 result = json.loads(response.body)['results']
112 result = json.loads(response.body)['suggestions']
113
114 repos, groups, commits = assert_and_get_content(result)
115
113
116 assert len(repos) == len(Repository.get_all())
114 assert result == []
117 assert len(groups) == len(RepoGroup.get_all())
118 assert len(commits) == 0
119
115
120 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
116 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
121 self.log_user()
117 self.log_user()
@@ -124,14 +120,47 b' class TestGotoSwitcherData(TestControlle'
124 route_path('goto_switcher_data'),
120 route_path('goto_switcher_data'),
125 params={'query': 'abc'},
121 params={'query': 'abc'},
126 extra_environ=xhr_header, status=200)
122 extra_environ=xhr_header, status=200)
127 result = json.loads(response.body)['results']
123 result = json.loads(response.body)['suggestions']
128
124
129 repos, groups, commits = assert_and_get_content(result)
125 repos, groups, users, commits = assert_and_get_main_filter_content(result)
130
126
131 assert len(repos) == 13
127 assert len(repos) == 13
132 assert len(groups) == 5
128 assert len(groups) == 5
129 assert len(users) == 0
133 assert len(commits) == 0
130 assert len(commits) == 0
134
131
132 def test_returns_list_of_users_filtered(self, xhr_header):
133 self.log_user()
134
135 response = self.app.get(
136 route_path('goto_switcher_data'),
137 params={'query': 'user:admin'},
138 extra_environ=xhr_header, status=200)
139 result = json.loads(response.body)['suggestions']
140
141 repos, groups, users, commits = assert_and_get_main_filter_content(result)
142
143 assert len(repos) == 0
144 assert len(groups) == 0
145 assert len(users) == 1
146 assert len(commits) == 0
147
148 def test_returns_list_of_commits_filtered(self, xhr_header):
149 self.log_user()
150
151 response = self.app.get(
152 route_path('goto_switcher_data'),
153 params={'query': 'commit:e8'},
154 extra_environ=xhr_header, status=200)
155 result = json.loads(response.body)['suggestions']
156
157 repos, groups, users, commits = assert_and_get_main_filter_content(result)
158
159 assert len(repos) == 0
160 assert len(groups) == 0
161 assert len(users) == 0
162 assert len(commits) == 5
163
135 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
164 def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header):
136 self.log_user()
165 self.log_user()
137
166
@@ -139,13 +168,13 b' class TestGotoSwitcherData(TestControlle'
139 route_path('goto_switcher_data'),
168 route_path('goto_switcher_data'),
140 params={'query': 'abc'},
169 params={'query': 'abc'},
141 extra_environ=xhr_header, status=200)
170 extra_environ=xhr_header, status=200)
142 result = json.loads(response.body)['results']
171 result = json.loads(response.body)['suggestions']
143
172
144 repos, groups, commits = assert_and_get_content(result)
173 repos, groups, users, commits = assert_and_get_main_filter_content(result)
145
174
146 test_repos = [x['text'] for x in repos[:4]]
175 test_repos = [x['value_display'] for x in repos[:4]]
147 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
176 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
148
177
149 test_groups = [x['text'] for x in groups[:4]]
178 test_groups = [x['value_display'] for x in groups[:4]]
150 assert ['abc_repos', 'repos_abc',
179 assert ['abc_repos', 'repos_abc',
151 'forked-abc', 'forked-abc/a'] == test_groups
180 'forked-abc', 'forked-abc/a'] == test_groups
@@ -20,7 +20,7 b''
20
20
21 import json
21 import json
22
22
23 from . import assert_and_get_content
23 from . import assert_and_get_repo_list_content
24 from rhodecode.tests import TestController
24 from rhodecode.tests import TestController
25 from rhodecode.tests.fixture import Fixture
25 from rhodecode.tests.fixture import Fixture
26 from rhodecode.model.db import Repository
26 from rhodecode.model.db import Repository
@@ -50,11 +50,9 b' class TestRepoListData(TestController):'
50 extra_environ=xhr_header, status=200)
50 extra_environ=xhr_header, status=200)
51 result = json.loads(response.body)['results']
51 result = json.loads(response.body)['results']
52
52
53 repos, groups, commits = assert_and_get_content(result)
53 repos = assert_and_get_repo_list_content(result)
54
54
55 assert len(repos) == len(Repository.get_all())
55 assert len(repos) == len(Repository.get_all())
56 assert len(groups) == 0
57 assert len(commits) == 0
58
56
59 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
57 def test_returns_list_of_repos_and_groups_filtered(self, xhr_header):
60 self.log_user()
58 self.log_user()
@@ -65,12 +63,10 b' class TestRepoListData(TestController):'
65 extra_environ=xhr_header, status=200)
63 extra_environ=xhr_header, status=200)
66 result = json.loads(response.body)['results']
64 result = json.loads(response.body)['results']
67
65
68 repos, groups, commits = assert_and_get_content(result)
66 repos = assert_and_get_repo_list_content(result)
69
67
70 assert len(repos) == len(Repository.query().filter(
68 assert len(repos) == len(Repository.query().filter(
71 Repository.repo_name.ilike('%vcs_test_git%')).all())
69 Repository.repo_name.ilike('%vcs_test_git%')).all())
72 assert len(groups) == 0
73 assert len(commits) == 0
74
70
75 def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header):
71 def test_returns_list_of_repos_and_groups_filtered_with_type(self, xhr_header):
76 self.log_user()
72 self.log_user()
@@ -81,12 +77,10 b' class TestRepoListData(TestController):'
81 extra_environ=xhr_header, status=200)
77 extra_environ=xhr_header, status=200)
82 result = json.loads(response.body)['results']
78 result = json.loads(response.body)['results']
83
79
84 repos, groups, commits = assert_and_get_content(result)
80 repos = assert_and_get_repo_list_content(result)
85
81
86 assert len(repos) == len(Repository.query().filter(
82 assert len(repos) == len(Repository.query().filter(
87 Repository.repo_name.ilike('%vcs_test_git%')).all())
83 Repository.repo_name.ilike('%vcs_test_git%')).all())
88 assert len(groups) == 0
89 assert len(commits) == 0
90
84
91 def test_returns_list_of_repos_non_ascii_query(self, xhr_header):
85 def test_returns_list_of_repos_non_ascii_query(self, xhr_header):
92 self.log_user()
86 self.log_user()
@@ -96,8 +90,6 b' class TestRepoListData(TestController):'
96 extra_environ=xhr_header, status=200)
90 extra_environ=xhr_header, status=200)
97 result = json.loads(response.body)['results']
91 result = json.loads(response.body)['results']
98
92
99 repos, groups, commits = assert_and_get_content(result)
93 repos = assert_and_get_repo_list_content(result)
100
94
101 assert len(repos) == 0
95 assert len(repos) == 0
102 assert len(groups) == 0
103 assert len(commits) == 0
@@ -20,6 +20,7 b''
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import collections
23
24
24 from pyramid.view import view_config
25 from pyramid.view import view_config
25
26
@@ -31,7 +32,7 b' from rhodecode.lib.index import searcher'
31 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.utils2 import safe_unicode, str2bool
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import (
34 from rhodecode.model.db import (
34 func, or_, in_filter_generator, Repository, RepoGroup)
35 func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import RepoGroupList, RepoList
38 from rhodecode.model.scm import RepoGroupList, RepoList
@@ -104,6 +105,7 b' class HomeView(BaseAppView):'
104 return {'suggestions': _user_groups}
105 return {'suggestions': _user_groups}
105
106
106 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
107 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
108 org_query = name_contains
107 allowed_ids = self._rhodecode_user.repo_acl_ids(
109 allowed_ids = self._rhodecode_user.repo_acl_ids(
108 ['repository.read', 'repository.write', 'repository.admin'],
110 ['repository.read', 'repository.write', 'repository.admin'],
109 cache=False, name_filter=name_contains) or [-1]
111 cache=False, name_filter=name_contains) or [-1]
@@ -125,20 +127,24 b' class HomeView(BaseAppView):'
125 Repository.repo_name.ilike(ilike_expression))
127 Repository.repo_name.ilike(ilike_expression))
126 query = query.limit(limit)
128 query = query.limit(limit)
127
129
128 acl_repo_iter = query
130 acl_iter = query
129
131
130 return [
132 return [
131 {
133 {
132 'id': obj.repo_name,
134 'id': obj.repo_name,
135 'value': org_query,
136 'value_display': obj.repo_name,
133 'text': obj.repo_name,
137 'text': obj.repo_name,
134 'type': 'repo',
138 'type': 'repo',
135 'obj': {'repo_type': obj.repo_type, 'private': obj.private,
139 'repo_id': obj.repo_id,
136 'repo_id': obj.repo_id},
140 'repo_type': obj.repo_type,
141 'private': obj.private,
137 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
142 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
138 }
143 }
139 for obj in acl_repo_iter]
144 for obj in acl_iter]
140
145
141 def _get_repo_group_list(self, name_contains=None, limit=20):
146 def _get_repo_group_list(self, name_contains=None, limit=20):
147 org_query = name_contains
142 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
148 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
143 ['group.read', 'group.write', 'group.admin'],
149 ['group.read', 'group.write', 'group.admin'],
144 cache=False, name_filter=name_contains) or [-1]
150 cache=False, name_filter=name_contains) or [-1]
@@ -157,20 +163,56 b' class HomeView(BaseAppView):'
157 RepoGroup.group_name.ilike(ilike_expression))
163 RepoGroup.group_name.ilike(ilike_expression))
158 query = query.limit(limit)
164 query = query.limit(limit)
159
165
160 acl_repo_iter = query
166 acl_iter = query
161
167
162 return [
168 return [
163 {
169 {
164 'id': obj.group_name,
170 'id': obj.group_name,
165 'text': obj.group_name,
171 'value': org_query,
166 'type': 'group',
172 'value_display': obj.group_name,
167 'obj': {},
173 'type': 'repo_group',
168 'url': h.route_path(
174 'url': h.route_path(
169 'repo_group_home', repo_group_name=obj.group_name)
175 'repo_group_home', repo_group_name=obj.group_name)
170 }
176 }
171 for obj in acl_repo_iter]
177 for obj in acl_iter]
178
179 def _get_user_list(self, name_contains=None, limit=20):
180 org_query = name_contains
181 if not name_contains:
182 return []
183
184 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
185 if len(name_contains) != 1:
186 return []
187 name_contains = name_contains[0]
188
189 query = User.query()\
190 .order_by(func.length(User.username))\
191 .order_by(User.username) \
192 .filter(User.username != User.DEFAULT_USER)
172
193
173 def _get_hash_commit_list(self, auth_user, query=None):
194 if name_contains:
195 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
196 query = query.filter(
197 User.username.ilike(ilike_expression))
198 query = query.limit(limit)
199
200 acl_iter = query
201
202 return [
203 {
204 'id': obj.user_id,
205 'value': org_query,
206 'value_display': obj.username,
207 'type': 'user',
208 'icon_link': h.gravatar_url(obj.email, 30),
209 'url': h.route_path(
210 'user_profile', username=obj.username)
211 }
212 for obj in acl_iter]
213
214 def _get_hash_commit_list(self, auth_user, query):
215 org_query = query
174 if not query or len(query) < 3:
216 if not query or len(query) < 3:
175 return []
217 return []
176
218
@@ -178,20 +220,21 b' class HomeView(BaseAppView):'
178
220
179 if len(commit_hashes) != 1:
221 if len(commit_hashes) != 1:
180 return []
222 return []
181
223 commit_hash = commit_hashes[0]
182 commit_hash_prefix = commit_hashes[0]
183
224
184 searcher = searcher_from_config(self.request.registry.settings)
225 searcher = searcher_from_config(self.request.registry.settings)
185 result = searcher.search(
226 result = searcher.search(
186 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user,
227 'commit_id:%s*' % commit_hash, 'commit', auth_user,
187 raise_on_exc=False)
228 raise_on_exc=False)
188
229
189 return [
230 return [
190 {
231 {
191 'id': entry['commit_id'],
232 'id': entry['commit_id'],
192 'text': entry['commit_id'],
233 'value': org_query,
234 'value_display': 'repo `{}` commit: {}'.format(
235 entry['repository'], entry['commit_id']),
193 'type': 'commit',
236 'type': 'commit',
194 'obj': {'repo': entry['repository']},
237 'repo': entry['repository'],
195 'url': h.route_path(
238 'url': h.route_path(
196 'repo_commit',
239 'repo_commit',
197 repo_name=entry['repository'], commit_id=entry['commit_id'])
240 repo_name=entry['repository'], commit_id=entry['commit_id'])
@@ -235,41 +278,47 b' class HomeView(BaseAppView):'
235 _ = self.request.translate
278 _ = self.request.translate
236
279
237 query = self.request.GET.get('query')
280 query = self.request.GET.get('query')
238 log.debug('generating goto switcher list, query %s', query)
281 log.debug('generating main filter data, query %s', query)
282
283 default_search_val = 'Full text search for: `{}`'.format(query)
284 res = []
285 if not query:
286 return {'suggestions': res}
239
287
240 res = []
288 res.append({
289 'id': -1,
290 'value': query,
291 'value_display': default_search_val,
292 'type': 'search',
293 'url': h.route_path(
294 'search', _query={'q': query})
295 })
296
241 repo_groups = self._get_repo_group_list(query)
297 repo_groups = self._get_repo_group_list(query)
242 if repo_groups:
298 for serialized_repo_group in repo_groups:
243 res.append({
299 res.append(serialized_repo_group)
244 'text': _('Groups'),
245 'children': repo_groups
246 })
247
300
248 repos = self._get_repo_list(query)
301 repos = self._get_repo_list(query)
249 if repos:
302 for serialized_repo in repos:
250 res.append({
303 res.append(serialized_repo)
251 'text': _('Repositories'),
304
252 'children': repos
305 # TODO(marcink): permissions for that ?
253 })
306 users = self._get_user_list(query)
307 for serialized_user in users:
308 res.append(serialized_user)
254
309
255 commits = self._get_hash_commit_list(c.auth_user, query)
310 commits = self._get_hash_commit_list(c.auth_user, query)
256 if commits:
311 if commits:
257 unique_repos = {}
312 unique_repos = collections.OrderedDict()
258 for commit in commits:
313 for commit in commits:
259 unique_repos.setdefault(commit['obj']['repo'], []
314 repo_name = commit['repo']
260 ).append(commit)
315 unique_repos.setdefault(repo_name, []).append(commit)
261
316
262 for repo in unique_repos:
317 for repo, commits in unique_repos.items():
263 res.append({
318 for commit in commits:
264 'text': _('Commits in %(repo)s') % {'repo': repo},
319 res.append(commit)
265 'children': unique_repos[repo]
266 })
267
320
268 data = {
321 return {'suggestions': res}
269 'more': False,
270 'results': res
271 }
272 return data
273
322
274 def _get_groups_and_repos(self, repo_group_id=None):
323 def _get_groups_and_repos(self, repo_group_id=None):
275 # repo groups groups
324 # repo groups groups
@@ -766,7 +766,10 b' class RepoPullRequestsView(RepoAppView, '
766 'id': obj['name'],
766 'id': obj['name'],
767 'text': obj['name'],
767 'text': obj['name'],
768 'type': 'repo',
768 'type': 'repo',
769 'obj': obj['dbrepo']
769 'repo_id': obj['dbrepo']['repo_id'],
770 'repo_type': obj['dbrepo']['repo_type'],
771 'private': obj['dbrepo']['private'],
772
770 })
773 })
771
774
772 data = {
775 data = {
@@ -281,11 +281,9 b''
281 }
281 }
282
282
283 .navigation li.open {
283 .navigation li.open {
284
284 .submenu {
285 .submenu,
285 display: block;
286 .repo_switcher {
286 }
287 display: block;
288 }
289 }
287 }
290
288
291 .navigation li:last-child .submenu {
289 .navigation li:last-child .submenu {
@@ -642,3 +640,45 b' ul#context-pages {'
642
640
643 }
641 }
644 }
642 }
643
644 .main_filter_help_box {
645 padding: 7px 7px;
646 border-top: 1px solid @grey4;
647 border-right: 1px solid @grey4;
648 border-bottom: 1px solid @grey4;
649 display: inline-block;
650 vertical-align: top;
651 margin-left: -5px;
652 background: @grey3;
653 }
654
655 .main_filter_input_box {
656 display: inline-block;
657 }
658
659 .main_filter_box {
660 margin: 9px 0 0 0;
661 }
662
663 #main_filter_help {
664 background: @grey3;
665 border: 1px solid black;
666 position: absolute;
667 white-space: pre-wrap;
668 z-index: 9999;
669 color: @nav-grey;
670 margin: 1px 7px;
671 padding: 0 2px;
672 }
673
674 .main_filter_input {
675 padding: 6px;
676 min-width: 220px;
677 color: @nav-grey;
678 background: @grey3;
679 }
680
681 .main_filter_input::placeholder {
682 color: @nav-grey;
683 opacity: 1;
684 }
@@ -228,7 +228,7 b' mark,'
228 clear: both;
228 clear: both;
229 float: left;
229 float: left;
230 width: 100%;
230 width: 100%;
231 margin: @pagepadding 0 @pagepadding;
231 margin: @pagepadding/2 0 @pagepadding;
232
232
233 .breadcrumbs{
233 .breadcrumbs{
234 float: left;
234 float: left;
@@ -20,7 +20,7 b' function setRCMouseBindings(repoName, re'
20
20
21 // / open the quick filter
21 // / open the quick filter
22 Mousetrap.bind(['/'], function(e) {
22 Mousetrap.bind(['/'], function(e) {
23 $('#repo_switcher').select2('open');
23 $('#main_filter').get(0).focus();
24
24
25 // return false to prevent default browser behavior
25 // return false to prevent default browser behavior
26 // and stop event from bubbling
26 // and stop event from bubbling
@@ -131,7 +131,7 b' var repoFilter = function(data) {'
131
131
132 $.each(data.results[0].children, function() {
132 $.each(data.results[0].children, function() {
133 // replace name to ID for submision
133 // replace name to ID for submision
134 this.id = this.obj.repo_id;
134 this.id = this.repo_id;
135 results.push(this);
135 results.push(this);
136 });
136 });
137
137
@@ -154,7 +154,7 b' var selectVcsScope = function() {'
154 dropdownAutoWidth: true,
154 dropdownAutoWidth: true,
155 containerCssClass: "drop-menu",
155 containerCssClass: "drop-menu",
156 dropdownCssClass: "drop-menu-dropdown",
156 dropdownCssClass: "drop-menu-dropdown",
157 formatResult: formatResult,
157 formatResult: formatRepoResult,
158 query: $.debounce(250, function(query){
158 query: $.debounce(250, function(query){
159 self = this;
159 self = this;
160 var cacheKey = query.term;
160 var cacheKey = query.term;
@@ -165,8 +165,8 b' var repoTypeFilter = function(data) {'
165
165
166 $.each(data.results[0].children, function() {
166 $.each(data.results[0].children, function() {
167 // filter out the SAME repo, it cannot be used as fork of itself
167 // filter out the SAME repo, it cannot be used as fork of itself
168 if (this.obj.repo_id != currentRepoId) {
168 if (this.repo_id != currentRepoId) {
169 this.id = this.obj.repo_id;
169 this.id = this.repo_id;
170 results.push(this)
170 results.push(this)
171 }
171 }
172 });
172 });
@@ -181,7 +181,7 b' var repoTypeFilter = function(data) {'
181 dropdownAutoWidth: true,
181 dropdownAutoWidth: true,
182 containerCssClass: "drop-menu",
182 containerCssClass: "drop-menu",
183 dropdownCssClass: "drop-menu-dropdown",
183 dropdownCssClass: "drop-menu-dropdown",
184 formatResult: formatResult,
184 formatResult: formatRepoResult,
185 query: $.debounce(250, function(query){
185 query: $.debounce(250, function(query){
186 self = this;
186 self = this;
187 var cacheKey = query.term;
187 var cacheKey = query.term;
@@ -129,7 +129,7 b' var repoFilter = function(data) {'
129
129
130 $.each(data.results[0].children, function() {
130 $.each(data.results[0].children, function() {
131 // replace name to ID for submision
131 // replace name to ID for submision
132 this.id = this.obj.repo_id;
132 this.id = this.repo_id;
133 results.push(this);
133 results.push(this);
134 });
134 });
135
135
@@ -152,7 +152,7 b' var selectVcsScope = function() {'
152 dropdownAutoWidth: true,
152 dropdownAutoWidth: true,
153 containerCssClass: "drop-menu",
153 containerCssClass: "drop-menu",
154 dropdownCssClass: "drop-menu-dropdown",
154 dropdownCssClass: "drop-menu-dropdown",
155 formatResult: formatResult,
155 formatResult: formatRepoResult,
156 query: $.debounce(250, function(query){
156 query: $.debounce(250, function(query){
157 self = this;
157 self = this;
158 var cacheKey = query.term;
158 var cacheKey = query.term;
@@ -226,7 +226,7 b''
226 <!--- CONTEXT BAR -->
226 <!--- CONTEXT BAR -->
227 <div id="context-bar">
227 <div id="context-bar">
228 <div class="wrapper">
228 <div class="wrapper">
229 <ul id="context-pages" class="horizontal-list navigation">
229 <ul id="context-pages" class="navigation horizontal-list">
230 <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>
230 <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>
231 <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>
231 <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>
232 <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>
232 <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 b''
386 return ""
386 return ""
387 %>
387 %>
388 <ul id="quick" class="main_nav navigation horizontal-list">
388 <ul id="quick" class="main_nav navigation horizontal-list">
389 <!-- repo switcher -->
389
390 <li class="${is_active('repositories')} repo_switcher_li has_select2">
390 ## Main filter
391 <input id="repo_switcher" name="repo_switcher" type="hidden">
391 <li>
392 </li>
392 <div class="menulabel main_filter_box">
393 <div class="main_filter_input_box">
394 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
395 </div>
396 <div class="main_filter_help_box">
397 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
398 </div>
399 </div>
400
401 <div id="main_filter_help" style="display: none">
402 Use '/' key to quickly access this field.
403 Enter name of repository, or repository group for quick search.
404
405 Prefix query to allow special search:
406
407 For usernames, e.g user:admin
408
409 For commit hash/id, e.g commit:efced4
410
411 </div>
412 </li>
393
413
394 ## ROOT MENU
414 ## ROOT MENU
395 %if c.rhodecode_user.username != h.DEFAULT_USER:
415 %if c.rhodecode_user.username != h.DEFAULT_USER:
@@ -432,6 +452,9 b''
432 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
452 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
433 </li>
453 </li>
434 % endif
454 % endif
455 ## render extra user menu
456 ${usermenu(active=(active=='my_account'))}
457
435 % if c.debug_style:
458 % if c.debug_style:
436 <li class="${is_active('debug_style')}">
459 <li class="${is_active('debug_style')}">
437 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
460 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
@@ -439,102 +462,127 b''
439 </a>
462 </a>
440 </li>
463 </li>
441 % endif
464 % endif
442 ## render extra user menu
443 ${usermenu(active=(active=='my_account'))}
444 </ul>
465 </ul>
445
466
446 <script type="text/javascript">
467 <script type="text/javascript">
447 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
468 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
448
449 /*format the look of items in the list*/
450 var format = function(state, escapeMarkup){
451 if (!state.id){
452 return state.text; // optgroup
453 }
454 var obj_dict = state.obj;
455 var tmpl = '';
456
469
457 if(obj_dict && state.type == 'repo'){
470 var formatRepoResult = function(result, container, query, escapeMarkup) {
458 if(obj_dict['repo_type'] === 'hg'){
471 return function(data, escapeMarkup) {
459 tmpl += '<i class="icon-hg"></i> ';
472 if (!data.repo_id){
460 }
473 return data.text; // optgroup text Repositories
461 else if(obj_dict['repo_type'] === 'git'){
462 tmpl += '<i class="icon-git"></i> ';
463 }
464 else if(obj_dict['repo_type'] === 'svn'){
465 tmpl += '<i class="icon-svn"></i> ';
466 }
474 }
467 if(obj_dict['private']){
475
468 tmpl += '<i class="icon-lock" ></i> ';
476 var tmpl = '';
469 }
477 var repoType = data['repo_type'];
470 else if(visual_show_public_icon){
478 var repoName = data['text'];
471 tmpl += '<i class="icon-unlock-alt"></i> ';
479
480 if(data && data.type == 'repo'){
481 if(repoType === 'hg'){
482 tmpl += '<i class="icon-hg"></i> ';
483 }
484 else if(repoType === 'git'){
485 tmpl += '<i class="icon-git"></i> ';
486 }
487 else if(repoType === 'svn'){
488 tmpl += '<i class="icon-svn"></i> ';
489 }
490 if(data['private']){
491 tmpl += '<i class="icon-lock" ></i> ';
492 }
493 else if(visualShowPublicIcon){
494 tmpl += '<i class="icon-unlock-alt"></i> ';
495 }
472 }
496 }
473 }
497 tmpl += escapeMarkup(repoName);
474 if(obj_dict && state.type == 'commit') {
498 return tmpl;
475 tmpl += '<i class="icon-tag"></i>';
476 }
477 if(obj_dict && state.type == 'group'){
478 tmpl += '<i class="icon-folder-close"></i> ';
479 }
480 tmpl += escapeMarkup(state.text);
481 return tmpl;
482 };
483
499
484 var formatResult = function(result, container, query, escapeMarkup) {
500 }(result, escapeMarkup);
485 return format(result, escapeMarkup);
486 };
487
488 var formatSelection = function(data, container, escapeMarkup) {
489 return format(data, escapeMarkup);
490 };
501 };
491
502
492 $("#repo_switcher").select2({
503
493 cachedDataSource: {},
504 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
494 minimumInputLength: 2,
505
495 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
506 if (value.split(':').length === 2) {
496 dropdownAutoWidth: true,
507 value = value.split(':')[1]
497 formatResult: formatResult,
508 }
498 formatSelection: formatSelection,
509
499 containerCssClass: "repo-switcher",
510 var searchType = data['type'];
500 dropdownCssClass: "repo-switcher-dropdown",
511 var valueDisplay = data['value_display'];
501 escapeMarkup: function(m){
512
502 // don't escape our custom placeholder
513 var escapeRegExChars = function (value) {
503 if(m.substr(0,23) == '<div class="menulabel">'){
514 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
504 return m;
515 };
505 }
516 var pattern = '(' + escapeRegExChars(value) + ')';
517
518 // highlight match
519 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
520 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
521
522 var icon = '';
506
523
507 return Select2.util.escapeMarkup(m);
524 if (searchType === 'search') {
508 },
525 icon += '<i class="icon-more"></i> ';
509 query: $.debounce(250, function(query){
526 }
510 self = this;
527 else if (searchType === 'repo') {
511 var cacheKey = query.term;
528 if (data['repo_type'] === 'hg') {
512 var cachedData = self.cachedDataSource[cacheKey];
529 icon += '<i class="icon-hg"></i> ';
530 }
531 else if (data['repo_type'] === 'git') {
532 icon += '<i class="icon-git"></i> ';
533 }
534 else if (data['repo_type'] === 'svn') {
535 icon += '<i class="icon-svn"></i> ';
536 }
537 if (data['private']) {
538 icon += '<i class="icon-lock" ></i> ';
539 }
540 else if (visualShowPublicIcon) {
541 icon += '<i class="icon-unlock-alt"></i> ';
542 }
543 }
544 else if (searchType === 'repo_group') {
545 icon += '<i class="icon-folder-close"></i> ';
546 }
547 else if (searchType === 'user') {
548 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
549 }
550 else if (searchType === 'commit') {
551 icon += '<i class="icon-tag"></i>';
552 }
513
553
514 if (cachedData) {
554 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
515 query.callback({results: cachedData.results});
555 return tmpl.format(icon, valueDisplay);
516 } else {
556 };
517 $.ajax({
557
518 url: pyroutes.url('goto_switcher_data'),
558 var handleSelect = function(element, suggestion) {
519 data: {'query': query.term},
559 window.location = suggestion['url'];
520 dataType: 'json',
560 };
521 type: 'GET',
561 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
522 success: function(data) {
562 if (queryLowerCase.split(':').length === 2) {
523 self.cachedDataSource[cacheKey] = data;
563 queryLowerCase = queryLowerCase.split(':')[1]
524 query.callback({results: data.results});
564 }
525 },
565 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
526 error: function(data, textStatus, errorThrown) {
566 };
527 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
567
528 }
568 $('#main_filter').autocomplete({
529 })
569 serviceUrl: pyroutes.url('goto_switcher_data'),
530 }
570 minChars:2,
531 })
571 maxHeight:400,
572 deferRequestBy: 300, //miliseconds
573 tabDisabled: true,
574 autoSelectFirst: true,
575 formatResult: autocompleteMainFilterFormatResult,
576 lookupFilter: autocompleteMainFilterResult,
577 onSelect: function(element, suggestion){
578 handleSelect(element, suggestion);
579 return false;
580 }
532 });
581 });
533
582
534 $("#repo_switcher").on('select2-selecting', function(e){
583 showMainFilterBox = function () {
535 e.preventDefault();
584 $('#main_filter_help').toggle();
536 window.location = e.choice.url;
585 }
537 });
538
586
539 </script>
587 </script>
540 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
588 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
@@ -557,7 +605,7 b''
557 </tr>
605 </tr>
558 <%
606 <%
559 elems = [
607 elems = [
560 ('/', 'Open quick search box'),
608 ('/', 'Use quick search box'),
561 ('g h', 'Goto home page'),
609 ('g h', 'Goto home page'),
562 ('g g', 'Goto my private gists page'),
610 ('g g', 'Goto my private gists page'),
563 ('g G', 'Goto my public gists page'),
611 ('g G', 'Goto my public gists page'),
@@ -611,3 +659,4 b''
611 </div><!-- /.modal-content -->
659 </div><!-- /.modal-content -->
612 </div><!-- /.modal-dialog -->
660 </div><!-- /.modal-dialog -->
613 </div><!-- /.modal -->
661 </div><!-- /.modal -->
662
@@ -5,9 +5,8 b''
5 <!-- box / title -->
5 <!-- box / title -->
6 <div class="title">
6 <div class="title">
7 <div class="block-left breadcrumbs">
7 <div class="block-left breadcrumbs">
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 ${self.breadcrumbs()}
8 ${self.breadcrumbs()}
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
9 <span id="match_container" style="display:none"><span id="match_count">0</span> ${_('matches')}</span>
11 </div>
10 </div>
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
11 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 <div class="block-right">
12 <div class="block-right">
@@ -137,65 +136,6 b''
137 });
136 });
138 % endif
137 % endif
139
138
140 var getDatatableCount = function() {
141 var reposCount = 0;
142 var reposCountTotal = 0;
143
144 % if c.repos_data != '[]':
145 var pageInfo = $('#repo_list_table').dataTable().api().page.info();
146 var reposCount = pageInfo.recordsDisplay;
147 var reposCountTotal = pageInfo.recordsTotal;
148 % endif
149
150 var repoGroupsCount = 0;
151 var repoGroupsCountTotal = 0;
152
153 % if c.repo_groups_data != '[]':
154 var pageInfo = $('#group_list_table').dataTable().api().page.info();
155 var repoGroupsCount = pageInfo.recordsDisplay;
156 var repoGroupsCountTotal = pageInfo.recordsTotal;
157 % endif
158
159 if (repoGroupsCount !== repoGroupsCountTotal) {
160 $('#match_count').text(reposCount + repoGroupsCount);
161 }
162 if (reposCount !== reposCountTotal) {
163 $('#match_container').show();
164 }
165 if ($('#q_filter').val() === '') {
166 $('#match_container').hide();
167 }
168 };
169
170 // update the counter when doing search
171 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
172 getDatatableCount();
173 });
174
175 // filter, filter both grids
176 $('#q_filter').on( 'keyup', function () {
177
178 % if c.repo_groups_data != '[]':
179 var repo_group_api = $('#group_list_table').dataTable().api();
180 repo_group_api
181 .columns( 0 )
182 .search( this.value )
183 .draw();
184 % endif
185
186 % if c.repos_data != '[]':
187 var repo_api = $('#repo_list_table').dataTable().api();
188 repo_api
189 .columns( 0 )
190 .search( this.value )
191 .draw();
192 % endif
193
194 });
195
196 // refilter table if page load via back button
197 $("#q_filter").trigger('keyup');
198
199 });
139 });
200 </script>
140 </script>
201 </%def>
141 </%def>
@@ -465,7 +465,7 b''
465 query: $.debounce(250, function(query) {
465 query: $.debounce(250, function(query) {
466 queryTargetRepo(this, query);
466 queryTargetRepo(this, query);
467 }),
467 }),
468 formatResult: formatResult
468 formatResult: formatRepoResult
469 });
469 });
470
470
471 sourceRefSelect2.initRef();
471 sourceRefSelect2.initRef();
General Comments 0
You need to be logged in to leave comments. Login now