##// END OF EJS Templates
go-to search: updated logic of goto switcher...
marcink -
r3556:a75b51f8 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,659 +1,746 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
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 import re
21 import re
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 CSRFRequired)
31 CSRFRequired)
32 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.model.db import (
35 from rhodecode.model.db import (
36 func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
36 func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
39 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.user_group import UserGroupModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class HomeView(BaseAppView):
46 class HomeView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
50 c.user = c.auth_user.get_instance()
50 c.user = c.auth_user.get_instance()
51
51
52 return c
52 return c
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @view_config(
55 @view_config(
56 route_name='user_autocomplete_data', request_method='GET',
56 route_name='user_autocomplete_data', request_method='GET',
57 renderer='json_ext', xhr=True)
57 renderer='json_ext', xhr=True)
58 def user_autocomplete_data(self):
58 def user_autocomplete_data(self):
59 self.load_default_context()
59 self.load_default_context()
60 query = self.request.GET.get('query')
60 query = self.request.GET.get('query')
61 active = str2bool(self.request.GET.get('active') or True)
61 active = str2bool(self.request.GET.get('active') or True)
62 include_groups = str2bool(self.request.GET.get('user_groups'))
62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65
65
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 query, active, include_groups)
67 query, active, include_groups)
68
68
69 _users = UserModel().get_users(
69 _users = UserModel().get_users(
70 name_contains=query, only_active=active)
70 name_contains=query, only_active=active)
71
71
72 def maybe_skip_default_user(usr):
72 def maybe_skip_default_user(usr):
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 return False
74 return False
75 return True
75 return True
76 _users = filter(maybe_skip_default_user, _users)
76 _users = filter(maybe_skip_default_user, _users)
77
77
78 if include_groups:
78 if include_groups:
79 # extend with user groups
79 # extend with user groups
80 _user_groups = UserGroupModel().get_user_groups(
80 _user_groups = UserGroupModel().get_user_groups(
81 name_contains=query, only_active=active,
81 name_contains=query, only_active=active,
82 expand_groups=expand_groups)
82 expand_groups=expand_groups)
83 _users = _users + _user_groups
83 _users = _users + _user_groups
84
84
85 return {'suggestions': _users}
85 return {'suggestions': _users}
86
86
87 @LoginRequired()
87 @LoginRequired()
88 @NotAnonymous()
88 @NotAnonymous()
89 @view_config(
89 @view_config(
90 route_name='user_group_autocomplete_data', request_method='GET',
90 route_name='user_group_autocomplete_data', request_method='GET',
91 renderer='json_ext', xhr=True)
91 renderer='json_ext', xhr=True)
92 def user_group_autocomplete_data(self):
92 def user_group_autocomplete_data(self):
93 self.load_default_context()
93 self.load_default_context()
94 query = self.request.GET.get('query')
94 query = self.request.GET.get('query')
95 active = str2bool(self.request.GET.get('active') or True)
95 active = str2bool(self.request.GET.get('active') or True)
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97
97
98 log.debug('generating user group list, query:%s, active:%s',
98 log.debug('generating user group list, query:%s, active:%s',
99 query, active)
99 query, active)
100
100
101 _user_groups = UserGroupModel().get_user_groups(
101 _user_groups = UserGroupModel().get_user_groups(
102 name_contains=query, only_active=active,
102 name_contains=query, only_active=active,
103 expand_groups=expand_groups)
103 expand_groups=expand_groups)
104 _user_groups = _user_groups
104 _user_groups = _user_groups
105
105
106 return {'suggestions': _user_groups}
106 return {'suggestions': _user_groups}
107
107
108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
108 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
109 org_query = name_contains
109 org_query = name_contains
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 ['repository.read', 'repository.write', 'repository.admin'],
111 ['repository.read', 'repository.write', 'repository.admin'],
112 cache=False, name_filter=name_contains) or [-1]
112 cache=False, name_filter=name_contains) or [-1]
113
113
114 query = Repository.query()\
114 query = Repository.query()\
115 .order_by(func.length(Repository.repo_name))\
116 .order_by(Repository.repo_name)\
117 .filter(Repository.archived.isnot(true()))\
115 .filter(Repository.archived.isnot(true()))\
118 .filter(or_(
116 .filter(or_(
119 # generate multiple IN to fix limitation problems
117 # generate multiple IN to fix limitation problems
120 *in_filter_generator(Repository.repo_id, allowed_ids)
118 *in_filter_generator(Repository.repo_id, allowed_ids)
121 ))
119 ))
122
120
121 query = query.order_by(case(
122 [
123 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
124 ],
125 ))
126 query = query.order_by(func.length(Repository.repo_name))
127 query = query.order_by(Repository.repo_name)
128
123 if repo_type:
129 if repo_type:
124 query = query.filter(Repository.repo_type == repo_type)
130 query = query.filter(Repository.repo_type == repo_type)
125
131
126 if name_contains:
132 if name_contains:
127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
133 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
128 query = query.filter(
134 query = query.filter(
129 Repository.repo_name.ilike(ilike_expression))
135 Repository.repo_name.ilike(ilike_expression))
130 query = query.limit(limit)
136 query = query.limit(limit)
131
137
132 acl_iter = query
138 acl_iter = query
133
139
134 return [
140 return [
135 {
141 {
136 'id': obj.repo_name,
142 'id': obj.repo_name,
137 'value': org_query,
143 'value': org_query,
138 'value_display': obj.repo_name,
144 'value_display': obj.repo_name,
139 'text': obj.repo_name,
145 'text': obj.repo_name,
140 'type': 'repo',
146 'type': 'repo',
141 'repo_id': obj.repo_id,
147 'repo_id': obj.repo_id,
142 'repo_type': obj.repo_type,
148 'repo_type': obj.repo_type,
143 'private': obj.private,
149 'private': obj.private,
144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
150 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
145 }
151 }
146 for obj in acl_iter]
152 for obj in acl_iter]
147
153
148 def _get_repo_group_list(self, name_contains=None, limit=20):
154 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
149 org_query = name_contains
155 org_query = name_contains
150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
156 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
151 ['group.read', 'group.write', 'group.admin'],
157 ['group.read', 'group.write', 'group.admin'],
152 cache=False, name_filter=name_contains) or [-1]
158 cache=False, name_filter=name_contains) or [-1]
153
159
154 query = RepoGroup.query()\
160 query = RepoGroup.query()\
155 .order_by(func.length(RepoGroup.group_name))\
156 .order_by(RepoGroup.group_name) \
157 .filter(or_(
161 .filter(or_(
158 # generate multiple IN to fix limitation problems
162 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
163 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 ))
164 ))
161
165
166 query = query.order_by(case(
167 [
168 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
169 ],
170 ))
171 query = query.order_by(func.length(RepoGroup.group_name))
172 query = query.order_by(RepoGroup.group_name)
173
162 if name_contains:
174 if name_contains:
163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
175 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
164 query = query.filter(
176 query = query.filter(
165 RepoGroup.group_name.ilike(ilike_expression))
177 RepoGroup.group_name.ilike(ilike_expression))
166 query = query.limit(limit)
178 query = query.limit(limit)
167
179
168 acl_iter = query
180 acl_iter = query
169
181
170 return [
182 return [
171 {
183 {
172 'id': obj.group_name,
184 'id': obj.group_name,
173 'value': org_query,
185 'value': org_query,
174 'value_display': obj.group_name,
186 'value_display': obj.group_name,
175 'text': obj.group_name,
187 'text': obj.group_name,
176 'type': 'repo_group',
188 'type': 'repo_group',
177 'repo_group_id': obj.group_id,
189 'repo_group_id': obj.group_id,
178 'url': h.route_path(
190 'url': h.route_path(
179 'repo_group_home', repo_group_name=obj.group_name)
191 'repo_group_home', repo_group_name=obj.group_name)
180 }
192 }
181 for obj in acl_iter]
193 for obj in acl_iter]
182
194
183 def _get_user_list(self, name_contains=None, limit=20):
195 def _get_user_list(self, name_contains=None, limit=20):
184 org_query = name_contains
196 org_query = name_contains
185 if not name_contains:
197 if not name_contains:
186 return []
198 return [], False
187
199
188 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
200 # TODO(marcink): should all logged in users be allowed to search others?
201 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
202 if not allowed_user_search:
203 return [], False
204
205 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
189 if len(name_contains) != 1:
206 if len(name_contains) != 1:
190 return []
207 return [], False
208
191 name_contains = name_contains[0]
209 name_contains = name_contains[0]
192
210
193 query = User.query()\
211 query = User.query()\
194 .order_by(func.length(User.username))\
212 .order_by(func.length(User.username))\
195 .order_by(User.username) \
213 .order_by(User.username) \
196 .filter(User.username != User.DEFAULT_USER)
214 .filter(User.username != User.DEFAULT_USER)
197
215
198 if name_contains:
216 if name_contains:
199 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
217 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
200 query = query.filter(
218 query = query.filter(
201 User.username.ilike(ilike_expression))
219 User.username.ilike(ilike_expression))
202 query = query.limit(limit)
220 query = query.limit(limit)
203
221
204 acl_iter = query
222 acl_iter = query
205
223
206 return [
224 return [
207 {
225 {
208 'id': obj.user_id,
226 'id': obj.user_id,
209 'value': org_query,
227 'value': org_query,
210 'value_display': obj.username,
228 'value_display': 'user: `{}`'.format(obj.username),
211 'type': 'user',
229 'type': 'user',
212 'icon_link': h.gravatar_url(obj.email, 30),
230 'icon_link': h.gravatar_url(obj.email, 30),
213 'url': h.route_path(
231 'url': h.route_path(
214 'user_profile', username=obj.username)
232 'user_profile', username=obj.username)
215 }
233 }
216 for obj in acl_iter]
234 for obj in acl_iter], True
217
235
218 def _get_user_groups_list(self, name_contains=None, limit=20):
236 def _get_user_groups_list(self, name_contains=None, limit=20):
219 org_query = name_contains
237 org_query = name_contains
220 if not name_contains:
238 if not name_contains:
221 return []
239 return [], False
222
240
223 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
241 # TODO(marcink): should all logged in users be allowed to search others?
242 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
243 if not allowed_user_search:
244 return [], False
245
246 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
224 if len(name_contains) != 1:
247 if len(name_contains) != 1:
225 return []
248 return [], False
249
226 name_contains = name_contains[0]
250 name_contains = name_contains[0]
227
251
228 query = UserGroup.query()\
252 query = UserGroup.query()\
229 .order_by(func.length(UserGroup.users_group_name))\
253 .order_by(func.length(UserGroup.users_group_name))\
230 .order_by(UserGroup.users_group_name)
254 .order_by(UserGroup.users_group_name)
231
255
232 if name_contains:
256 if name_contains:
233 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
257 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
234 query = query.filter(
258 query = query.filter(
235 UserGroup.users_group_name.ilike(ilike_expression))
259 UserGroup.users_group_name.ilike(ilike_expression))
236 query = query.limit(limit)
260 query = query.limit(limit)
237
261
238 acl_iter = query
262 acl_iter = query
239
263
240 return [
264 return [
241 {
265 {
242 'id': obj.users_group_id,
266 'id': obj.users_group_id,
243 'value': org_query,
267 'value': org_query,
244 'value_display': obj.users_group_name,
268 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
245 'type': 'user_group',
269 'type': 'user_group',
246 'url': h.route_path(
270 'url': h.route_path(
247 'user_group_profile', user_group_name=obj.users_group_name)
271 'user_group_profile', user_group_name=obj.users_group_name)
248 }
272 }
249 for obj in acl_iter]
273 for obj in acl_iter], True
250
274
251 def _get_hash_commit_list(self, auth_user, searcher, query):
275 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
276 repo_name = repo_group_name = None
277 if repo:
278 repo_name = repo.repo_name
279 if repo_group:
280 repo_group_name = repo_group.group_name
281
252 org_query = query
282 org_query = query
253 if not query or len(query) < 3 or not searcher:
283 if not query or len(query) < 3 or not searcher:
254 return []
284 return [], False
255
285
256 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
286 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
257
287
258 if len(commit_hashes) != 1:
288 if len(commit_hashes) != 1:
259 return []
289 return [], False
290
260 commit_hash = commit_hashes[0]
291 commit_hash = commit_hashes[0]
261
292
262 result = searcher.search(
293 result = searcher.search(
263 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
294 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
264 raise_on_exc=False)
295 repo_name, repo_group_name, raise_on_exc=False)
265
296
266 commits = []
297 commits = []
267 for entry in result['results']:
298 for entry in result['results']:
268 repo_data = {
299 repo_data = {
269 'repository_id': entry.get('repository_id'),
300 'repository_id': entry.get('repository_id'),
270 'repository_type': entry.get('repo_type'),
301 'repository_type': entry.get('repo_type'),
271 'repository_name': entry.get('repository'),
302 'repository_name': entry.get('repository'),
272 }
303 }
273
304
274 commit_entry = {
305 commit_entry = {
275 'id': entry['commit_id'],
306 'id': entry['commit_id'],
276 'value': org_query,
307 'value': org_query,
277 'value_display': '`{}` commit: {}'.format(
308 'value_display': '`{}` commit: {}'.format(
278 entry['repository'], entry['commit_id']),
309 entry['repository'], entry['commit_id']),
279 'type': 'commit',
310 'type': 'commit',
280 'repo': entry['repository'],
311 'repo': entry['repository'],
281 'repo_data': repo_data,
312 'repo_data': repo_data,
282
313
283 'url': h.route_path(
314 'url': h.route_path(
284 'repo_commit',
315 'repo_commit',
285 repo_name=entry['repository'], commit_id=entry['commit_id'])
316 repo_name=entry['repository'], commit_id=entry['commit_id'])
286 }
317 }
287
318
288 commits.append(commit_entry)
319 commits.append(commit_entry)
289 return commits
320 return commits, True
290
321
291 def _get_path_list(self, auth_user, searcher, query):
322 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
323 repo_name = repo_group_name = None
324 if repo:
325 repo_name = repo.repo_name
326 if repo_group:
327 repo_group_name = repo_group.group_name
328
292 org_query = query
329 org_query = query
293 if not query or len(query) < 3 or not searcher:
330 if not query or len(query) < 3 or not searcher:
294 return []
331 return [], False
295
332
296 paths_re = re.compile('(?:file:)(.{1,})').findall(query)
333 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
297 if len(paths_re) != 1:
334 if len(paths_re) != 1:
298 return []
335 return [], False
336
299 file_path = paths_re[0]
337 file_path = paths_re[0]
300
338
301 search_path = searcher.escape_specials(file_path)
339 search_path = searcher.escape_specials(file_path)
302 result = searcher.search(
340 result = searcher.search(
303 'file.raw:*{}*'.format(search_path), 'path', auth_user,
341 'file.raw:*{}*'.format(search_path), 'path', auth_user,
304 raise_on_exc=False)
342 repo_name, repo_group_name, raise_on_exc=False)
305
343
306 files = []
344 files = []
307 for entry in result['results']:
345 for entry in result['results']:
308 repo_data = {
346 repo_data = {
309 'repository_id': entry.get('repository_id'),
347 'repository_id': entry.get('repository_id'),
310 'repository_type': entry.get('repo_type'),
348 'repository_type': entry.get('repo_type'),
311 'repository_name': entry.get('repository'),
349 'repository_name': entry.get('repository'),
312 }
350 }
313
351
314 file_entry = {
352 file_entry = {
315 'id': entry['commit_id'],
353 'id': entry['commit_id'],
316 'value': org_query,
354 'value': org_query,
317 'value_display': '`{}` file: {}'.format(
355 'value_display': '`{}` file: {}'.format(
318 entry['repository'], entry['file']),
356 entry['repository'], entry['file']),
319 'type': 'file',
357 'type': 'file',
320 'repo': entry['repository'],
358 'repo': entry['repository'],
321 'repo_data': repo_data,
359 'repo_data': repo_data,
322
360
323 'url': h.route_path(
361 'url': h.route_path(
324 'repo_files',
362 'repo_files',
325 repo_name=entry['repository'], commit_id=entry['commit_id'],
363 repo_name=entry['repository'], commit_id=entry['commit_id'],
326 f_path=entry['file'])
364 f_path=entry['file'])
327 }
365 }
328
366
329 files.append(file_entry)
367 files.append(file_entry)
330 return files
368 return files, True
331
369
332 @LoginRequired()
370 @LoginRequired()
333 @view_config(
371 @view_config(
334 route_name='repo_list_data', request_method='GET',
372 route_name='repo_list_data', request_method='GET',
335 renderer='json_ext', xhr=True)
373 renderer='json_ext', xhr=True)
336 def repo_list_data(self):
374 def repo_list_data(self):
337 _ = self.request.translate
375 _ = self.request.translate
338 self.load_default_context()
376 self.load_default_context()
339
377
340 query = self.request.GET.get('query')
378 query = self.request.GET.get('query')
341 repo_type = self.request.GET.get('repo_type')
379 repo_type = self.request.GET.get('repo_type')
342 log.debug('generating repo list, query:%s, repo_type:%s',
380 log.debug('generating repo list, query:%s, repo_type:%s',
343 query, repo_type)
381 query, repo_type)
344
382
345 res = []
383 res = []
346 repos = self._get_repo_list(query, repo_type=repo_type)
384 repos = self._get_repo_list(query, repo_type=repo_type)
347 if repos:
385 if repos:
348 res.append({
386 res.append({
349 'text': _('Repositories'),
387 'text': _('Repositories'),
350 'children': repos
388 'children': repos
351 })
389 })
352
390
353 data = {
391 data = {
354 'more': False,
392 'more': False,
355 'results': res
393 'results': res
356 }
394 }
357 return data
395 return data
358
396
359 @LoginRequired()
397 @LoginRequired()
360 @view_config(
398 @view_config(
361 route_name='repo_group_list_data', request_method='GET',
399 route_name='repo_group_list_data', request_method='GET',
362 renderer='json_ext', xhr=True)
400 renderer='json_ext', xhr=True)
363 def repo_group_list_data(self):
401 def repo_group_list_data(self):
364 _ = self.request.translate
402 _ = self.request.translate
365 self.load_default_context()
403 self.load_default_context()
366
404
367 query = self.request.GET.get('query')
405 query = self.request.GET.get('query')
368
406
369 log.debug('generating repo group list, query:%s',
407 log.debug('generating repo group list, query:%s',
370 query)
408 query)
371
409
372 res = []
410 res = []
373 repo_groups = self._get_repo_group_list(query)
411 repo_groups = self._get_repo_group_list(query)
374 if repo_groups:
412 if repo_groups:
375 res.append({
413 res.append({
376 'text': _('Repository Groups'),
414 'text': _('Repository Groups'),
377 'children': repo_groups
415 'children': repo_groups
378 })
416 })
379
417
380 data = {
418 data = {
381 'more': False,
419 'more': False,
382 'results': res
420 'results': res
383 }
421 }
384 return data
422 return data
385
423
386 def _get_default_search_queries(self, search_context, searcher, query):
424 def _get_default_search_queries(self, search_context, searcher, query):
387 if not searcher:
425 if not searcher:
388 return []
426 return []
389
427
390 is_es_6 = searcher.is_es_6
428 is_es_6 = searcher.is_es_6
391
429
392 queries = []
430 queries = []
393 repo_group_name, repo_name, repo_context = None, None, None
431 repo_group_name, repo_name, repo_context = None, None, None
394
432
395 # repo group context
433 # repo group context
396 if search_context.get('search_context[repo_group_name]'):
434 if search_context.get('search_context[repo_group_name]'):
397 repo_group_name = search_context.get('search_context[repo_group_name]')
435 repo_group_name = search_context.get('search_context[repo_group_name]')
398 if search_context.get('search_context[repo_name]'):
436 if search_context.get('search_context[repo_name]'):
399 repo_name = search_context.get('search_context[repo_name]')
437 repo_name = search_context.get('search_context[repo_name]')
400 repo_context = search_context.get('search_context[repo_view_type]')
438 repo_context = search_context.get('search_context[repo_view_type]')
401
439
402 if is_es_6 and repo_name:
440 if is_es_6 and repo_name:
403 # files
441 # files
404 def query_modifier():
442 def query_modifier():
405 qry = query
443 qry = query
406 return {'q': qry, 'type': 'content'}
444 return {'q': qry, 'type': 'content'}
407 label = u'File search for `{}` in this repository.'.format(query)
445 label = u'File search for `{}` in this repository.'.format(query)
408 queries.append(
446 file_qry = {
409 {
447 'id': -10,
410 'id': -10,
448 'value': query,
411 'value': query,
449 'value_display': label,
412 'value_display': label,
450 'type': 'search',
413 'type': 'search',
451 'url': h.route_path('search_repo',
414 'url': h.route_path('search_repo',
452 repo_name=repo_name,
415 repo_name=repo_name,
453 _query=query_modifier())
416 _query=query_modifier())
417 }
454 }
418 )
419
455
420 # commits
456 # commits
421 def query_modifier():
457 def query_modifier():
422 qry = query
458 qry = query
423 return {'q': qry, 'type': 'commit'}
459 return {'q': qry, 'type': 'commit'}
424
460
425 label = u'Commit search for `{}` in this repository.'.format(query)
461 label = u'Commit search for `{}` in this repository.'.format(query)
426 queries.append(
462 commit_qry = {
427 {
463 'id': -20,
428 'id': -20,
464 'value': query,
429 'value': query,
465 'value_display': label,
430 'value_display': label,
466 'type': 'search',
431 'type': 'search',
467 'url': h.route_path('search_repo',
432 'url': h.route_path('search_repo',
468 repo_name=repo_name,
433 repo_name=repo_name,
469 _query=query_modifier())
434 _query=query_modifier())
435 }
470 }
436 )
471
472 if repo_context in ['commit', 'changelog']:
473 queries.extend([commit_qry, file_qry])
474 elif repo_context in ['files', 'summary']:
475 queries.extend([file_qry, commit_qry])
476 else:
477 queries.extend([commit_qry, file_qry])
437
478
438 elif is_es_6 and repo_group_name:
479 elif is_es_6 and repo_group_name:
439 # files
480 # files
440 def query_modifier():
481 def query_modifier():
441 qry = query
482 qry = query
442 return {'q': qry, 'type': 'content'}
483 return {'q': qry, 'type': 'content'}
443
484
444 label = u'File search for `{}` in this repository group'.format(query)
485 label = u'File search for `{}` in this repository group'.format(query)
445 queries.append(
486 file_qry = {
446 {
487 'id': -30,
447 'id': -30,
488 'value': query,
448 'value': query,
489 'value_display': label,
449 'value_display': label,
490 'type': 'search',
450 'type': 'search',
491 'url': h.route_path('search_repo_group',
451 'url': h.route_path('search_repo_group',
492 repo_group_name=repo_group_name,
452 repo_group_name=repo_group_name,
493 _query=query_modifier())
453 _query=query_modifier())
454 }
494 }
455 )
456
495
457 # commits
496 # commits
458 def query_modifier():
497 def query_modifier():
459 qry = query
498 qry = query
460 return {'q': qry, 'type': 'commit'}
499 return {'q': qry, 'type': 'commit'}
461
500
462 label = u'Commit search for `{}` in this repository group'.format(query)
501 label = u'Commit search for `{}` in this repository group'.format(query)
463 queries.append(
502 commit_qry = {
464 {
503 'id': -40,
465 'id': -40,
504 'value': query,
466 'value': query,
505 'value_display': label,
467 'value_display': label,
506 'type': 'search',
468 'type': 'search',
507 'url': h.route_path('search_repo_group',
469 'url': h.route_path('search_repo_group',
508 repo_group_name=repo_group_name,
470 repo_group_name=repo_group_name,
509 _query=query_modifier())
471 _query=query_modifier())
472 }
510 }
473 )
474
511
512 if repo_context in ['commit', 'changelog']:
513 queries.extend([commit_qry, file_qry])
514 elif repo_context in ['files', 'summary']:
515 queries.extend([file_qry, commit_qry])
516 else:
517 queries.extend([commit_qry, file_qry])
518
519 # Global, not scoped
475 if not queries:
520 if not queries:
476 queries.append(
521 queries.append(
477 {
522 {
478 'id': -1,
523 'id': -1,
479 'value': query,
524 'value': query,
480 'value_display': u'File search for: `{}`'.format(query),
525 'value_display': u'File search for: `{}`'.format(query),
481 'type': 'search',
526 'type': 'search',
482 'url': h.route_path('search',
527 'url': h.route_path('search',
483 _query={'q': query, 'type': 'content'})
528 _query={'q': query, 'type': 'content'})
484 })
529 })
485 queries.append(
530 queries.append(
486 {
531 {
487 'id': -2,
532 'id': -2,
488 'value': query,
533 'value': query,
489 'value_display': u'Commit search for: `{}`'.format(query),
534 'value_display': u'Commit search for: `{}`'.format(query),
490 'type': 'search',
535 'type': 'search',
491 'url': h.route_path('search',
536 'url': h.route_path('search',
492 _query={'q': query, 'type': 'commit'})
537 _query={'q': query, 'type': 'commit'})
493 })
538 })
494
539
495 return queries
540 return queries
496
541
497 @LoginRequired()
542 @LoginRequired()
498 @view_config(
543 @view_config(
499 route_name='goto_switcher_data', request_method='GET',
544 route_name='goto_switcher_data', request_method='GET',
500 renderer='json_ext', xhr=True)
545 renderer='json_ext', xhr=True)
501 def goto_switcher_data(self):
546 def goto_switcher_data(self):
502 c = self.load_default_context()
547 c = self.load_default_context()
503
548
504 _ = self.request.translate
549 _ = self.request.translate
505
550
506 query = self.request.GET.get('query')
551 query = self.request.GET.get('query')
507 log.debug('generating main filter data, query %s', query)
552 log.debug('generating main filter data, query %s', query)
508
553
509 res = []
554 res = []
510 if not query:
555 if not query:
511 return {'suggestions': res}
556 return {'suggestions': res}
512
557
558 def no_match(name):
559 return {
560 'id': -1,
561 'value': "",
562 'value_display': name,
563 'type': 'text',
564 'url': ""
565 }
513 searcher = searcher_from_config(self.request.registry.settings)
566 searcher = searcher_from_config(self.request.registry.settings)
514 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
567 has_specialized_search = False
515 res.append(_q)
516
568
569 # set repo context
570 repo = None
571 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
572 if repo_id:
573 repo = Repository.get(repo_id)
574
575 # set group context
576 repo_group = None
517 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
577 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
518 if repo_group_id:
578 if repo_group_id:
519 repo_group = RepoGroup.get(repo_group_id)
579 repo_group = RepoGroup.get(repo_group_id)
520 composed_hint = '{}/{}'.format(repo_group.group_name, query)
580 prefix_match = False
521 show_hint = not query.startswith(repo_group.group_name)
581
522 if repo_group and show_hint:
582 # user: type search
523 hint = u'Repository search inside: `{}`'.format(composed_hint)
583 if not prefix_match:
524 res.append({
584 users, prefix_match = self._get_user_list(query)
525 'id': -1,
585 if users:
526 'value': composed_hint,
586 has_specialized_search = True
527 'value_display': hint,
587 for serialized_user in users:
528 'type': 'hint',
588 res.append(serialized_user)
529 'url': ""
589 elif prefix_match:
530 })
590 has_specialized_search = True
591 res.append(no_match('No matching users found'))
531
592
532 repo_groups = self._get_repo_group_list(query)
593 # user_group: type search
533 for serialized_repo_group in repo_groups:
594 if not prefix_match:
534 res.append(serialized_repo_group)
595 user_groups, prefix_match = self._get_user_groups_list(query)
596 if user_groups:
597 has_specialized_search = True
598 for serialized_user_group in user_groups:
599 res.append(serialized_user_group)
600 elif prefix_match:
601 has_specialized_search = True
602 res.append(no_match('No matching user groups found'))
535
603
536 repos = self._get_repo_list(query)
604 # FTS commit: type search
537 for serialized_repo in repos:
605 if not prefix_match:
538 res.append(serialized_repo)
606 commits, prefix_match = self._get_hash_commit_list(
607 c.auth_user, searcher, query, repo, repo_group)
608 if commits:
609 has_specialized_search = True
610 unique_repos = collections.OrderedDict()
611 for commit in commits:
612 repo_name = commit['repo']
613 unique_repos.setdefault(repo_name, []).append(commit)
539
614
540 # TODO(marcink): should all logged in users be allowed to search others?
615 for _repo, commits in unique_repos.items():
541 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
616 for commit in commits:
542 if allowed_user_search:
617 res.append(commit)
543 users = self._get_user_list(query)
618 elif prefix_match:
544 for serialized_user in users:
619 has_specialized_search = True
545 res.append(serialized_user)
620 res.append(no_match('No matching commits found'))
546
621
547 user_groups = self._get_user_groups_list(query)
622 # FTS file: type search
548 for serialized_user_group in user_groups:
623 if not prefix_match:
549 res.append(serialized_user_group)
624 paths, prefix_match = self._get_path_list(
625 c.auth_user, searcher, query, repo, repo_group)
626 if paths:
627 has_specialized_search = True
628 unique_repos = collections.OrderedDict()
629 for path in paths:
630 repo_name = path['repo']
631 unique_repos.setdefault(repo_name, []).append(path)
550
632
551 commits = self._get_hash_commit_list(c.auth_user, searcher, query)
633 for repo, paths in unique_repos.items():
552 if commits:
634 for path in paths:
553 unique_repos = collections.OrderedDict()
635 res.append(path)
554 for commit in commits:
636 elif prefix_match:
555 repo_name = commit['repo']
637 has_specialized_search = True
556 unique_repos.setdefault(repo_name, []).append(commit)
638 res.append(no_match('No matching files found'))
557
639
558 for repo, commits in unique_repos.items():
640 # main suggestions
559 for commit in commits:
641 if not has_specialized_search:
560 res.append(commit)
642 repo_group_name = ''
643 if repo_group:
644 repo_group_name = repo_group.group_name
561
645
562 paths = self._get_path_list(c.auth_user, searcher, query)
646 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
563 if paths:
647 res.append(_q)
564 unique_repos = collections.OrderedDict()
648
565 for path in paths:
649 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
566 repo_name = path['repo']
650 for serialized_repo_group in repo_groups:
567 unique_repos.setdefault(repo_name, []).append(path)
651 res.append(serialized_repo_group)
568
652
569 for repo, paths in unique_repos.items():
653 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
570 for path in paths:
654 for serialized_repo in repos:
571 res.append(path)
655 res.append(serialized_repo)
656
657 if not repos and not repo_groups:
658 res.append(no_match('No matches found'))
572
659
573 return {'suggestions': res}
660 return {'suggestions': res}
574
661
575 def _get_groups_and_repos(self, repo_group_id=None):
662 def _get_groups_and_repos(self, repo_group_id=None):
576 # repo groups groups
663 # repo groups groups
577 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
664 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
578 _perms = ['group.read', 'group.write', 'group.admin']
665 _perms = ['group.read', 'group.write', 'group.admin']
579 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
666 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
580 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
667 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
581 repo_group_list=repo_group_list_acl, admin=False)
668 repo_group_list=repo_group_list_acl, admin=False)
582
669
583 # repositories
670 # repositories
584 repo_list = Repository.get_all_repos(group_id=repo_group_id)
671 repo_list = Repository.get_all_repos(group_id=repo_group_id)
585 _perms = ['repository.read', 'repository.write', 'repository.admin']
672 _perms = ['repository.read', 'repository.write', 'repository.admin']
586 repo_list_acl = RepoList(repo_list, perm_set=_perms)
673 repo_list_acl = RepoList(repo_list, perm_set=_perms)
587 repo_data = RepoModel().get_repos_as_dict(
674 repo_data = RepoModel().get_repos_as_dict(
588 repo_list=repo_list_acl, admin=False)
675 repo_list=repo_list_acl, admin=False)
589
676
590 return repo_data, repo_group_data
677 return repo_data, repo_group_data
591
678
592 @LoginRequired()
679 @LoginRequired()
593 @view_config(
680 @view_config(
594 route_name='home', request_method='GET',
681 route_name='home', request_method='GET',
595 renderer='rhodecode:templates/index.mako')
682 renderer='rhodecode:templates/index.mako')
596 def main_page(self):
683 def main_page(self):
597 c = self.load_default_context()
684 c = self.load_default_context()
598 c.repo_group = None
685 c.repo_group = None
599
686
600 repo_data, repo_group_data = self._get_groups_and_repos()
687 repo_data, repo_group_data = self._get_groups_and_repos()
601 # json used to render the grids
688 # json used to render the grids
602 c.repos_data = json.dumps(repo_data)
689 c.repos_data = json.dumps(repo_data)
603 c.repo_groups_data = json.dumps(repo_group_data)
690 c.repo_groups_data = json.dumps(repo_group_data)
604
691
605 return self._get_template_context(c)
692 return self._get_template_context(c)
606
693
607 @LoginRequired()
694 @LoginRequired()
608 @HasRepoGroupPermissionAnyDecorator(
695 @HasRepoGroupPermissionAnyDecorator(
609 'group.read', 'group.write', 'group.admin')
696 'group.read', 'group.write', 'group.admin')
610 @view_config(
697 @view_config(
611 route_name='repo_group_home', request_method='GET',
698 route_name='repo_group_home', request_method='GET',
612 renderer='rhodecode:templates/index_repo_group.mako')
699 renderer='rhodecode:templates/index_repo_group.mako')
613 @view_config(
700 @view_config(
614 route_name='repo_group_home_slash', request_method='GET',
701 route_name='repo_group_home_slash', request_method='GET',
615 renderer='rhodecode:templates/index_repo_group.mako')
702 renderer='rhodecode:templates/index_repo_group.mako')
616 def repo_group_main_page(self):
703 def repo_group_main_page(self):
617 c = self.load_default_context()
704 c = self.load_default_context()
618 c.repo_group = self.request.db_repo_group
705 c.repo_group = self.request.db_repo_group
619 repo_data, repo_group_data = self._get_groups_and_repos(
706 repo_data, repo_group_data = self._get_groups_and_repos(
620 c.repo_group.group_id)
707 c.repo_group.group_id)
621
708
622 # json used to render the grids
709 # json used to render the grids
623 c.repos_data = json.dumps(repo_data)
710 c.repos_data = json.dumps(repo_data)
624 c.repo_groups_data = json.dumps(repo_group_data)
711 c.repo_groups_data = json.dumps(repo_group_data)
625
712
626 return self._get_template_context(c)
713 return self._get_template_context(c)
627
714
628 @LoginRequired()
715 @LoginRequired()
629 @CSRFRequired()
716 @CSRFRequired()
630 @view_config(
717 @view_config(
631 route_name='markup_preview', request_method='POST',
718 route_name='markup_preview', request_method='POST',
632 renderer='string', xhr=True)
719 renderer='string', xhr=True)
633 def markup_preview(self):
720 def markup_preview(self):
634 # Technically a CSRF token is not needed as no state changes with this
721 # Technically a CSRF token is not needed as no state changes with this
635 # call. However, as this is a POST is better to have it, so automated
722 # call. However, as this is a POST is better to have it, so automated
636 # tools don't flag it as potential CSRF.
723 # tools don't flag it as potential CSRF.
637 # Post is required because the payload could be bigger than the maximum
724 # Post is required because the payload could be bigger than the maximum
638 # allowed by GET.
725 # allowed by GET.
639
726
640 text = self.request.POST.get('text')
727 text = self.request.POST.get('text')
641 renderer = self.request.POST.get('renderer') or 'rst'
728 renderer = self.request.POST.get('renderer') or 'rst'
642 if text:
729 if text:
643 return h.render(text, renderer=renderer, mentions=True)
730 return h.render(text, renderer=renderer, mentions=True)
644 return ''
731 return ''
645
732
646 @LoginRequired()
733 @LoginRequired()
647 @CSRFRequired()
734 @CSRFRequired()
648 @view_config(
735 @view_config(
649 route_name='store_user_session_value', request_method='POST',
736 route_name='store_user_session_value', request_method='POST',
650 renderer='string', xhr=True)
737 renderer='string', xhr=True)
651 def store_user_session_attr(self):
738 def store_user_session_attr(self):
652 key = self.request.POST.get('key')
739 key = self.request.POST.get('key')
653 val = self.request.POST.get('val')
740 val = self.request.POST.get('val')
654
741
655 existing_value = self.request.session.get(key)
742 existing_value = self.request.session.get(key)
656 if existing_value != val:
743 if existing_value != val:
657 self.request.session[key] = val
744 self.request.session[key] = val
658
745
659 return 'stored:{}'.format(key)
746 return 'stored:{}'.format(key)
@@ -1,2042 +1,2042 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
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 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20):
153 def shorter(text, size=20):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 return text[:size - len(postfix)] + postfix
156 return text[:size - len(postfix)] + postfix
157 return text
157 return text
158
158
159
159
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
161 """
161 """
162 Reset button
162 Reset button
163 """
163 """
164 _set_input_attrs(attrs, type, name, value)
164 _set_input_attrs(attrs, type, name, value)
165 _set_id_attr(attrs, id, name)
165 _set_id_attr(attrs, id, name)
166 convert_boolean_attrs(attrs, ["disabled"])
166 convert_boolean_attrs(attrs, ["disabled"])
167 return HTML.input(**attrs)
167 return HTML.input(**attrs)
168
168
169 reset = _reset
169 reset = _reset
170 safeid = _make_safe_id_component
170 safeid = _make_safe_id_component
171
171
172
172
173 def branding(name, length=40):
173 def branding(name, length=40):
174 return truncate(name, length, indicator="")
174 return truncate(name, length, indicator="")
175
175
176
176
177 def FID(raw_id, path):
177 def FID(raw_id, path):
178 """
178 """
179 Creates a unique ID for filenode based on it's hash of path and commit
179 Creates a unique ID for filenode based on it's hash of path and commit
180 it's safe to use in urls
180 it's safe to use in urls
181
181
182 :param raw_id:
182 :param raw_id:
183 :param path:
183 :param path:
184 """
184 """
185
185
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
187
187
188
188
189 class _GetError(object):
189 class _GetError(object):
190 """Get error from form_errors, and represent it as span wrapped error
190 """Get error from form_errors, and represent it as span wrapped error
191 message
191 message
192
192
193 :param field_name: field to fetch errors for
193 :param field_name: field to fetch errors for
194 :param form_errors: form errors dict
194 :param form_errors: form errors dict
195 """
195 """
196
196
197 def __call__(self, field_name, form_errors):
197 def __call__(self, field_name, form_errors):
198 tmpl = """<span class="error_msg">%s</span>"""
198 tmpl = """<span class="error_msg">%s</span>"""
199 if form_errors and field_name in form_errors:
199 if form_errors and field_name in form_errors:
200 return literal(tmpl % form_errors.get(field_name))
200 return literal(tmpl % form_errors.get(field_name))
201
201
202 get_error = _GetError()
202 get_error = _GetError()
203
203
204
204
205 class _ToolTip(object):
205 class _ToolTip(object):
206
206
207 def __call__(self, tooltip_title, trim_at=50):
207 def __call__(self, tooltip_title, trim_at=50):
208 """
208 """
209 Special function just to wrap our text into nice formatted
209 Special function just to wrap our text into nice formatted
210 autowrapped text
210 autowrapped text
211
211
212 :param tooltip_title:
212 :param tooltip_title:
213 """
213 """
214 tooltip_title = escape(tooltip_title)
214 tooltip_title = escape(tooltip_title)
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 return tooltip_title
216 return tooltip_title
217 tooltip = _ToolTip()
217 tooltip = _ToolTip()
218
218
219
219
220 def files_breadcrumbs(repo_name, commit_id, file_path):
220 def files_breadcrumbs(repo_name, commit_id, file_path):
221 if isinstance(file_path, str):
221 if isinstance(file_path, str):
222 file_path = safe_unicode(file_path)
222 file_path = safe_unicode(file_path)
223
223
224 # TODO: johbo: Is this always a url like path, or is this operating
224 # TODO: johbo: Is this always a url like path, or is this operating
225 # system dependent?
225 # system dependent?
226 path_segments = file_path.split('/')
226 path_segments = file_path.split('/')
227
227
228 repo_name_html = escape(repo_name)
228 repo_name_html = escape(repo_name)
229 if len(path_segments) == 1 and path_segments[0] == '':
229 if len(path_segments) == 1 and path_segments[0] == '':
230 url_segments = [repo_name_html]
230 url_segments = [repo_name_html]
231 else:
231 else:
232 url_segments = [
232 url_segments = [
233 link_to(
233 link_to(
234 repo_name_html,
234 repo_name_html,
235 route_path(
235 route_path(
236 'repo_files',
236 'repo_files',
237 repo_name=repo_name,
237 repo_name=repo_name,
238 commit_id=commit_id,
238 commit_id=commit_id,
239 f_path=''),
239 f_path=''),
240 class_='pjax-link')]
240 class_='pjax-link')]
241
241
242 last_cnt = len(path_segments) - 1
242 last_cnt = len(path_segments) - 1
243 for cnt, segment in enumerate(path_segments):
243 for cnt, segment in enumerate(path_segments):
244 if not segment:
244 if not segment:
245 continue
245 continue
246 segment_html = escape(segment)
246 segment_html = escape(segment)
247
247
248 if cnt != last_cnt:
248 if cnt != last_cnt:
249 url_segments.append(
249 url_segments.append(
250 link_to(
250 link_to(
251 segment_html,
251 segment_html,
252 route_path(
252 route_path(
253 'repo_files',
253 'repo_files',
254 repo_name=repo_name,
254 repo_name=repo_name,
255 commit_id=commit_id,
255 commit_id=commit_id,
256 f_path='/'.join(path_segments[:cnt + 1])),
256 f_path='/'.join(path_segments[:cnt + 1])),
257 class_='pjax-link'))
257 class_='pjax-link'))
258 else:
258 else:
259 url_segments.append(segment_html)
259 url_segments.append(segment_html)
260
260
261 return literal('/'.join(url_segments))
261 return literal('/'.join(url_segments))
262
262
263
263
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
265 """
265 """
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
267
267
268 If ``outfile`` is given and a valid file object (an object
268 If ``outfile`` is given and a valid file object (an object
269 with a ``write`` method), the result will be written to it, otherwise
269 with a ``write`` method), the result will be written to it, otherwise
270 it is returned as a string.
270 it is returned as a string.
271 """
271 """
272 if use_hl_filter:
272 if use_hl_filter:
273 # add HL filter
273 # add HL filter
274 from rhodecode.lib.index import search_utils
274 from rhodecode.lib.index import search_utils
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
276 return pygments.format(pygments.lex(code, lexer), formatter)
276 return pygments.format(pygments.lex(code, lexer), formatter)
277
277
278
278
279 class CodeHtmlFormatter(HtmlFormatter):
279 class CodeHtmlFormatter(HtmlFormatter):
280 """
280 """
281 My code Html Formatter for source codes
281 My code Html Formatter for source codes
282 """
282 """
283
283
284 def wrap(self, source, outfile):
284 def wrap(self, source, outfile):
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
286
286
287 def _wrap_code(self, source):
287 def _wrap_code(self, source):
288 for cnt, it in enumerate(source):
288 for cnt, it in enumerate(source):
289 i, t = it
289 i, t = it
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
291 yield i, t
291 yield i, t
292
292
293 def _wrap_tablelinenos(self, inner):
293 def _wrap_tablelinenos(self, inner):
294 dummyoutfile = StringIO.StringIO()
294 dummyoutfile = StringIO.StringIO()
295 lncount = 0
295 lncount = 0
296 for t, line in inner:
296 for t, line in inner:
297 if t:
297 if t:
298 lncount += 1
298 lncount += 1
299 dummyoutfile.write(line)
299 dummyoutfile.write(line)
300
300
301 fl = self.linenostart
301 fl = self.linenostart
302 mw = len(str(lncount + fl - 1))
302 mw = len(str(lncount + fl - 1))
303 sp = self.linenospecial
303 sp = self.linenospecial
304 st = self.linenostep
304 st = self.linenostep
305 la = self.lineanchors
305 la = self.lineanchors
306 aln = self.anchorlinenos
306 aln = self.anchorlinenos
307 nocls = self.noclasses
307 nocls = self.noclasses
308 if sp:
308 if sp:
309 lines = []
309 lines = []
310
310
311 for i in range(fl, fl + lncount):
311 for i in range(fl, fl + lncount):
312 if i % st == 0:
312 if i % st == 0:
313 if i % sp == 0:
313 if i % sp == 0:
314 if aln:
314 if aln:
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
316 (la, i, mw, i))
316 (la, i, mw, i))
317 else:
317 else:
318 lines.append('<span class="special">%*d</span>' % (mw, i))
318 lines.append('<span class="special">%*d</span>' % (mw, i))
319 else:
319 else:
320 if aln:
320 if aln:
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
322 else:
322 else:
323 lines.append('%*d' % (mw, i))
323 lines.append('%*d' % (mw, i))
324 else:
324 else:
325 lines.append('')
325 lines.append('')
326 ls = '\n'.join(lines)
326 ls = '\n'.join(lines)
327 else:
327 else:
328 lines = []
328 lines = []
329 for i in range(fl, fl + lncount):
329 for i in range(fl, fl + lncount):
330 if i % st == 0:
330 if i % st == 0:
331 if aln:
331 if aln:
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
333 else:
333 else:
334 lines.append('%*d' % (mw, i))
334 lines.append('%*d' % (mw, i))
335 else:
335 else:
336 lines.append('')
336 lines.append('')
337 ls = '\n'.join(lines)
337 ls = '\n'.join(lines)
338
338
339 # in case you wonder about the seemingly redundant <div> here: since the
339 # in case you wonder about the seemingly redundant <div> here: since the
340 # content in the other cell also is wrapped in a div, some browsers in
340 # content in the other cell also is wrapped in a div, some browsers in
341 # some configurations seem to mess up the formatting...
341 # some configurations seem to mess up the formatting...
342 if nocls:
342 if nocls:
343 yield 0, ('<table class="%stable">' % self.cssclass +
343 yield 0, ('<table class="%stable">' % self.cssclass +
344 '<tr><td><div class="linenodiv" '
344 '<tr><td><div class="linenodiv" '
345 'style="background-color: #f0f0f0; padding-right: 10px">'
345 'style="background-color: #f0f0f0; padding-right: 10px">'
346 '<pre style="line-height: 125%">' +
346 '<pre style="line-height: 125%">' +
347 ls + '</pre></div></td><td id="hlcode" class="code">')
347 ls + '</pre></div></td><td id="hlcode" class="code">')
348 else:
348 else:
349 yield 0, ('<table class="%stable">' % self.cssclass +
349 yield 0, ('<table class="%stable">' % self.cssclass +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
351 ls + '</pre></div></td><td id="hlcode" class="code">')
351 ls + '</pre></div></td><td id="hlcode" class="code">')
352 yield 0, dummyoutfile.getvalue()
352 yield 0, dummyoutfile.getvalue()
353 yield 0, '</td></tr></table>'
353 yield 0, '</td></tr></table>'
354
354
355
355
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
357 def __init__(self, **kw):
357 def __init__(self, **kw):
358 # only show these line numbers if set
358 # only show these line numbers if set
359 self.only_lines = kw.pop('only_line_numbers', [])
359 self.only_lines = kw.pop('only_line_numbers', [])
360 self.query_terms = kw.pop('query_terms', [])
360 self.query_terms = kw.pop('query_terms', [])
361 self.max_lines = kw.pop('max_lines', 5)
361 self.max_lines = kw.pop('max_lines', 5)
362 self.line_context = kw.pop('line_context', 3)
362 self.line_context = kw.pop('line_context', 3)
363 self.url = kw.pop('url', None)
363 self.url = kw.pop('url', None)
364
364
365 super(CodeHtmlFormatter, self).__init__(**kw)
365 super(CodeHtmlFormatter, self).__init__(**kw)
366
366
367 def _wrap_code(self, source):
367 def _wrap_code(self, source):
368 for cnt, it in enumerate(source):
368 for cnt, it in enumerate(source):
369 i, t = it
369 i, t = it
370 t = '<pre>%s</pre>' % t
370 t = '<pre>%s</pre>' % t
371 yield i, t
371 yield i, t
372
372
373 def _wrap_tablelinenos(self, inner):
373 def _wrap_tablelinenos(self, inner):
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
375
375
376 last_shown_line_number = 0
376 last_shown_line_number = 0
377 current_line_number = 1
377 current_line_number = 1
378
378
379 for t, line in inner:
379 for t, line in inner:
380 if not t:
380 if not t:
381 yield t, line
381 yield t, line
382 continue
382 continue
383
383
384 if current_line_number in self.only_lines:
384 if current_line_number in self.only_lines:
385 if last_shown_line_number + 1 != current_line_number:
385 if last_shown_line_number + 1 != current_line_number:
386 yield 0, '<tr>'
386 yield 0, '<tr>'
387 yield 0, '<td class="line">...</td>'
387 yield 0, '<td class="line">...</td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
389 yield 0, '</tr>'
389 yield 0, '</tr>'
390
390
391 yield 0, '<tr>'
391 yield 0, '<tr>'
392 if self.url:
392 if self.url:
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
394 self.url, current_line_number, current_line_number)
394 self.url, current_line_number, current_line_number)
395 else:
395 else:
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
397 current_line_number)
397 current_line_number)
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
399 yield 0, '</tr>'
399 yield 0, '</tr>'
400
400
401 last_shown_line_number = current_line_number
401 last_shown_line_number = current_line_number
402
402
403 current_line_number += 1
403 current_line_number += 1
404
404
405 yield 0, '</table>'
405 yield 0, '</table>'
406
406
407
407
408 def hsv_to_rgb(h, s, v):
408 def hsv_to_rgb(h, s, v):
409 """ Convert hsv color values to rgb """
409 """ Convert hsv color values to rgb """
410
410
411 if s == 0.0:
411 if s == 0.0:
412 return v, v, v
412 return v, v, v
413 i = int(h * 6.0) # XXX assume int() truncates!
413 i = int(h * 6.0) # XXX assume int() truncates!
414 f = (h * 6.0) - i
414 f = (h * 6.0) - i
415 p = v * (1.0 - s)
415 p = v * (1.0 - s)
416 q = v * (1.0 - s * f)
416 q = v * (1.0 - s * f)
417 t = v * (1.0 - s * (1.0 - f))
417 t = v * (1.0 - s * (1.0 - f))
418 i = i % 6
418 i = i % 6
419 if i == 0:
419 if i == 0:
420 return v, t, p
420 return v, t, p
421 if i == 1:
421 if i == 1:
422 return q, v, p
422 return q, v, p
423 if i == 2:
423 if i == 2:
424 return p, v, t
424 return p, v, t
425 if i == 3:
425 if i == 3:
426 return p, q, v
426 return p, q, v
427 if i == 4:
427 if i == 4:
428 return t, p, v
428 return t, p, v
429 if i == 5:
429 if i == 5:
430 return v, p, q
430 return v, p, q
431
431
432
432
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
434 """
434 """
435 Generator for getting n of evenly distributed colors using
435 Generator for getting n of evenly distributed colors using
436 hsv color and golden ratio. It always return same order of colors
436 hsv color and golden ratio. It always return same order of colors
437
437
438 :param n: number of colors to generate
438 :param n: number of colors to generate
439 :param saturation: saturation of returned colors
439 :param saturation: saturation of returned colors
440 :param lightness: lightness of returned colors
440 :param lightness: lightness of returned colors
441 :returns: RGB tuple
441 :returns: RGB tuple
442 """
442 """
443
443
444 golden_ratio = 0.618033988749895
444 golden_ratio = 0.618033988749895
445 h = 0.22717784590367374
445 h = 0.22717784590367374
446
446
447 for _ in xrange(n):
447 for _ in xrange(n):
448 h += golden_ratio
448 h += golden_ratio
449 h %= 1
449 h %= 1
450 HSV_tuple = [h, saturation, lightness]
450 HSV_tuple = [h, saturation, lightness]
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
453
453
454
454
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
456 """
456 """
457 Returns a function which when called with an argument returns a unique
457 Returns a function which when called with an argument returns a unique
458 color for that argument, eg.
458 color for that argument, eg.
459
459
460 :param n: number of colors to generate
460 :param n: number of colors to generate
461 :param saturation: saturation of returned colors
461 :param saturation: saturation of returned colors
462 :param lightness: lightness of returned colors
462 :param lightness: lightness of returned colors
463 :returns: css RGB string
463 :returns: css RGB string
464
464
465 >>> color_hash = color_hasher()
465 >>> color_hash = color_hasher()
466 >>> color_hash('hello')
466 >>> color_hash('hello')
467 'rgb(34, 12, 59)'
467 'rgb(34, 12, 59)'
468 >>> color_hash('hello')
468 >>> color_hash('hello')
469 'rgb(34, 12, 59)'
469 'rgb(34, 12, 59)'
470 >>> color_hash('other')
470 >>> color_hash('other')
471 'rgb(90, 224, 159)'
471 'rgb(90, 224, 159)'
472 """
472 """
473
473
474 color_dict = {}
474 color_dict = {}
475 cgenerator = unique_color_generator(
475 cgenerator = unique_color_generator(
476 saturation=saturation, lightness=lightness)
476 saturation=saturation, lightness=lightness)
477
477
478 def get_color_string(thing):
478 def get_color_string(thing):
479 if thing in color_dict:
479 if thing in color_dict:
480 col = color_dict[thing]
480 col = color_dict[thing]
481 else:
481 else:
482 col = color_dict[thing] = cgenerator.next()
482 col = color_dict[thing] = cgenerator.next()
483 return "rgb(%s)" % (', '.join(col))
483 return "rgb(%s)" % (', '.join(col))
484
484
485 return get_color_string
485 return get_color_string
486
486
487
487
488 def get_lexer_safe(mimetype=None, filepath=None):
488 def get_lexer_safe(mimetype=None, filepath=None):
489 """
489 """
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
491 defaulting to plain text if none could be found
491 defaulting to plain text if none could be found
492 """
492 """
493 lexer = None
493 lexer = None
494 try:
494 try:
495 if mimetype:
495 if mimetype:
496 lexer = get_lexer_for_mimetype(mimetype)
496 lexer = get_lexer_for_mimetype(mimetype)
497 if not lexer:
497 if not lexer:
498 lexer = get_lexer_for_filename(filepath)
498 lexer = get_lexer_for_filename(filepath)
499 except pygments.util.ClassNotFound:
499 except pygments.util.ClassNotFound:
500 pass
500 pass
501
501
502 if not lexer:
502 if not lexer:
503 lexer = get_lexer_by_name('text')
503 lexer = get_lexer_by_name('text')
504
504
505 return lexer
505 return lexer
506
506
507
507
508 def get_lexer_for_filenode(filenode):
508 def get_lexer_for_filenode(filenode):
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
510 return lexer
510 return lexer
511
511
512
512
513 def pygmentize(filenode, **kwargs):
513 def pygmentize(filenode, **kwargs):
514 """
514 """
515 pygmentize function using pygments
515 pygmentize function using pygments
516
516
517 :param filenode:
517 :param filenode:
518 """
518 """
519 lexer = get_lexer_for_filenode(filenode)
519 lexer = get_lexer_for_filenode(filenode)
520 return literal(code_highlight(filenode.content, lexer,
520 return literal(code_highlight(filenode.content, lexer,
521 CodeHtmlFormatter(**kwargs)))
521 CodeHtmlFormatter(**kwargs)))
522
522
523
523
524 def is_following_repo(repo_name, user_id):
524 def is_following_repo(repo_name, user_id):
525 from rhodecode.model.scm import ScmModel
525 from rhodecode.model.scm import ScmModel
526 return ScmModel().is_following_repo(repo_name, user_id)
526 return ScmModel().is_following_repo(repo_name, user_id)
527
527
528
528
529 class _Message(object):
529 class _Message(object):
530 """A message returned by ``Flash.pop_messages()``.
530 """A message returned by ``Flash.pop_messages()``.
531
531
532 Converting the message to a string returns the message text. Instances
532 Converting the message to a string returns the message text. Instances
533 also have the following attributes:
533 also have the following attributes:
534
534
535 * ``message``: the message text.
535 * ``message``: the message text.
536 * ``category``: the category specified when the message was created.
536 * ``category``: the category specified when the message was created.
537 """
537 """
538
538
539 def __init__(self, category, message):
539 def __init__(self, category, message):
540 self.category = category
540 self.category = category
541 self.message = message
541 self.message = message
542
542
543 def __str__(self):
543 def __str__(self):
544 return self.message
544 return self.message
545
545
546 __unicode__ = __str__
546 __unicode__ = __str__
547
547
548 def __html__(self):
548 def __html__(self):
549 return escape(safe_unicode(self.message))
549 return escape(safe_unicode(self.message))
550
550
551
551
552 class Flash(object):
552 class Flash(object):
553 # List of allowed categories. If None, allow any category.
553 # List of allowed categories. If None, allow any category.
554 categories = ["warning", "notice", "error", "success"]
554 categories = ["warning", "notice", "error", "success"]
555
555
556 # Default category if none is specified.
556 # Default category if none is specified.
557 default_category = "notice"
557 default_category = "notice"
558
558
559 def __init__(self, session_key="flash", categories=None,
559 def __init__(self, session_key="flash", categories=None,
560 default_category=None):
560 default_category=None):
561 """
561 """
562 Instantiate a ``Flash`` object.
562 Instantiate a ``Flash`` object.
563
563
564 ``session_key`` is the key to save the messages under in the user's
564 ``session_key`` is the key to save the messages under in the user's
565 session.
565 session.
566
566
567 ``categories`` is an optional list which overrides the default list
567 ``categories`` is an optional list which overrides the default list
568 of categories.
568 of categories.
569
569
570 ``default_category`` overrides the default category used for messages
570 ``default_category`` overrides the default category used for messages
571 when none is specified.
571 when none is specified.
572 """
572 """
573 self.session_key = session_key
573 self.session_key = session_key
574 if categories is not None:
574 if categories is not None:
575 self.categories = categories
575 self.categories = categories
576 if default_category is not None:
576 if default_category is not None:
577 self.default_category = default_category
577 self.default_category = default_category
578 if self.categories and self.default_category not in self.categories:
578 if self.categories and self.default_category not in self.categories:
579 raise ValueError(
579 raise ValueError(
580 "unrecognized default category %r" % (self.default_category,))
580 "unrecognized default category %r" % (self.default_category,))
581
581
582 def pop_messages(self, session=None, request=None):
582 def pop_messages(self, session=None, request=None):
583 """
583 """
584 Return all accumulated messages and delete them from the session.
584 Return all accumulated messages and delete them from the session.
585
585
586 The return value is a list of ``Message`` objects.
586 The return value is a list of ``Message`` objects.
587 """
587 """
588 messages = []
588 messages = []
589
589
590 if not session:
590 if not session:
591 if not request:
591 if not request:
592 request = get_current_request()
592 request = get_current_request()
593 session = request.session
593 session = request.session
594
594
595 # Pop the 'old' pylons flash messages. They are tuples of the form
595 # Pop the 'old' pylons flash messages. They are tuples of the form
596 # (category, message)
596 # (category, message)
597 for cat, msg in session.pop(self.session_key, []):
597 for cat, msg in session.pop(self.session_key, []):
598 messages.append(_Message(cat, msg))
598 messages.append(_Message(cat, msg))
599
599
600 # Pop the 'new' pyramid flash messages for each category as list
600 # Pop the 'new' pyramid flash messages for each category as list
601 # of strings.
601 # of strings.
602 for cat in self.categories:
602 for cat in self.categories:
603 for msg in session.pop_flash(queue=cat):
603 for msg in session.pop_flash(queue=cat):
604 messages.append(_Message(cat, msg))
604 messages.append(_Message(cat, msg))
605 # Map messages from the default queue to the 'notice' category.
605 # Map messages from the default queue to the 'notice' category.
606 for msg in session.pop_flash():
606 for msg in session.pop_flash():
607 messages.append(_Message('notice', msg))
607 messages.append(_Message('notice', msg))
608
608
609 session.save()
609 session.save()
610 return messages
610 return messages
611
611
612 def json_alerts(self, session=None, request=None):
612 def json_alerts(self, session=None, request=None):
613 payloads = []
613 payloads = []
614 messages = flash.pop_messages(session=session, request=request)
614 messages = flash.pop_messages(session=session, request=request)
615 if messages:
615 if messages:
616 for message in messages:
616 for message in messages:
617 subdata = {}
617 subdata = {}
618 if hasattr(message.message, 'rsplit'):
618 if hasattr(message.message, 'rsplit'):
619 flash_data = message.message.rsplit('|DELIM|', 1)
619 flash_data = message.message.rsplit('|DELIM|', 1)
620 org_message = flash_data[0]
620 org_message = flash_data[0]
621 if len(flash_data) > 1:
621 if len(flash_data) > 1:
622 subdata = json.loads(flash_data[1])
622 subdata = json.loads(flash_data[1])
623 else:
623 else:
624 org_message = message.message
624 org_message = message.message
625 payloads.append({
625 payloads.append({
626 'message': {
626 'message': {
627 'message': u'{}'.format(org_message),
627 'message': u'{}'.format(org_message),
628 'level': message.category,
628 'level': message.category,
629 'force': True,
629 'force': True,
630 'subdata': subdata
630 'subdata': subdata
631 }
631 }
632 })
632 })
633 return json.dumps(payloads)
633 return json.dumps(payloads)
634
634
635 def __call__(self, message, category=None, ignore_duplicate=False,
635 def __call__(self, message, category=None, ignore_duplicate=False,
636 session=None, request=None):
636 session=None, request=None):
637
637
638 if not session:
638 if not session:
639 if not request:
639 if not request:
640 request = get_current_request()
640 request = get_current_request()
641 session = request.session
641 session = request.session
642
642
643 session.flash(
643 session.flash(
644 message, queue=category, allow_duplicate=not ignore_duplicate)
644 message, queue=category, allow_duplicate=not ignore_duplicate)
645
645
646
646
647 flash = Flash()
647 flash = Flash()
648
648
649 #==============================================================================
649 #==============================================================================
650 # SCM FILTERS available via h.
650 # SCM FILTERS available via h.
651 #==============================================================================
651 #==============================================================================
652 from rhodecode.lib.vcs.utils import author_name, author_email
652 from rhodecode.lib.vcs.utils import author_name, author_email
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
654 from rhodecode.model.db import User, ChangesetStatus
654 from rhodecode.model.db import User, ChangesetStatus
655
655
656 capitalize = lambda x: x.capitalize()
656 capitalize = lambda x: x.capitalize()
657 email = author_email
657 email = author_email
658 short_id = lambda x: x[:12]
658 short_id = lambda x: x[:12]
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
660
660
661
661
662 import pytz
662 import pytz
663 import tzlocal
663 import tzlocal
664 local_timezone = tzlocal.get_localzone()
664 local_timezone = tzlocal.get_localzone()
665
665
666
666
667 def age_component(datetime_iso, value=None, time_is_local=False):
667 def age_component(datetime_iso, value=None, time_is_local=False):
668 title = value or format_date(datetime_iso)
668 title = value or format_date(datetime_iso)
669 tzinfo = '+00:00'
669 tzinfo = '+00:00'
670
670
671 # detect if we have a timezone info, otherwise, add it
671 # detect if we have a timezone info, otherwise, add it
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
674 if force_timezone:
674 if force_timezone:
675 force_timezone = pytz.timezone(force_timezone)
675 force_timezone = pytz.timezone(force_timezone)
676 timezone = force_timezone or local_timezone
676 timezone = force_timezone or local_timezone
677 offset = timezone.localize(datetime_iso).strftime('%z')
677 offset = timezone.localize(datetime_iso).strftime('%z')
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
679
679
680 return literal(
680 return literal(
681 '<time class="timeago tooltip" '
681 '<time class="timeago tooltip" '
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
683 datetime_iso, title, tzinfo))
683 datetime_iso, title, tzinfo))
684
684
685
685
686 def _shorten_commit_id(commit_id, commit_len=None):
686 def _shorten_commit_id(commit_id, commit_len=None):
687 if commit_len is None:
687 if commit_len is None:
688 request = get_current_request()
688 request = get_current_request()
689 commit_len = request.call_context.visual.show_sha_length
689 commit_len = request.call_context.visual.show_sha_length
690 return commit_id[:commit_len]
690 return commit_id[:commit_len]
691
691
692
692
693 def show_id(commit, show_idx=None, commit_len=None):
693 def show_id(commit, show_idx=None, commit_len=None):
694 """
694 """
695 Configurable function that shows ID
695 Configurable function that shows ID
696 by default it's r123:fffeeefffeee
696 by default it's r123:fffeeefffeee
697
697
698 :param commit: commit instance
698 :param commit: commit instance
699 """
699 """
700 if show_idx is None:
700 if show_idx is None:
701 request = get_current_request()
701 request = get_current_request()
702 show_idx = request.call_context.visual.show_revision_number
702 show_idx = request.call_context.visual.show_revision_number
703
703
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
705 if show_idx:
705 if show_idx:
706 return 'r%s:%s' % (commit.idx, raw_id)
706 return 'r%s:%s' % (commit.idx, raw_id)
707 else:
707 else:
708 return '%s' % (raw_id, )
708 return '%s' % (raw_id, )
709
709
710
710
711 def format_date(date):
711 def format_date(date):
712 """
712 """
713 use a standardized formatting for dates used in RhodeCode
713 use a standardized formatting for dates used in RhodeCode
714
714
715 :param date: date/datetime object
715 :param date: date/datetime object
716 :return: formatted date
716 :return: formatted date
717 """
717 """
718
718
719 if date:
719 if date:
720 _fmt = "%a, %d %b %Y %H:%M:%S"
720 _fmt = "%a, %d %b %Y %H:%M:%S"
721 return safe_unicode(date.strftime(_fmt))
721 return safe_unicode(date.strftime(_fmt))
722
722
723 return u""
723 return u""
724
724
725
725
726 class _RepoChecker(object):
726 class _RepoChecker(object):
727
727
728 def __init__(self, backend_alias):
728 def __init__(self, backend_alias):
729 self._backend_alias = backend_alias
729 self._backend_alias = backend_alias
730
730
731 def __call__(self, repository):
731 def __call__(self, repository):
732 if hasattr(repository, 'alias'):
732 if hasattr(repository, 'alias'):
733 _type = repository.alias
733 _type = repository.alias
734 elif hasattr(repository, 'repo_type'):
734 elif hasattr(repository, 'repo_type'):
735 _type = repository.repo_type
735 _type = repository.repo_type
736 else:
736 else:
737 _type = repository
737 _type = repository
738 return _type == self._backend_alias
738 return _type == self._backend_alias
739
739
740
740
741 is_git = _RepoChecker('git')
741 is_git = _RepoChecker('git')
742 is_hg = _RepoChecker('hg')
742 is_hg = _RepoChecker('hg')
743 is_svn = _RepoChecker('svn')
743 is_svn = _RepoChecker('svn')
744
744
745
745
746 def get_repo_type_by_name(repo_name):
746 def get_repo_type_by_name(repo_name):
747 repo = Repository.get_by_repo_name(repo_name)
747 repo = Repository.get_by_repo_name(repo_name)
748 if repo:
748 if repo:
749 return repo.repo_type
749 return repo.repo_type
750
750
751
751
752 def is_svn_without_proxy(repository):
752 def is_svn_without_proxy(repository):
753 if is_svn(repository):
753 if is_svn(repository):
754 from rhodecode.model.settings import VcsSettingsModel
754 from rhodecode.model.settings import VcsSettingsModel
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
757 return False
757 return False
758
758
759
759
760 def discover_user(author):
760 def discover_user(author):
761 """
761 """
762 Tries to discover RhodeCode User based on the autho string. Author string
762 Tries to discover RhodeCode User based on the autho string. Author string
763 is typically `FirstName LastName <email@address.com>`
763 is typically `FirstName LastName <email@address.com>`
764 """
764 """
765
765
766 # if author is already an instance use it for extraction
766 # if author is already an instance use it for extraction
767 if isinstance(author, User):
767 if isinstance(author, User):
768 return author
768 return author
769
769
770 # Valid email in the attribute passed, see if they're in the system
770 # Valid email in the attribute passed, see if they're in the system
771 _email = author_email(author)
771 _email = author_email(author)
772 if _email != '':
772 if _email != '':
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
774 if user is not None:
774 if user is not None:
775 return user
775 return user
776
776
777 # Maybe it's a username, we try to extract it and fetch by username ?
777 # Maybe it's a username, we try to extract it and fetch by username ?
778 _author = author_name(author)
778 _author = author_name(author)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
780 if user is not None:
780 if user is not None:
781 return user
781 return user
782
782
783 return None
783 return None
784
784
785
785
786 def email_or_none(author):
786 def email_or_none(author):
787 # extract email from the commit string
787 # extract email from the commit string
788 _email = author_email(author)
788 _email = author_email(author)
789
789
790 # If we have an email, use it, otherwise
790 # If we have an email, use it, otherwise
791 # see if it contains a username we can get an email from
791 # see if it contains a username we can get an email from
792 if _email != '':
792 if _email != '':
793 return _email
793 return _email
794 else:
794 else:
795 user = User.get_by_username(
795 user = User.get_by_username(
796 author_name(author), case_insensitive=True, cache=True)
796 author_name(author), case_insensitive=True, cache=True)
797
797
798 if user is not None:
798 if user is not None:
799 return user.email
799 return user.email
800
800
801 # No valid email, not a valid user in the system, none!
801 # No valid email, not a valid user in the system, none!
802 return None
802 return None
803
803
804
804
805 def link_to_user(author, length=0, **kwargs):
805 def link_to_user(author, length=0, **kwargs):
806 user = discover_user(author)
806 user = discover_user(author)
807 # user can be None, but if we have it already it means we can re-use it
807 # user can be None, but if we have it already it means we can re-use it
808 # in the person() function, so we save 1 intensive-query
808 # in the person() function, so we save 1 intensive-query
809 if user:
809 if user:
810 author = user
810 author = user
811
811
812 display_person = person(author, 'username_or_name_or_email')
812 display_person = person(author, 'username_or_name_or_email')
813 if length:
813 if length:
814 display_person = shorter(display_person, length)
814 display_person = shorter(display_person, length)
815
815
816 if user:
816 if user:
817 return link_to(
817 return link_to(
818 escape(display_person),
818 escape(display_person),
819 route_path('user_profile', username=user.username),
819 route_path('user_profile', username=user.username),
820 **kwargs)
820 **kwargs)
821 else:
821 else:
822 return escape(display_person)
822 return escape(display_person)
823
823
824
824
825 def link_to_group(users_group_name, **kwargs):
825 def link_to_group(users_group_name, **kwargs):
826 return link_to(
826 return link_to(
827 escape(users_group_name),
827 escape(users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
829 **kwargs)
829 **kwargs)
830
830
831
831
832 def person(author, show_attr="username_and_name"):
832 def person(author, show_attr="username_and_name"):
833 user = discover_user(author)
833 user = discover_user(author)
834 if user:
834 if user:
835 return getattr(user, show_attr)
835 return getattr(user, show_attr)
836 else:
836 else:
837 _author = author_name(author)
837 _author = author_name(author)
838 _email = email(author)
838 _email = email(author)
839 return _author or _email
839 return _author or _email
840
840
841
841
842 def author_string(email):
842 def author_string(email):
843 if email:
843 if email:
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
845 if user:
845 if user:
846 if user.first_name or user.last_name:
846 if user.first_name or user.last_name:
847 return '%s %s &lt;%s&gt;' % (
847 return '%s %s &lt;%s&gt;' % (
848 user.first_name, user.last_name, email)
848 user.first_name, user.last_name, email)
849 else:
849 else:
850 return email
850 return email
851 else:
851 else:
852 return email
852 return email
853 else:
853 else:
854 return None
854 return None
855
855
856
856
857 def person_by_id(id_, show_attr="username_and_name"):
857 def person_by_id(id_, show_attr="username_and_name"):
858 # attr to return from fetched user
858 # attr to return from fetched user
859 person_getter = lambda usr: getattr(usr, show_attr)
859 person_getter = lambda usr: getattr(usr, show_attr)
860
860
861 #maybe it's an ID ?
861 #maybe it's an ID ?
862 if str(id_).isdigit() or isinstance(id_, int):
862 if str(id_).isdigit() or isinstance(id_, int):
863 id_ = int(id_)
863 id_ = int(id_)
864 user = User.get(id_)
864 user = User.get(id_)
865 if user is not None:
865 if user is not None:
866 return person_getter(user)
866 return person_getter(user)
867 return id_
867 return id_
868
868
869
869
870 def gravatar_with_user(request, author, show_disabled=False):
870 def gravatar_with_user(request, author, show_disabled=False):
871 _render = request.get_partial_renderer(
871 _render = request.get_partial_renderer(
872 'rhodecode:templates/base/base.mako')
872 'rhodecode:templates/base/base.mako')
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
874
874
875
875
876 tags_paterns = OrderedDict((
876 tags_paterns = OrderedDict((
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
878 '<div class="metatag" tag="lang">\\2</div>')),
878 '<div class="metatag" tag="lang">\\2</div>')),
879
879
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
882
882
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
885
885
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
888
888
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
891
891
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
894
894
895 # label in grey
895 # label in grey
896 ('label', (re.compile(r'\[([a-z]+)\]'),
896 ('label', (re.compile(r'\[([a-z]+)\]'),
897 '<div class="metatag" tag="label">\\1</div>')),
897 '<div class="metatag" tag="label">\\1</div>')),
898
898
899 # generic catch all in grey
899 # generic catch all in grey
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
901 '<div class="metatag" tag="generic">\\1</div>')),
901 '<div class="metatag" tag="generic">\\1</div>')),
902 ))
902 ))
903
903
904
904
905 def extract_metatags(value):
905 def extract_metatags(value):
906 """
906 """
907 Extract supported meta-tags from given text value
907 Extract supported meta-tags from given text value
908 """
908 """
909 tags = []
909 tags = []
910 if not value:
910 if not value:
911 return tags, ''
911 return tags, ''
912
912
913 for key, val in tags_paterns.items():
913 for key, val in tags_paterns.items():
914 pat, replace_html = val
914 pat, replace_html = val
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
916 value = pat.sub('', value)
916 value = pat.sub('', value)
917
917
918 return tags, value
918 return tags, value
919
919
920
920
921 def style_metatag(tag_type, value):
921 def style_metatag(tag_type, value):
922 """
922 """
923 converts tags from value into html equivalent
923 converts tags from value into html equivalent
924 """
924 """
925 if not value:
925 if not value:
926 return ''
926 return ''
927
927
928 html_value = value
928 html_value = value
929 tag_data = tags_paterns.get(tag_type)
929 tag_data = tags_paterns.get(tag_type)
930 if tag_data:
930 if tag_data:
931 pat, replace_html = tag_data
931 pat, replace_html = tag_data
932 # convert to plain `unicode` instead of a markup tag to be used in
932 # convert to plain `unicode` instead of a markup tag to be used in
933 # regex expressions. safe_unicode doesn't work here
933 # regex expressions. safe_unicode doesn't work here
934 html_value = pat.sub(replace_html, unicode(value))
934 html_value = pat.sub(replace_html, unicode(value))
935
935
936 return html_value
936 return html_value
937
937
938
938
939 def bool2icon(value, show_at_false=True):
939 def bool2icon(value, show_at_false=True):
940 """
940 """
941 Returns boolean value of a given value, represented as html element with
941 Returns boolean value of a given value, represented as html element with
942 classes that will represent icons
942 classes that will represent icons
943
943
944 :param value: given value to convert to html node
944 :param value: given value to convert to html node
945 """
945 """
946
946
947 if value: # does bool conversion
947 if value: # does bool conversion
948 return HTML.tag('i', class_="icon-true")
948 return HTML.tag('i', class_="icon-true")
949 else: # not true as bool
949 else: # not true as bool
950 if show_at_false:
950 if show_at_false:
951 return HTML.tag('i', class_="icon-false")
951 return HTML.tag('i', class_="icon-false")
952 return HTML.tag('i')
952 return HTML.tag('i')
953
953
954 #==============================================================================
954 #==============================================================================
955 # PERMS
955 # PERMS
956 #==============================================================================
956 #==============================================================================
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
960 csrf_token_key
960 csrf_token_key
961
961
962
962
963 #==============================================================================
963 #==============================================================================
964 # GRAVATAR URL
964 # GRAVATAR URL
965 #==============================================================================
965 #==============================================================================
966 class InitialsGravatar(object):
966 class InitialsGravatar(object):
967 def __init__(self, email_address, first_name, last_name, size=30,
967 def __init__(self, email_address, first_name, last_name, size=30,
968 background=None, text_color='#fff'):
968 background=None, text_color='#fff'):
969 self.size = size
969 self.size = size
970 self.first_name = first_name
970 self.first_name = first_name
971 self.last_name = last_name
971 self.last_name = last_name
972 self.email_address = email_address
972 self.email_address = email_address
973 self.background = background or self.str2color(email_address)
973 self.background = background or self.str2color(email_address)
974 self.text_color = text_color
974 self.text_color = text_color
975
975
976 def get_color_bank(self):
976 def get_color_bank(self):
977 """
977 """
978 returns a predefined list of colors that gravatars can use.
978 returns a predefined list of colors that gravatars can use.
979 Those are randomized distinct colors that guarantee readability and
979 Those are randomized distinct colors that guarantee readability and
980 uniqueness.
980 uniqueness.
981
981
982 generated with: http://phrogz.net/css/distinct-colors.html
982 generated with: http://phrogz.net/css/distinct-colors.html
983 """
983 """
984 return [
984 return [
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1029 '#4f8c46', '#368dd9', '#5c0073'
1029 '#4f8c46', '#368dd9', '#5c0073'
1030 ]
1030 ]
1031
1031
1032 def rgb_to_hex_color(self, rgb_tuple):
1032 def rgb_to_hex_color(self, rgb_tuple):
1033 """
1033 """
1034 Converts an rgb_tuple passed to an hex color.
1034 Converts an rgb_tuple passed to an hex color.
1035
1035
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1037 """
1037 """
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1039
1039
1040 def email_to_int_list(self, email_str):
1040 def email_to_int_list(self, email_str):
1041 """
1041 """
1042 Get every byte of the hex digest value of email and turn it to integer.
1042 Get every byte of the hex digest value of email and turn it to integer.
1043 It's going to be always between 0-255
1043 It's going to be always between 0-255
1044 """
1044 """
1045 digest = md5_safe(email_str.lower())
1045 digest = md5_safe(email_str.lower())
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1047
1047
1048 def pick_color_bank_index(self, email_str, color_bank):
1048 def pick_color_bank_index(self, email_str, color_bank):
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1050
1050
1051 def str2color(self, email_str):
1051 def str2color(self, email_str):
1052 """
1052 """
1053 Tries to map in a stable algorithm an email to color
1053 Tries to map in a stable algorithm an email to color
1054
1054
1055 :param email_str:
1055 :param email_str:
1056 """
1056 """
1057 color_bank = self.get_color_bank()
1057 color_bank = self.get_color_bank()
1058 # pick position (module it's length so we always find it in the
1058 # pick position (module it's length so we always find it in the
1059 # bank even if it's smaller than 256 values
1059 # bank even if it's smaller than 256 values
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1061 return color_bank[pos]
1061 return color_bank[pos]
1062
1062
1063 def normalize_email(self, email_address):
1063 def normalize_email(self, email_address):
1064 import unicodedata
1064 import unicodedata
1065 # default host used to fill in the fake/missing email
1065 # default host used to fill in the fake/missing email
1066 default_host = u'localhost'
1066 default_host = u'localhost'
1067
1067
1068 if not email_address:
1068 if not email_address:
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1070
1070
1071 email_address = safe_unicode(email_address)
1071 email_address = safe_unicode(email_address)
1072
1072
1073 if u'@' not in email_address:
1073 if u'@' not in email_address:
1074 email_address = u'%s@%s' % (email_address, default_host)
1074 email_address = u'%s@%s' % (email_address, default_host)
1075
1075
1076 if email_address.endswith(u'@'):
1076 if email_address.endswith(u'@'):
1077 email_address = u'%s%s' % (email_address, default_host)
1077 email_address = u'%s%s' % (email_address, default_host)
1078
1078
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1080 .encode('ascii', 'ignore')
1080 .encode('ascii', 'ignore')
1081 return email_address
1081 return email_address
1082
1082
1083 def get_initials(self):
1083 def get_initials(self):
1084 """
1084 """
1085 Returns 2 letter initials calculated based on the input.
1085 Returns 2 letter initials calculated based on the input.
1086 The algorithm picks first given email address, and takes first letter
1086 The algorithm picks first given email address, and takes first letter
1087 of part before @, and then the first letter of server name. In case
1087 of part before @, and then the first letter of server name. In case
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1089 the server letter with first letter of somestring2
1089 the server letter with first letter of somestring2
1090
1090
1091 In case function was initialized with both first and lastname, this
1091 In case function was initialized with both first and lastname, this
1092 overrides the extraction from email by first letter of the first and
1092 overrides the extraction from email by first letter of the first and
1093 last name. We add special logic to that functionality, In case Full name
1093 last name. We add special logic to that functionality, In case Full name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1095 (Von Rossum) picking `R`.
1095 (Von Rossum) picking `R`.
1096
1096
1097 Function also normalizes the non-ascii characters to they ascii
1097 Function also normalizes the non-ascii characters to they ascii
1098 representation, eg Δ„ => A
1098 representation, eg Δ„ => A
1099 """
1099 """
1100 import unicodedata
1100 import unicodedata
1101 # replace non-ascii to ascii
1101 # replace non-ascii to ascii
1102 first_name = unicodedata.normalize(
1102 first_name = unicodedata.normalize(
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1104 last_name = unicodedata.normalize(
1104 last_name = unicodedata.normalize(
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1106
1106
1107 # do NFKD encoding, and also make sure email has proper format
1107 # do NFKD encoding, and also make sure email has proper format
1108 email_address = self.normalize_email(self.email_address)
1108 email_address = self.normalize_email(self.email_address)
1109
1109
1110 # first push the email initials
1110 # first push the email initials
1111 prefix, server = email_address.split('@', 1)
1111 prefix, server = email_address.split('@', 1)
1112
1112
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1114 _dot_split = prefix.rsplit('.', 1)
1114 _dot_split = prefix.rsplit('.', 1)
1115 if len(_dot_split) == 2 and _dot_split[1]:
1115 if len(_dot_split) == 2 and _dot_split[1]:
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1117 else:
1117 else:
1118 initials = [prefix[0], server[0]]
1118 initials = [prefix[0], server[0]]
1119
1119
1120 # then try to replace either first_name or last_name
1120 # then try to replace either first_name or last_name
1121 fn_letter = (first_name or " ")[0].strip()
1121 fn_letter = (first_name or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1123
1123
1124 if fn_letter:
1124 if fn_letter:
1125 initials[0] = fn_letter
1125 initials[0] = fn_letter
1126
1126
1127 if ln_letter:
1127 if ln_letter:
1128 initials[1] = ln_letter
1128 initials[1] = ln_letter
1129
1129
1130 return ''.join(initials).upper()
1130 return ''.join(initials).upper()
1131
1131
1132 def get_img_data_by_type(self, font_family, img_type):
1132 def get_img_data_by_type(self, font_family, img_type):
1133 default_user = """
1133 default_user = """
1134 <svg xmlns="http://www.w3.org/2000/svg"
1134 <svg xmlns="http://www.w3.org/2000/svg"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1136 viewBox="-15 -10 439.165 429.164"
1136 viewBox="-15 -10 439.165 429.164"
1137
1137
1138 xml:space="preserve"
1138 xml:space="preserve"
1139 style="background:{background};" >
1139 style="background:{background};" >
1140
1140
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1144 168.596,153.916,216.671,
1144 168.596,153.916,216.671,
1145 204.583,216.671z" fill="{text_color}"/>
1145 204.583,216.671z" fill="{text_color}"/>
1146 <path d="M407.164,374.717L360.88,
1146 <path d="M407.164,374.717L360.88,
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1150 0-48.762-8.122-69.078-23.488
1150 0-48.762-8.122-69.078-23.488
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1155 19.402-10.527 C409.699,390.129,
1155 19.402-10.527 C409.699,390.129,
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1157 </svg>""".format(
1157 </svg>""".format(
1158 size=self.size,
1158 size=self.size,
1159 background='#979797', # @grey4
1159 background='#979797', # @grey4
1160 text_color=self.text_color,
1160 text_color=self.text_color,
1161 font_family=font_family)
1161 font_family=font_family)
1162
1162
1163 return {
1163 return {
1164 "default_user": default_user
1164 "default_user": default_user
1165 }[img_type]
1165 }[img_type]
1166
1166
1167 def get_img_data(self, svg_type=None):
1167 def get_img_data(self, svg_type=None):
1168 """
1168 """
1169 generates the svg metadata for image
1169 generates the svg metadata for image
1170 """
1170 """
1171 fonts = [
1171 fonts = [
1172 '-apple-system',
1172 '-apple-system',
1173 'BlinkMacSystemFont',
1173 'BlinkMacSystemFont',
1174 'Segoe UI',
1174 'Segoe UI',
1175 'Roboto',
1175 'Roboto',
1176 'Oxygen-Sans',
1176 'Oxygen-Sans',
1177 'Ubuntu',
1177 'Ubuntu',
1178 'Cantarell',
1178 'Cantarell',
1179 'Helvetica Neue',
1179 'Helvetica Neue',
1180 'sans-serif'
1180 'sans-serif'
1181 ]
1181 ]
1182 font_family = ','.join(fonts)
1182 font_family = ','.join(fonts)
1183 if svg_type:
1183 if svg_type:
1184 return self.get_img_data_by_type(font_family, svg_type)
1184 return self.get_img_data_by_type(font_family, svg_type)
1185
1185
1186 initials = self.get_initials()
1186 initials = self.get_initials()
1187 img_data = """
1187 img_data = """
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1189 width="{size}" height="{size}"
1189 width="{size}" height="{size}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1191 viewBox="0 0 {size} {size}">
1191 viewBox="0 0 {size} {size}">
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1193 pointer-events="auto" fill="{text_color}"
1193 pointer-events="auto" fill="{text_color}"
1194 font-family="{font_family}"
1194 font-family="{font_family}"
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1196 </text>
1196 </text>
1197 </svg>""".format(
1197 </svg>""".format(
1198 size=self.size,
1198 size=self.size,
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1200 background=self.background,
1200 background=self.background,
1201 text_color=self.text_color,
1201 text_color=self.text_color,
1202 text=initials.upper(),
1202 text=initials.upper(),
1203 font_family=font_family)
1203 font_family=font_family)
1204
1204
1205 return img_data
1205 return img_data
1206
1206
1207 def generate_svg(self, svg_type=None):
1207 def generate_svg(self, svg_type=None):
1208 img_data = self.get_img_data(svg_type)
1208 img_data = self.get_img_data(svg_type)
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1210
1210
1211
1211
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1213 svg_type = None
1213 svg_type = None
1214 if email_address == User.DEFAULT_USER_EMAIL:
1214 if email_address == User.DEFAULT_USER_EMAIL:
1215 svg_type = 'default_user'
1215 svg_type = 'default_user'
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1217 return klass.generate_svg(svg_type=svg_type)
1217 return klass.generate_svg(svg_type=svg_type)
1218
1218
1219
1219
1220 def gravatar_url(email_address, size=30, request=None):
1220 def gravatar_url(email_address, size=30, request=None):
1221 request = get_current_request()
1221 request = get_current_request()
1222 _use_gravatar = request.call_context.visual.use_gravatar
1222 _use_gravatar = request.call_context.visual.use_gravatar
1223 _gravatar_url = request.call_context.visual.gravatar_url
1223 _gravatar_url = request.call_context.visual.gravatar_url
1224
1224
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1226
1226
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1228 if isinstance(email_address, unicode):
1228 if isinstance(email_address, unicode):
1229 # hashlib crashes on unicode items
1229 # hashlib crashes on unicode items
1230 email_address = safe_str(email_address)
1230 email_address = safe_str(email_address)
1231
1231
1232 # empty email or default user
1232 # empty email or default user
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1235
1235
1236 if _use_gravatar:
1236 if _use_gravatar:
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1238 # get the host and schema here.
1238 # get the host and schema here.
1239 request = get_current_request()
1239 request = get_current_request()
1240 tmpl = safe_str(_gravatar_url)
1240 tmpl = safe_str(_gravatar_url)
1241 tmpl = tmpl.replace('{email}', email_address)\
1241 tmpl = tmpl.replace('{email}', email_address)\
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1243 .replace('{netloc}', request.host)\
1243 .replace('{netloc}', request.host)\
1244 .replace('{scheme}', request.scheme)\
1244 .replace('{scheme}', request.scheme)\
1245 .replace('{size}', safe_str(size))
1245 .replace('{size}', safe_str(size))
1246 return tmpl
1246 return tmpl
1247 else:
1247 else:
1248 return initials_gravatar(email_address, '', '', size=size)
1248 return initials_gravatar(email_address, '', '', size=size)
1249
1249
1250
1250
1251 class Page(_Page):
1251 class Page(_Page):
1252 """
1252 """
1253 Custom pager to match rendering style with paginator
1253 Custom pager to match rendering style with paginator
1254 """
1254 """
1255
1255
1256 def _get_pos(self, cur_page, max_page, items):
1256 def _get_pos(self, cur_page, max_page, items):
1257 edge = (items / 2) + 1
1257 edge = (items / 2) + 1
1258 if (cur_page <= edge):
1258 if (cur_page <= edge):
1259 radius = max(items / 2, items - cur_page)
1259 radius = max(items / 2, items - cur_page)
1260 elif (max_page - cur_page) < edge:
1260 elif (max_page - cur_page) < edge:
1261 radius = (items - 1) - (max_page - cur_page)
1261 radius = (items - 1) - (max_page - cur_page)
1262 else:
1262 else:
1263 radius = items / 2
1263 radius = items / 2
1264
1264
1265 left = max(1, (cur_page - (radius)))
1265 left = max(1, (cur_page - (radius)))
1266 right = min(max_page, cur_page + (radius))
1266 right = min(max_page, cur_page + (radius))
1267 return left, cur_page, right
1267 return left, cur_page, right
1268
1268
1269 def _range(self, regexp_match):
1269 def _range(self, regexp_match):
1270 """
1270 """
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1272
1272
1273 Arguments:
1273 Arguments:
1274
1274
1275 regexp_match
1275 regexp_match
1276 A "re" (regular expressions) match object containing the
1276 A "re" (regular expressions) match object containing the
1277 radius of linked pages around the current page in
1277 radius of linked pages around the current page in
1278 regexp_match.group(1) as a string
1278 regexp_match.group(1) as a string
1279
1279
1280 This function is supposed to be called as a callable in
1280 This function is supposed to be called as a callable in
1281 re.sub.
1281 re.sub.
1282
1282
1283 """
1283 """
1284 radius = int(regexp_match.group(1))
1284 radius = int(regexp_match.group(1))
1285
1285
1286 # Compute the first and last page number within the radius
1286 # Compute the first and last page number within the radius
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1288 # -> leftmost_page = 5
1288 # -> leftmost_page = 5
1289 # -> rightmost_page = 9
1289 # -> rightmost_page = 9
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1291 self.last_page,
1291 self.last_page,
1292 (radius * 2) + 1)
1292 (radius * 2) + 1)
1293 nav_items = []
1293 nav_items = []
1294
1294
1295 # Create a link to the first page (unless we are on the first page
1295 # Create a link to the first page (unless we are on the first page
1296 # or there would be no need to insert '..' spacers)
1296 # or there would be no need to insert '..' spacers)
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1299
1299
1300 # Insert dots if there are pages between the first page
1300 # Insert dots if there are pages between the first page
1301 # and the currently displayed page range
1301 # and the currently displayed page range
1302 if leftmost_page - self.first_page > 1:
1302 if leftmost_page - self.first_page > 1:
1303 # Wrap in a SPAN tag if nolink_attr is set
1303 # Wrap in a SPAN tag if nolink_attr is set
1304 text = '..'
1304 text = '..'
1305 if self.dotdot_attr:
1305 if self.dotdot_attr:
1306 text = HTML.span(c=text, **self.dotdot_attr)
1306 text = HTML.span(c=text, **self.dotdot_attr)
1307 nav_items.append(text)
1307 nav_items.append(text)
1308
1308
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1310 # Hilight the current page number and do not use a link
1310 # Hilight the current page number and do not use a link
1311 if thispage == self.page:
1311 if thispage == self.page:
1312 text = '%s' % (thispage,)
1312 text = '%s' % (thispage,)
1313 # Wrap in a SPAN tag if nolink_attr is set
1313 # Wrap in a SPAN tag if nolink_attr is set
1314 if self.curpage_attr:
1314 if self.curpage_attr:
1315 text = HTML.span(c=text, **self.curpage_attr)
1315 text = HTML.span(c=text, **self.curpage_attr)
1316 nav_items.append(text)
1316 nav_items.append(text)
1317 # Otherwise create just a link to that page
1317 # Otherwise create just a link to that page
1318 else:
1318 else:
1319 text = '%s' % (thispage,)
1319 text = '%s' % (thispage,)
1320 nav_items.append(self._pagerlink(thispage, text))
1320 nav_items.append(self._pagerlink(thispage, text))
1321
1321
1322 # Insert dots if there are pages between the displayed
1322 # Insert dots if there are pages between the displayed
1323 # page numbers and the end of the page range
1323 # page numbers and the end of the page range
1324 if self.last_page - rightmost_page > 1:
1324 if self.last_page - rightmost_page > 1:
1325 text = '..'
1325 text = '..'
1326 # Wrap in a SPAN tag if nolink_attr is set
1326 # Wrap in a SPAN tag if nolink_attr is set
1327 if self.dotdot_attr:
1327 if self.dotdot_attr:
1328 text = HTML.span(c=text, **self.dotdot_attr)
1328 text = HTML.span(c=text, **self.dotdot_attr)
1329 nav_items.append(text)
1329 nav_items.append(text)
1330
1330
1331 # Create a link to the very last page (unless we are on the last
1331 # Create a link to the very last page (unless we are on the last
1332 # page or there would be no need to insert '..' spacers)
1332 # page or there would be no need to insert '..' spacers)
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1335
1335
1336 ## prerender links
1336 ## prerender links
1337 #_page_link = url.current()
1337 #_page_link = url.current()
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1340 return self.separator.join(nav_items)
1340 return self.separator.join(nav_items)
1341
1341
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1343 show_if_single_page=False, separator=' ', onclick=None,
1343 show_if_single_page=False, separator=' ', onclick=None,
1344 symbol_first='<<', symbol_last='>>',
1344 symbol_first='<<', symbol_last='>>',
1345 symbol_previous='<', symbol_next='>',
1345 symbol_previous='<', symbol_next='>',
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1347 curpage_attr={'class': 'pager_curpage'},
1347 curpage_attr={'class': 'pager_curpage'},
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1349
1349
1350 self.curpage_attr = curpage_attr
1350 self.curpage_attr = curpage_attr
1351 self.separator = separator
1351 self.separator = separator
1352 self.pager_kwargs = kwargs
1352 self.pager_kwargs = kwargs
1353 self.page_param = page_param
1353 self.page_param = page_param
1354 self.partial_param = partial_param
1354 self.partial_param = partial_param
1355 self.onclick = onclick
1355 self.onclick = onclick
1356 self.link_attr = link_attr
1356 self.link_attr = link_attr
1357 self.dotdot_attr = dotdot_attr
1357 self.dotdot_attr = dotdot_attr
1358
1358
1359 # Don't show navigator if there is no more than one page
1359 # Don't show navigator if there is no more than one page
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1361 return ''
1361 return ''
1362
1362
1363 from string import Template
1363 from string import Template
1364 # Replace ~...~ in token format by range of pages
1364 # Replace ~...~ in token format by range of pages
1365 result = re.sub(r'~(\d+)~', self._range, format)
1365 result = re.sub(r'~(\d+)~', self._range, format)
1366
1366
1367 # Interpolate '%' variables
1367 # Interpolate '%' variables
1368 result = Template(result).safe_substitute({
1368 result = Template(result).safe_substitute({
1369 'first_page': self.first_page,
1369 'first_page': self.first_page,
1370 'last_page': self.last_page,
1370 'last_page': self.last_page,
1371 'page': self.page,
1371 'page': self.page,
1372 'page_count': self.page_count,
1372 'page_count': self.page_count,
1373 'items_per_page': self.items_per_page,
1373 'items_per_page': self.items_per_page,
1374 'first_item': self.first_item,
1374 'first_item': self.first_item,
1375 'last_item': self.last_item,
1375 'last_item': self.last_item,
1376 'item_count': self.item_count,
1376 'item_count': self.item_count,
1377 'link_first': self.page > self.first_page and \
1377 'link_first': self.page > self.first_page and \
1378 self._pagerlink(self.first_page, symbol_first) or '',
1378 self._pagerlink(self.first_page, symbol_first) or '',
1379 'link_last': self.page < self.last_page and \
1379 'link_last': self.page < self.last_page and \
1380 self._pagerlink(self.last_page, symbol_last) or '',
1380 self._pagerlink(self.last_page, symbol_last) or '',
1381 'link_previous': self.previous_page and \
1381 'link_previous': self.previous_page and \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1384 'link_next': self.next_page and \
1384 'link_next': self.next_page and \
1385 self._pagerlink(self.next_page, symbol_next) \
1385 self._pagerlink(self.next_page, symbol_next) \
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1387 })
1387 })
1388
1388
1389 return literal(result)
1389 return literal(result)
1390
1390
1391
1391
1392 #==============================================================================
1392 #==============================================================================
1393 # REPO PAGER, PAGER FOR REPOSITORY
1393 # REPO PAGER, PAGER FOR REPOSITORY
1394 #==============================================================================
1394 #==============================================================================
1395 class RepoPage(Page):
1395 class RepoPage(Page):
1396
1396
1397 def __init__(self, collection, page=1, items_per_page=20,
1397 def __init__(self, collection, page=1, items_per_page=20,
1398 item_count=None, url=None, **kwargs):
1398 item_count=None, url=None, **kwargs):
1399
1399
1400 """Create a "RepoPage" instance. special pager for paging
1400 """Create a "RepoPage" instance. special pager for paging
1401 repository
1401 repository
1402 """
1402 """
1403 self._url_generator = url
1403 self._url_generator = url
1404
1404
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1406 self.kwargs = kwargs
1406 self.kwargs = kwargs
1407
1407
1408 # Save a reference to the collection
1408 # Save a reference to the collection
1409 self.original_collection = collection
1409 self.original_collection = collection
1410
1410
1411 self.collection = collection
1411 self.collection = collection
1412
1412
1413 # The self.page is the number of the current page.
1413 # The self.page is the number of the current page.
1414 # The first page has the number 1!
1414 # The first page has the number 1!
1415 try:
1415 try:
1416 self.page = int(page) # make it int() if we get it as a string
1416 self.page = int(page) # make it int() if we get it as a string
1417 except (ValueError, TypeError):
1417 except (ValueError, TypeError):
1418 self.page = 1
1418 self.page = 1
1419
1419
1420 self.items_per_page = items_per_page
1420 self.items_per_page = items_per_page
1421
1421
1422 # Unless the user tells us how many items the collections has
1422 # Unless the user tells us how many items the collections has
1423 # we calculate that ourselves.
1423 # we calculate that ourselves.
1424 if item_count is not None:
1424 if item_count is not None:
1425 self.item_count = item_count
1425 self.item_count = item_count
1426 else:
1426 else:
1427 self.item_count = len(self.collection)
1427 self.item_count = len(self.collection)
1428
1428
1429 # Compute the number of the first and last available page
1429 # Compute the number of the first and last available page
1430 if self.item_count > 0:
1430 if self.item_count > 0:
1431 self.first_page = 1
1431 self.first_page = 1
1432 self.page_count = int(math.ceil(float(self.item_count) /
1432 self.page_count = int(math.ceil(float(self.item_count) /
1433 self.items_per_page))
1433 self.items_per_page))
1434 self.last_page = self.first_page + self.page_count - 1
1434 self.last_page = self.first_page + self.page_count - 1
1435
1435
1436 # Make sure that the requested page number is the range of
1436 # Make sure that the requested page number is the range of
1437 # valid pages
1437 # valid pages
1438 if self.page > self.last_page:
1438 if self.page > self.last_page:
1439 self.page = self.last_page
1439 self.page = self.last_page
1440 elif self.page < self.first_page:
1440 elif self.page < self.first_page:
1441 self.page = self.first_page
1441 self.page = self.first_page
1442
1442
1443 # Note: the number of items on this page can be less than
1443 # Note: the number of items on this page can be less than
1444 # items_per_page if the last page is not full
1444 # items_per_page if the last page is not full
1445 self.first_item = max(0, (self.item_count) - (self.page *
1445 self.first_item = max(0, (self.item_count) - (self.page *
1446 items_per_page))
1446 items_per_page))
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1448 (self.page - 1))
1448 (self.page - 1))
1449
1449
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1451
1451
1452 # Links to previous and next page
1452 # Links to previous and next page
1453 if self.page > self.first_page:
1453 if self.page > self.first_page:
1454 self.previous_page = self.page - 1
1454 self.previous_page = self.page - 1
1455 else:
1455 else:
1456 self.previous_page = None
1456 self.previous_page = None
1457
1457
1458 if self.page < self.last_page:
1458 if self.page < self.last_page:
1459 self.next_page = self.page + 1
1459 self.next_page = self.page + 1
1460 else:
1460 else:
1461 self.next_page = None
1461 self.next_page = None
1462
1462
1463 # No items available
1463 # No items available
1464 else:
1464 else:
1465 self.first_page = None
1465 self.first_page = None
1466 self.page_count = 0
1466 self.page_count = 0
1467 self.last_page = None
1467 self.last_page = None
1468 self.first_item = None
1468 self.first_item = None
1469 self.last_item = None
1469 self.last_item = None
1470 self.previous_page = None
1470 self.previous_page = None
1471 self.next_page = None
1471 self.next_page = None
1472 self.items = []
1472 self.items = []
1473
1473
1474 # This is a subclass of the 'list' type. Initialise the list now.
1474 # This is a subclass of the 'list' type. Initialise the list now.
1475 list.__init__(self, reversed(self.items))
1475 list.__init__(self, reversed(self.items))
1476
1476
1477
1477
1478 def breadcrumb_repo_link(repo):
1478 def breadcrumb_repo_link(repo):
1479 """
1479 """
1480 Makes a breadcrumbs path link to repo
1480 Makes a breadcrumbs path link to repo
1481
1481
1482 ex::
1482 ex::
1483 group >> subgroup >> repo
1483 group >> subgroup >> repo
1484
1484
1485 :param repo: a Repository instance
1485 :param repo: a Repository instance
1486 """
1486 """
1487
1487
1488 path = [
1488 path = [
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1490 for group in repo.groups_with_parents
1490 for group in repo.groups_with_parents
1491 ] + [
1491 ] + [
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1493 ]
1493 ]
1494
1494
1495 return literal(' &raquo; '.join(path))
1495 return literal(' &raquo; '.join(path))
1496
1496
1497
1497
1498 def breadcrumb_repo_group_link(repo_group):
1498 def breadcrumb_repo_group_link(repo_group):
1499 """
1499 """
1500 Makes a breadcrumbs path link to repo
1500 Makes a breadcrumbs path link to repo
1501
1501
1502 ex::
1502 ex::
1503 group >> subgroup
1503 group >> subgroup
1504
1504
1505 :param repo_group: a Repository Group instance
1505 :param repo_group: a Repository Group instance
1506 """
1506 """
1507
1507
1508 path = [
1508 path = [
1509 link_to(group.name,
1509 link_to(group.name,
1510 route_path('repo_group_home', repo_group_name=group.group_name))
1510 route_path('repo_group_home', repo_group_name=group.group_name))
1511 for group in repo_group.parents
1511 for group in repo_group.parents
1512 ] + [
1512 ] + [
1513 link_to(repo_group.name,
1513 link_to(repo_group.name,
1514 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1514 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1515 ]
1515 ]
1516
1516
1517 return literal(' &raquo; '.join(path))
1517 return literal(' &raquo; '.join(path))
1518
1518
1519
1519
1520 def format_byte_size_binary(file_size):
1520 def format_byte_size_binary(file_size):
1521 """
1521 """
1522 Formats file/folder sizes to standard.
1522 Formats file/folder sizes to standard.
1523 """
1523 """
1524 if file_size is None:
1524 if file_size is None:
1525 file_size = 0
1525 file_size = 0
1526
1526
1527 formatted_size = format_byte_size(file_size, binary=True)
1527 formatted_size = format_byte_size(file_size, binary=True)
1528 return formatted_size
1528 return formatted_size
1529
1529
1530
1530
1531 def urlify_text(text_, safe=True):
1531 def urlify_text(text_, safe=True):
1532 """
1532 """
1533 Extrac urls from text and make html links out of them
1533 Extrac urls from text and make html links out of them
1534
1534
1535 :param text_:
1535 :param text_:
1536 """
1536 """
1537
1537
1538 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1538 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1539 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1539 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1540
1540
1541 def url_func(match_obj):
1541 def url_func(match_obj):
1542 url_full = match_obj.groups()[0]
1542 url_full = match_obj.groups()[0]
1543 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1543 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1544 _newtext = url_pat.sub(url_func, text_)
1544 _newtext = url_pat.sub(url_func, text_)
1545 if safe:
1545 if safe:
1546 return literal(_newtext)
1546 return literal(_newtext)
1547 return _newtext
1547 return _newtext
1548
1548
1549
1549
1550 def urlify_commits(text_, repository):
1550 def urlify_commits(text_, repository):
1551 """
1551 """
1552 Extract commit ids from text and make link from them
1552 Extract commit ids from text and make link from them
1553
1553
1554 :param text_:
1554 :param text_:
1555 :param repository: repo name to build the URL with
1555 :param repository: repo name to build the URL with
1556 """
1556 """
1557
1557
1558 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1558 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1559
1559
1560 def url_func(match_obj):
1560 def url_func(match_obj):
1561 commit_id = match_obj.groups()[1]
1561 commit_id = match_obj.groups()[1]
1562 pref = match_obj.groups()[0]
1562 pref = match_obj.groups()[0]
1563 suf = match_obj.groups()[2]
1563 suf = match_obj.groups()[2]
1564
1564
1565 tmpl = (
1565 tmpl = (
1566 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1566 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1567 '%(commit_id)s</a>%(suf)s'
1567 '%(commit_id)s</a>%(suf)s'
1568 )
1568 )
1569 return tmpl % {
1569 return tmpl % {
1570 'pref': pref,
1570 'pref': pref,
1571 'cls': 'revision-link',
1571 'cls': 'revision-link',
1572 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1572 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1573 'commit_id': commit_id,
1573 'commit_id': commit_id,
1574 'suf': suf
1574 'suf': suf
1575 }
1575 }
1576
1576
1577 newtext = URL_PAT.sub(url_func, text_)
1577 newtext = URL_PAT.sub(url_func, text_)
1578
1578
1579 return newtext
1579 return newtext
1580
1580
1581
1581
1582 def _process_url_func(match_obj, repo_name, uid, entry,
1582 def _process_url_func(match_obj, repo_name, uid, entry,
1583 return_raw_data=False, link_format='html'):
1583 return_raw_data=False, link_format='html'):
1584 pref = ''
1584 pref = ''
1585 if match_obj.group().startswith(' '):
1585 if match_obj.group().startswith(' '):
1586 pref = ' '
1586 pref = ' '
1587
1587
1588 issue_id = ''.join(match_obj.groups())
1588 issue_id = ''.join(match_obj.groups())
1589
1589
1590 if link_format == 'html':
1590 if link_format == 'html':
1591 tmpl = (
1591 tmpl = (
1592 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1592 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1593 '%(issue-prefix)s%(id-repr)s'
1593 '%(issue-prefix)s%(id-repr)s'
1594 '</a>')
1594 '</a>')
1595 elif link_format == 'rst':
1595 elif link_format == 'rst':
1596 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1596 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1597 elif link_format == 'markdown':
1597 elif link_format == 'markdown':
1598 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1598 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1599 else:
1599 else:
1600 raise ValueError('Bad link_format:{}'.format(link_format))
1600 raise ValueError('Bad link_format:{}'.format(link_format))
1601
1601
1602 (repo_name_cleaned,
1602 (repo_name_cleaned,
1603 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1603 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1604
1604
1605 # variables replacement
1605 # variables replacement
1606 named_vars = {
1606 named_vars = {
1607 'id': issue_id,
1607 'id': issue_id,
1608 'repo': repo_name,
1608 'repo': repo_name,
1609 'repo_name': repo_name_cleaned,
1609 'repo_name': repo_name_cleaned,
1610 'group_name': parent_group_name
1610 'group_name': parent_group_name
1611 }
1611 }
1612 # named regex variables
1612 # named regex variables
1613 named_vars.update(match_obj.groupdict())
1613 named_vars.update(match_obj.groupdict())
1614 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1614 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1615
1615
1616 def quote_cleaner(input_str):
1616 def quote_cleaner(input_str):
1617 """Remove quotes as it's HTML"""
1617 """Remove quotes as it's HTML"""
1618 return input_str.replace('"', '')
1618 return input_str.replace('"', '')
1619
1619
1620 data = {
1620 data = {
1621 'pref': pref,
1621 'pref': pref,
1622 'cls': quote_cleaner('issue-tracker-link'),
1622 'cls': quote_cleaner('issue-tracker-link'),
1623 'url': quote_cleaner(_url),
1623 'url': quote_cleaner(_url),
1624 'id-repr': issue_id,
1624 'id-repr': issue_id,
1625 'issue-prefix': entry['pref'],
1625 'issue-prefix': entry['pref'],
1626 'serv': entry['url'],
1626 'serv': entry['url'],
1627 }
1627 }
1628 if return_raw_data:
1628 if return_raw_data:
1629 return {
1629 return {
1630 'id': issue_id,
1630 'id': issue_id,
1631 'url': _url
1631 'url': _url
1632 }
1632 }
1633 return tmpl % data
1633 return tmpl % data
1634
1634
1635
1635
1636 def get_active_pattern_entries(repo_name):
1636 def get_active_pattern_entries(repo_name):
1637 repo = None
1637 repo = None
1638 if repo_name:
1638 if repo_name:
1639 # Retrieving repo_name to avoid invalid repo_name to explode on
1639 # Retrieving repo_name to avoid invalid repo_name to explode on
1640 # IssueTrackerSettingsModel but still passing invalid name further down
1640 # IssueTrackerSettingsModel but still passing invalid name further down
1641 repo = Repository.get_by_repo_name(repo_name, cache=True)
1641 repo = Repository.get_by_repo_name(repo_name, cache=True)
1642
1642
1643 settings_model = IssueTrackerSettingsModel(repo=repo)
1643 settings_model = IssueTrackerSettingsModel(repo=repo)
1644 active_entries = settings_model.get_settings(cache=True)
1644 active_entries = settings_model.get_settings(cache=True)
1645 return active_entries
1645 return active_entries
1646
1646
1647
1647
1648 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1648 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1649
1649
1650 allowed_formats = ['html', 'rst', 'markdown']
1650 allowed_formats = ['html', 'rst', 'markdown']
1651 if link_format not in allowed_formats:
1651 if link_format not in allowed_formats:
1652 raise ValueError('Link format can be only one of:{} got {}'.format(
1652 raise ValueError('Link format can be only one of:{} got {}'.format(
1653 allowed_formats, link_format))
1653 allowed_formats, link_format))
1654
1654
1655 active_entries = active_entries or get_active_pattern_entries(repo_name)
1655 active_entries = active_entries or get_active_pattern_entries(repo_name)
1656 issues_data = []
1656 issues_data = []
1657 newtext = text_string
1657 newtext = text_string
1658
1658
1659 for uid, entry in active_entries.items():
1659 for uid, entry in active_entries.items():
1660 log.debug('found issue tracker entry with uid %s', uid)
1660 log.debug('found issue tracker entry with uid %s', uid)
1661
1661
1662 if not (entry['pat'] and entry['url']):
1662 if not (entry['pat'] and entry['url']):
1663 log.debug('skipping due to missing data')
1663 log.debug('skipping due to missing data')
1664 continue
1664 continue
1665
1665
1666 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1666 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1667 uid, entry['pat'], entry['url'], entry['pref'])
1667 uid, entry['pat'], entry['url'], entry['pref'])
1668
1668
1669 try:
1669 try:
1670 pattern = re.compile(r'%s' % entry['pat'])
1670 pattern = re.compile(r'%s' % entry['pat'])
1671 except re.error:
1671 except re.error:
1672 log.exception(
1672 log.exception(
1673 'issue tracker pattern: `%s` failed to compile',
1673 'issue tracker pattern: `%s` failed to compile',
1674 entry['pat'])
1674 entry['pat'])
1675 continue
1675 continue
1676
1676
1677 data_func = partial(
1677 data_func = partial(
1678 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1678 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1679 return_raw_data=True)
1679 return_raw_data=True)
1680
1680
1681 for match_obj in pattern.finditer(text_string):
1681 for match_obj in pattern.finditer(text_string):
1682 issues_data.append(data_func(match_obj))
1682 issues_data.append(data_func(match_obj))
1683
1683
1684 url_func = partial(
1684 url_func = partial(
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1686 link_format=link_format)
1686 link_format=link_format)
1687
1687
1688 newtext = pattern.sub(url_func, newtext)
1688 newtext = pattern.sub(url_func, newtext)
1689 log.debug('processed prefix:uid `%s`', uid)
1689 log.debug('processed prefix:uid `%s`', uid)
1690
1690
1691 return newtext, issues_data
1691 return newtext, issues_data
1692
1692
1693
1693
1694 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1694 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1695 """
1695 """
1696 Parses given text message and makes proper links.
1696 Parses given text message and makes proper links.
1697 issues are linked to given issue-server, and rest is a commit link
1697 issues are linked to given issue-server, and rest is a commit link
1698
1698
1699 :param commit_text:
1699 :param commit_text:
1700 :param repository:
1700 :param repository:
1701 """
1701 """
1702 def escaper(string):
1702 def escaper(string):
1703 return string.replace('<', '&lt;').replace('>', '&gt;')
1703 return string.replace('<', '&lt;').replace('>', '&gt;')
1704
1704
1705 newtext = escaper(commit_text)
1705 newtext = escaper(commit_text)
1706
1706
1707 # extract http/https links and make them real urls
1707 # extract http/https links and make them real urls
1708 newtext = urlify_text(newtext, safe=False)
1708 newtext = urlify_text(newtext, safe=False)
1709
1709
1710 # urlify commits - extract commit ids and make link out of them, if we have
1710 # urlify commits - extract commit ids and make link out of them, if we have
1711 # the scope of repository present.
1711 # the scope of repository present.
1712 if repository:
1712 if repository:
1713 newtext = urlify_commits(newtext, repository)
1713 newtext = urlify_commits(newtext, repository)
1714
1714
1715 # process issue tracker patterns
1715 # process issue tracker patterns
1716 newtext, issues = process_patterns(newtext, repository or '',
1716 newtext, issues = process_patterns(newtext, repository or '',
1717 active_entries=active_pattern_entries)
1717 active_entries=active_pattern_entries)
1718
1718
1719 return literal(newtext)
1719 return literal(newtext)
1720
1720
1721
1721
1722 def render_binary(repo_name, file_obj):
1722 def render_binary(repo_name, file_obj):
1723 """
1723 """
1724 Choose how to render a binary file
1724 Choose how to render a binary file
1725 """
1725 """
1726
1726
1727 filename = file_obj.name
1727 filename = file_obj.name
1728
1728
1729 # images
1729 # images
1730 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1730 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1731 if fnmatch.fnmatch(filename, pat=ext):
1731 if fnmatch.fnmatch(filename, pat=ext):
1732 alt = escape(filename)
1732 alt = escape(filename)
1733 src = route_path(
1733 src = route_path(
1734 'repo_file_raw', repo_name=repo_name,
1734 'repo_file_raw', repo_name=repo_name,
1735 commit_id=file_obj.commit.raw_id,
1735 commit_id=file_obj.commit.raw_id,
1736 f_path=file_obj.path)
1736 f_path=file_obj.path)
1737 return literal(
1737 return literal(
1738 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1738 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1739
1739
1740
1740
1741 def renderer_from_filename(filename, exclude=None):
1741 def renderer_from_filename(filename, exclude=None):
1742 """
1742 """
1743 choose a renderer based on filename, this works only for text based files
1743 choose a renderer based on filename, this works only for text based files
1744 """
1744 """
1745
1745
1746 # ipython
1746 # ipython
1747 for ext in ['*.ipynb']:
1747 for ext in ['*.ipynb']:
1748 if fnmatch.fnmatch(filename, pat=ext):
1748 if fnmatch.fnmatch(filename, pat=ext):
1749 return 'jupyter'
1749 return 'jupyter'
1750
1750
1751 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1751 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1752 if is_markup:
1752 if is_markup:
1753 return is_markup
1753 return is_markup
1754 return None
1754 return None
1755
1755
1756
1756
1757 def render(source, renderer='rst', mentions=False, relative_urls=None,
1757 def render(source, renderer='rst', mentions=False, relative_urls=None,
1758 repo_name=None):
1758 repo_name=None):
1759
1759
1760 def maybe_convert_relative_links(html_source):
1760 def maybe_convert_relative_links(html_source):
1761 if relative_urls:
1761 if relative_urls:
1762 return relative_links(html_source, relative_urls)
1762 return relative_links(html_source, relative_urls)
1763 return html_source
1763 return html_source
1764
1764
1765 if renderer == 'plain':
1765 if renderer == 'plain':
1766 return literal(
1766 return literal(
1767 MarkupRenderer.plain(source, leading_newline=False))
1767 MarkupRenderer.plain(source, leading_newline=False))
1768
1768
1769 elif renderer == 'rst':
1769 elif renderer == 'rst':
1770 if repo_name:
1770 if repo_name:
1771 # process patterns on comments if we pass in repo name
1771 # process patterns on comments if we pass in repo name
1772 source, issues = process_patterns(
1772 source, issues = process_patterns(
1773 source, repo_name, link_format='rst')
1773 source, repo_name, link_format='rst')
1774
1774
1775 return literal(
1775 return literal(
1776 '<div class="rst-block">%s</div>' %
1776 '<div class="rst-block">%s</div>' %
1777 maybe_convert_relative_links(
1777 maybe_convert_relative_links(
1778 MarkupRenderer.rst(source, mentions=mentions)))
1778 MarkupRenderer.rst(source, mentions=mentions)))
1779
1779
1780 elif renderer == 'markdown':
1780 elif renderer == 'markdown':
1781 if repo_name:
1781 if repo_name:
1782 # process patterns on comments if we pass in repo name
1782 # process patterns on comments if we pass in repo name
1783 source, issues = process_patterns(
1783 source, issues = process_patterns(
1784 source, repo_name, link_format='markdown')
1784 source, repo_name, link_format='markdown')
1785
1785
1786 return literal(
1786 return literal(
1787 '<div class="markdown-block">%s</div>' %
1787 '<div class="markdown-block">%s</div>' %
1788 maybe_convert_relative_links(
1788 maybe_convert_relative_links(
1789 MarkupRenderer.markdown(source, flavored=True,
1789 MarkupRenderer.markdown(source, flavored=True,
1790 mentions=mentions)))
1790 mentions=mentions)))
1791
1791
1792 elif renderer == 'jupyter':
1792 elif renderer == 'jupyter':
1793 return literal(
1793 return literal(
1794 '<div class="ipynb">%s</div>' %
1794 '<div class="ipynb">%s</div>' %
1795 maybe_convert_relative_links(
1795 maybe_convert_relative_links(
1796 MarkupRenderer.jupyter(source)))
1796 MarkupRenderer.jupyter(source)))
1797
1797
1798 # None means just show the file-source
1798 # None means just show the file-source
1799 return None
1799 return None
1800
1800
1801
1801
1802 def commit_status(repo, commit_id):
1802 def commit_status(repo, commit_id):
1803 return ChangesetStatusModel().get_status(repo, commit_id)
1803 return ChangesetStatusModel().get_status(repo, commit_id)
1804
1804
1805
1805
1806 def commit_status_lbl(commit_status):
1806 def commit_status_lbl(commit_status):
1807 return dict(ChangesetStatus.STATUSES).get(commit_status)
1807 return dict(ChangesetStatus.STATUSES).get(commit_status)
1808
1808
1809
1809
1810 def commit_time(repo_name, commit_id):
1810 def commit_time(repo_name, commit_id):
1811 repo = Repository.get_by_repo_name(repo_name)
1811 repo = Repository.get_by_repo_name(repo_name)
1812 commit = repo.get_commit(commit_id=commit_id)
1812 commit = repo.get_commit(commit_id=commit_id)
1813 return commit.date
1813 return commit.date
1814
1814
1815
1815
1816 def get_permission_name(key):
1816 def get_permission_name(key):
1817 return dict(Permission.PERMS).get(key)
1817 return dict(Permission.PERMS).get(key)
1818
1818
1819
1819
1820 def journal_filter_help(request):
1820 def journal_filter_help(request):
1821 _ = request.translate
1821 _ = request.translate
1822 from rhodecode.lib.audit_logger import ACTIONS
1822 from rhodecode.lib.audit_logger import ACTIONS
1823 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1823 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1824
1824
1825 return _(
1825 return _(
1826 'Example filter terms:\n' +
1826 'Example filter terms:\n' +
1827 ' repository:vcs\n' +
1827 ' repository:vcs\n' +
1828 ' username:marcin\n' +
1828 ' username:marcin\n' +
1829 ' username:(NOT marcin)\n' +
1829 ' username:(NOT marcin)\n' +
1830 ' action:*push*\n' +
1830 ' action:*push*\n' +
1831 ' ip:127.0.0.1\n' +
1831 ' ip:127.0.0.1\n' +
1832 ' date:20120101\n' +
1832 ' date:20120101\n' +
1833 ' date:[20120101100000 TO 20120102]\n' +
1833 ' date:[20120101100000 TO 20120102]\n' +
1834 '\n' +
1834 '\n' +
1835 'Actions: {actions}\n' +
1835 'Actions: {actions}\n' +
1836 '\n' +
1836 '\n' +
1837 'Generate wildcards using \'*\' character:\n' +
1837 'Generate wildcards using \'*\' character:\n' +
1838 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1838 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1839 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1839 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1840 '\n' +
1840 '\n' +
1841 'Optional AND / OR operators in queries\n' +
1841 'Optional AND / OR operators in queries\n' +
1842 ' "repository:vcs OR repository:test"\n' +
1842 ' "repository:vcs OR repository:test"\n' +
1843 ' "username:test AND repository:test*"\n'
1843 ' "username:test AND repository:test*"\n'
1844 ).format(actions=actions)
1844 ).format(actions=actions)
1845
1845
1846
1846
1847 def not_mapped_error(repo_name):
1847 def not_mapped_error(repo_name):
1848 from rhodecode.translation import _
1848 from rhodecode.translation import _
1849 flash(_('%s repository is not mapped to db perhaps'
1849 flash(_('%s repository is not mapped to db perhaps'
1850 ' it was created or renamed from the filesystem'
1850 ' it was created or renamed from the filesystem'
1851 ' please run the application again'
1851 ' please run the application again'
1852 ' in order to rescan repositories') % repo_name, category='error')
1852 ' in order to rescan repositories') % repo_name, category='error')
1853
1853
1854
1854
1855 def ip_range(ip_addr):
1855 def ip_range(ip_addr):
1856 from rhodecode.model.db import UserIpMap
1856 from rhodecode.model.db import UserIpMap
1857 s, e = UserIpMap._get_ip_range(ip_addr)
1857 s, e = UserIpMap._get_ip_range(ip_addr)
1858 return '%s - %s' % (s, e)
1858 return '%s - %s' % (s, e)
1859
1859
1860
1860
1861 def form(url, method='post', needs_csrf_token=True, **attrs):
1861 def form(url, method='post', needs_csrf_token=True, **attrs):
1862 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1862 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1863 if method.lower() != 'get' and needs_csrf_token:
1863 if method.lower() != 'get' and needs_csrf_token:
1864 raise Exception(
1864 raise Exception(
1865 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1865 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1866 'CSRF token. If the endpoint does not require such token you can ' +
1866 'CSRF token. If the endpoint does not require such token you can ' +
1867 'explicitly set the parameter needs_csrf_token to false.')
1867 'explicitly set the parameter needs_csrf_token to false.')
1868
1868
1869 return wh_form(url, method=method, **attrs)
1869 return wh_form(url, method=method, **attrs)
1870
1870
1871
1871
1872 def secure_form(form_url, method="POST", multipart=False, **attrs):
1872 def secure_form(form_url, method="POST", multipart=False, **attrs):
1873 """Start a form tag that points the action to an url. This
1873 """Start a form tag that points the action to an url. This
1874 form tag will also include the hidden field containing
1874 form tag will also include the hidden field containing
1875 the auth token.
1875 the auth token.
1876
1876
1877 The url options should be given either as a string, or as a
1877 The url options should be given either as a string, or as a
1878 ``url()`` function. The method for the form defaults to POST.
1878 ``url()`` function. The method for the form defaults to POST.
1879
1879
1880 Options:
1880 Options:
1881
1881
1882 ``multipart``
1882 ``multipart``
1883 If set to True, the enctype is set to "multipart/form-data".
1883 If set to True, the enctype is set to "multipart/form-data".
1884 ``method``
1884 ``method``
1885 The method to use when submitting the form, usually either
1885 The method to use when submitting the form, usually either
1886 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1886 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1887 hidden input with name _method is added to simulate the verb
1887 hidden input with name _method is added to simulate the verb
1888 over POST.
1888 over POST.
1889
1889
1890 """
1890 """
1891 from webhelpers.pylonslib.secure_form import insecure_form
1891 from webhelpers.pylonslib.secure_form import insecure_form
1892
1892
1893 if 'request' in attrs:
1893 if 'request' in attrs:
1894 session = attrs['request'].session
1894 session = attrs['request'].session
1895 del attrs['request']
1895 del attrs['request']
1896 else:
1896 else:
1897 raise ValueError(
1897 raise ValueError(
1898 'Calling this form requires request= to be passed as argument')
1898 'Calling this form requires request= to be passed as argument')
1899
1899
1900 form = insecure_form(form_url, method, multipart, **attrs)
1900 form = insecure_form(form_url, method, multipart, **attrs)
1901 token = literal(
1901 token = literal(
1902 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1902 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1903 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1903 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1904
1904
1905 return literal("%s\n%s" % (form, token))
1905 return literal("%s\n%s" % (form, token))
1906
1906
1907
1907
1908 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1908 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1909 select_html = select(name, selected, options, **attrs)
1909 select_html = select(name, selected, options, **attrs)
1910 select2 = """
1910 select2 = """
1911 <script>
1911 <script>
1912 $(document).ready(function() {
1912 $(document).ready(function() {
1913 $('#%s').select2({
1913 $('#%s').select2({
1914 containerCssClass: 'drop-menu',
1914 containerCssClass: 'drop-menu',
1915 dropdownCssClass: 'drop-menu-dropdown',
1915 dropdownCssClass: 'drop-menu-dropdown',
1916 dropdownAutoWidth: true%s
1916 dropdownAutoWidth: true%s
1917 });
1917 });
1918 });
1918 });
1919 </script>
1919 </script>
1920 """
1920 """
1921 filter_option = """,
1921 filter_option = """,
1922 minimumResultsForSearch: -1
1922 minimumResultsForSearch: -1
1923 """
1923 """
1924 input_id = attrs.get('id') or name
1924 input_id = attrs.get('id') or name
1925 filter_enabled = "" if enable_filter else filter_option
1925 filter_enabled = "" if enable_filter else filter_option
1926 select_script = literal(select2 % (input_id, filter_enabled))
1926 select_script = literal(select2 % (input_id, filter_enabled))
1927
1927
1928 return literal(select_html+select_script)
1928 return literal(select_html+select_script)
1929
1929
1930
1930
1931 def get_visual_attr(tmpl_context_var, attr_name):
1931 def get_visual_attr(tmpl_context_var, attr_name):
1932 """
1932 """
1933 A safe way to get a variable from visual variable of template context
1933 A safe way to get a variable from visual variable of template context
1934
1934
1935 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1935 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1936 :param attr_name: name of the attribute we fetch from the c.visual
1936 :param attr_name: name of the attribute we fetch from the c.visual
1937 """
1937 """
1938 visual = getattr(tmpl_context_var, 'visual', None)
1938 visual = getattr(tmpl_context_var, 'visual', None)
1939 if not visual:
1939 if not visual:
1940 return
1940 return
1941 else:
1941 else:
1942 return getattr(visual, attr_name, None)
1942 return getattr(visual, attr_name, None)
1943
1943
1944
1944
1945 def get_last_path_part(file_node):
1945 def get_last_path_part(file_node):
1946 if not file_node.path:
1946 if not file_node.path:
1947 return u''
1947 return u''
1948
1948
1949 path = safe_unicode(file_node.path.split('/')[-1])
1949 path = safe_unicode(file_node.path.split('/')[-1])
1950 return u'../' + path
1950 return u'../' + path
1951
1951
1952
1952
1953 def route_url(*args, **kwargs):
1953 def route_url(*args, **kwargs):
1954 """
1954 """
1955 Wrapper around pyramids `route_url` (fully qualified url) function.
1955 Wrapper around pyramids `route_url` (fully qualified url) function.
1956 """
1956 """
1957 req = get_current_request()
1957 req = get_current_request()
1958 return req.route_url(*args, **kwargs)
1958 return req.route_url(*args, **kwargs)
1959
1959
1960
1960
1961 def route_path(*args, **kwargs):
1961 def route_path(*args, **kwargs):
1962 """
1962 """
1963 Wrapper around pyramids `route_path` function.
1963 Wrapper around pyramids `route_path` function.
1964 """
1964 """
1965 req = get_current_request()
1965 req = get_current_request()
1966 return req.route_path(*args, **kwargs)
1966 return req.route_path(*args, **kwargs)
1967
1967
1968
1968
1969 def route_path_or_none(*args, **kwargs):
1969 def route_path_or_none(*args, **kwargs):
1970 try:
1970 try:
1971 return route_path(*args, **kwargs)
1971 return route_path(*args, **kwargs)
1972 except KeyError:
1972 except KeyError:
1973 return None
1973 return None
1974
1974
1975
1975
1976 def current_route_path(request, **kw):
1976 def current_route_path(request, **kw):
1977 new_args = request.GET.mixed()
1977 new_args = request.GET.mixed()
1978 new_args.update(kw)
1978 new_args.update(kw)
1979 return request.current_route_path(_query=new_args)
1979 return request.current_route_path(_query=new_args)
1980
1980
1981
1981
1982 def api_call_example(method, args):
1982 def api_call_example(method, args):
1983 """
1983 """
1984 Generates an API call example via CURL
1984 Generates an API call example via CURL
1985 """
1985 """
1986 args_json = json.dumps(OrderedDict([
1986 args_json = json.dumps(OrderedDict([
1987 ('id', 1),
1987 ('id', 1),
1988 ('auth_token', 'SECRET'),
1988 ('auth_token', 'SECRET'),
1989 ('method', method),
1989 ('method', method),
1990 ('args', args)
1990 ('args', args)
1991 ]))
1991 ]))
1992 return literal(
1992 return literal(
1993 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1993 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1994 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1994 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1995 "and needs to be of `api calls` role."
1995 "and needs to be of `api calls` role."
1996 .format(
1996 .format(
1997 api_url=route_url('apiv2'),
1997 api_url=route_url('apiv2'),
1998 token_url=route_url('my_account_auth_tokens'),
1998 token_url=route_url('my_account_auth_tokens'),
1999 data=args_json))
1999 data=args_json))
2000
2000
2001
2001
2002 def notification_description(notification, request):
2002 def notification_description(notification, request):
2003 """
2003 """
2004 Generate notification human readable description based on notification type
2004 Generate notification human readable description based on notification type
2005 """
2005 """
2006 from rhodecode.model.notification import NotificationModel
2006 from rhodecode.model.notification import NotificationModel
2007 return NotificationModel().make_description(
2007 return NotificationModel().make_description(
2008 notification, translate=request.translate)
2008 notification, translate=request.translate)
2009
2009
2010
2010
2011 def go_import_header(request, db_repo=None):
2011 def go_import_header(request, db_repo=None):
2012 """
2012 """
2013 Creates a header for go-import functionality in Go Lang
2013 Creates a header for go-import functionality in Go Lang
2014 """
2014 """
2015
2015
2016 if not db_repo:
2016 if not db_repo:
2017 return
2017 return
2018 if 'go-get' not in request.GET:
2018 if 'go-get' not in request.GET:
2019 return
2019 return
2020
2020
2021 clone_url = db_repo.clone_url()
2021 clone_url = db_repo.clone_url()
2022 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2022 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2023 # we have a repo and go-get flag,
2023 # we have a repo and go-get flag,
2024 return literal('<meta name="go-import" content="{} {} {}">'.format(
2024 return literal('<meta name="go-import" content="{} {} {}">'.format(
2025 prefix, db_repo.repo_type, clone_url))
2025 prefix, db_repo.repo_type, clone_url))
2026
2026
2027
2027
2028 def reviewer_as_json(*args, **kwargs):
2028 def reviewer_as_json(*args, **kwargs):
2029 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2029 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2030 return _reviewer_as_json(*args, **kwargs)
2030 return _reviewer_as_json(*args, **kwargs)
2031
2031
2032
2032
2033 def get_repo_view_type(request):
2033 def get_repo_view_type(request):
2034 route_name = request.matched_route.name
2034 route_name = request.matched_route.name
2035 route_to_view_type = {
2035 route_to_view_type = {
2036 'repo_changelog': 'changelog',
2036 'repo_changelog': 'changelog',
2037 'repo_files': 'files',
2037 'repo_files': 'files',
2038 'repo_summary': 'summary',
2038 'repo_summary': 'summary',
2039 'repo_commit': 'commit'
2039 'repo_commit': 'commit'
2040 }
2040
2041
2041 }
2042 return route_to_view_type.get(route_name)
2042 return route_to_view_type.get(route_name)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,698 +1,733 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // HEADER NAVIGATION
5 // HEADER NAVIGATION
6
6
7 .horizontal-list {
7 .horizontal-list {
8 float: right;
8 float: right;
9 display: block;
9 display: block;
10 margin: 0;
10 margin: 0;
11 padding: 0;
11 padding: 0;
12 -webkit-padding-start: 0;
12 -webkit-padding-start: 0;
13 text-align: left;
13 text-align: left;
14 font-size: @navigation-fontsize;
14 font-size: @navigation-fontsize;
15 color: @grey6;
15 color: @grey6;
16 z-index:10;
16 z-index:10;
17
17
18 li {
18 li {
19 line-height: 1em;
19 line-height: 1em;
20 list-style-type: none;
20 list-style-type: none;
21
21
22 a {
22 a {
23 padding: 0 .5em;
23 padding: 0 .5em;
24
24
25 &.menu_link_notifications {
25 &.menu_link_notifications {
26 .pill(7px,@rcblue);
26 .pill(7px,@rcblue);
27 display: inline;
27 display: inline;
28 margin: 0 7px 0 .7em;
28 margin: 0 7px 0 .7em;
29 font-size: @basefontsize;
29 font-size: @basefontsize;
30 color: white;
30 color: white;
31
31
32 &.empty {
32 &.empty {
33 background-color: @grey4;
33 background-color: @grey4;
34 }
34 }
35
35
36 &:hover {
36 &:hover {
37 background-color: @rcdarkblue;
37 background-color: @rcdarkblue;
38 }
38 }
39 }
39 }
40 }
40 }
41 .pill_container {
41 .pill_container {
42 margin: 1.25em 0px 0px 0px;
42 margin: 1.25em 0px 0px 0px;
43 float: right;
43 float: right;
44 }
44 }
45
45
46 &#quick_login_li {
46 &#quick_login_li {
47 &:hover {
47 &:hover {
48 color: @grey5;
48 color: @grey5;
49 }
49 }
50
50
51 a.menu_link_notifications {
51 a.menu_link_notifications {
52 color: white;
52 color: white;
53 }
53 }
54
54
55 .user {
55 .user {
56 padding-bottom: 10px;
56 padding-bottom: 10px;
57 }
57 }
58
58
59 &.open {
59 &.open {
60 .user {
60 .user {
61 border-bottom: 5px solid @rcblue;
61 border-bottom: 5px solid @rcblue;
62 }
62 }
63 }
63 }
64 }
64 }
65
65
66 &:before { content: none; }
66 &:before { content: none; }
67
67
68 &:last-child {
68 &:last-child {
69 .menulabel {
69 .menulabel {
70 padding-right: 0;
70 padding-right: 0;
71 border-right: none;
71 border-right: none;
72
72
73 .show_more {
73 .show_more {
74 padding-right: 0;
74 padding-right: 0;
75 }
75 }
76 }
76 }
77
77
78 &> a {
78 &> a {
79 border-bottom: none;
79 border-bottom: none;
80 }
80 }
81 }
81 }
82
82
83 &.active {
83 &.active {
84 border-bottom: 5px solid @rcblue;
84 border-bottom: 5px solid @rcblue;
85 }
85 }
86
86
87 &.open {
87 &.open {
88
88
89 a {
89 a {
90 color: white;
90 color: white;
91 }
91 }
92 }
92 }
93
93
94 &:focus {
94 &:focus {
95 outline: none;
95 outline: none;
96 }
96 }
97
97
98 ul li {
98 ul li {
99 display: block;
99 display: block;
100
100
101 &:last-child> a {
101 &:last-child> a {
102 border-bottom: none;
102 border-bottom: none;
103 }
103 }
104
104
105 ul li:last-child a {
105 ul li:last-child a {
106 /* we don't expect more then 3 levels of submenu and the third
106 /* we don't expect more then 3 levels of submenu and the third
107 level can have different html structure */
107 level can have different html structure */
108 border-bottom: none;
108 border-bottom: none;
109 }
109 }
110 }
110 }
111 }
111 }
112
112
113 > li {
113 > li {
114 float: left;
114 float: left;
115 display: block;
115 display: block;
116 padding: 0;
116 padding: 0;
117
117
118 > a,
118 > a,
119 &.has_select2 a {
119 &.has_select2 a {
120 display: block;
120 display: block;
121 padding: 10px 0 2px;
121 padding: 10px 0 2px;
122 }
122 }
123
123
124 .menulabel {
124 .menulabel {
125 padding: 0 .5em;
125 padding: 0 .5em;
126 line-height: 1em;
126 line-height: 1em;
127 // for this specifically we do not use a variable
127 // for this specifically we do not use a variable
128 border-right: 1px solid @grey4;
128 border-right: 1px solid @grey4;
129 }
129 }
130
130
131 .pr_notifications {
131 .pr_notifications {
132 padding-left: .5em;
132 padding-left: .5em;
133 }
133 }
134
134
135 .pr_notifications + .menulabel {
135 .pr_notifications + .menulabel {
136 display:inline;
136 display:inline;
137 padding-left: 0;
137 padding-left: 0;
138 }
138 }
139
139
140 &:hover,
140 &:hover,
141 &.open,
141 &.open,
142 &.active {
142 &.active {
143 a {
143 a {
144 color: @grey1;
144 color: @grey1;
145 }
145 }
146 }
146 }
147 }
147 }
148
148
149 pre {
149 pre {
150 margin: 0;
150 margin: 0;
151 padding: 0;
151 padding: 0;
152 }
152 }
153
153
154 .select2-container,
154 .select2-container,
155 .menulink.childs {
155 .menulink.childs {
156 position: relative;
156 position: relative;
157 }
157 }
158
158
159 #quick_login {
159 #quick_login {
160
160
161 li a {
161 li a {
162 padding: .5em 0;
162 padding: .5em 0;
163 border-bottom: none;
163 border-bottom: none;
164 color: @grey2;
164 color: @grey2;
165
165
166 &:hover { color: @grey1; }
166 &:hover { color: @grey1; }
167 }
167 }
168 }
168 }
169
169
170 #quick_login_link {
170 #quick_login_link {
171 display: inline-block;
171 display: inline-block;
172
172
173 .gravatar {
173 .gravatar {
174 border: 1px solid @grey5;
174 border: 1px solid @grey5;
175 }
175 }
176
176
177 .gravatar-login {
177 .gravatar-login {
178 height: 20px;
178 height: 20px;
179 width: 20px;
179 width: 20px;
180 margin: -8px 0;
180 margin: -8px 0;
181 padding: 0;
181 padding: 0;
182 }
182 }
183
183
184 &:hover .user {
184 &:hover .user {
185 color: @grey6;
185 color: @grey6;
186 }
186 }
187 }
187 }
188 }
188 }
189 .header .horizontal-list {
189 .header .horizontal-list {
190
190
191 li {
191 li {
192
192
193 &#quick_login_li {
193 &#quick_login_li {
194 padding-left: .5em;
194 padding-left: .5em;
195
195
196 &:hover #quick_login_link {
196 &:hover #quick_login_link {
197 color: inherit;
197 color: inherit;
198 }
198 }
199
199
200 .menu_link_user {
200 .menu_link_user {
201 padding: 0 2px;
201 padding: 0 2px;
202 }
202 }
203 }
203 }
204 list-style-type: none;
204 list-style-type: none;
205 }
205 }
206
206
207 > li {
207 > li {
208
208
209 a {
209 a {
210 padding: 18px 0 12px 0;
210 padding: 18px 0 12px 0;
211 color: @nav-grey;
211 color: @nav-grey;
212
212
213 &.menu_link_notifications {
213 &.menu_link_notifications {
214 padding: 1px 8px;
214 padding: 1px 8px;
215 }
215 }
216 }
216 }
217
217
218 &:hover,
218 &:hover,
219 &.open,
219 &.open,
220 &.active {
220 &.active {
221 .pill_container a {
221 .pill_container a {
222 // don't select text for the pill container, it has it' own
222 // don't select text for the pill container, it has it' own
223 // hover behaviour
223 // hover behaviour
224 color: @nav-grey;
224 color: @nav-grey;
225 }
225 }
226 }
226 }
227
227
228 &:hover,
228 &:hover,
229 &.open,
229 &.open,
230 &.active {
230 &.active {
231 a {
231 a {
232 color: @grey6;
232 color: @grey6;
233 }
233 }
234 }
234 }
235
235
236 .select2-dropdown-open a {
236 .select2-dropdown-open a {
237 color: @grey6;
237 color: @grey6;
238 }
238 }
239
239
240 .repo-switcher {
240 .repo-switcher {
241 padding-left: 0;
241 padding-left: 0;
242
242
243 .menulabel {
243 .menulabel {
244 padding-left: 0;
244 padding-left: 0;
245 }
245 }
246 }
246 }
247 }
247 }
248
248
249 li ul li {
249 li ul li {
250 background-color:@grey2;
250 background-color:@grey2;
251
251
252 a {
252 a {
253 padding: .5em 0;
253 padding: .5em 0;
254 border-bottom: @border-thickness solid @border-default-color;
254 border-bottom: @border-thickness solid @border-default-color;
255 color: @grey6;
255 color: @grey6;
256 }
256 }
257
257
258 &:last-child a, &.last a{
258 &:last-child a, &.last a{
259 border-bottom: none;
259 border-bottom: none;
260 }
260 }
261
261
262 &:hover {
262 &:hover {
263 background-color: @grey3;
263 background-color: @grey3;
264 }
264 }
265 }
265 }
266
266
267 .submenu {
267 .submenu {
268 margin-top: 5px;
268 margin-top: 5px;
269 }
269 }
270 }
270 }
271
271
272 // SUBMENUS
272 // SUBMENUS
273 .navigation .submenu {
273 .navigation .submenu {
274 display: none;
274 display: none;
275 }
275 }
276
276
277 .navigation li.open {
277 .navigation li.open {
278 .submenu {
278 .submenu {
279 display: block;
279 display: block;
280 }
280 }
281 }
281 }
282
282
283 .navigation li:last-child .submenu {
283 .navigation li:last-child .submenu {
284 right: -20px;
284 right: -20px;
285 left: auto;
285 left: auto;
286 }
286 }
287
287
288 .submenu {
288 .submenu {
289 position: absolute;
289 position: absolute;
290 top: 100%;
290 top: 100%;
291 left: 0;
291 left: 0;
292 min-width: 150px;
292 min-width: 150px;
293 margin: 6px 0 0;
293 margin: 6px 0 0;
294 padding: 0;
294 padding: 0;
295 text-align: left;
295 text-align: left;
296 font-family: @text-light;
296 font-family: @text-light;
297 border-radius: @border-radius;
297 border-radius: @border-radius;
298 z-index: 20;
298 z-index: 20;
299
299
300 li {
300 li {
301 display: block;
301 display: block;
302 margin: 0;
302 margin: 0;
303 padding: 0 .5em;
303 padding: 0 .5em;
304 line-height: 1em;
304 line-height: 1em;
305 color: @grey3;
305 color: @grey3;
306 background-color: @grey6;
306 background-color: @grey6;
307 list-style-type: none;
307 list-style-type: none;
308
308
309 a {
309 a {
310 display: block;
310 display: block;
311 width: 100%;
311 width: 100%;
312 padding: .5em 0;
312 padding: .5em 0;
313 border-right: none;
313 border-right: none;
314 border-bottom: @border-thickness solid white;
314 border-bottom: @border-thickness solid white;
315 color: @grey3;
315 color: @grey3;
316 }
316 }
317
317
318 ul {
318 ul {
319 display: none;
319 display: none;
320 position: absolute;
320 position: absolute;
321 top: 0;
321 top: 0;
322 right: 100%;
322 right: 100%;
323 padding: 0;
323 padding: 0;
324 z-index: 30;
324 z-index: 30;
325 }
325 }
326 &:hover {
326 &:hover {
327 background-color: @grey5;
327 background-color: @grey5;
328 -webkit-transition: background .3s;
328 -webkit-transition: background .3s;
329 -moz-transition: background .3s;
329 -moz-transition: background .3s;
330 -o-transition: background .3s;
330 -o-transition: background .3s;
331 transition: background .3s;
331 transition: background .3s;
332
332
333 ul {
333 ul {
334 display: block;
334 display: block;
335 }
335 }
336 }
336 }
337 }
337 }
338
338
339 }
339 }
340
340
341
341
342
342
343
343
344 // repo dropdown
344 // repo dropdown
345 .quick_repo_menu {
345 .quick_repo_menu {
346 width: 15px;
346 width: 15px;
347 text-align: center;
347 text-align: center;
348 position: relative;
348 position: relative;
349 cursor: pointer;
349 cursor: pointer;
350
350
351 div {
351 div {
352 overflow: visible !important;
352 overflow: visible !important;
353 }
353 }
354
354
355 &.sorting {
355 &.sorting {
356 cursor: auto;
356 cursor: auto;
357 }
357 }
358
358
359 &:hover {
359 &:hover {
360 .menu_items_container {
360 .menu_items_container {
361 position: absolute;
361 position: absolute;
362 display: block;
362 display: block;
363 }
363 }
364 .menu_items {
364 .menu_items {
365 display: block;
365 display: block;
366 }
366 }
367 }
367 }
368
368
369 i {
369 i {
370 margin: 0;
370 margin: 0;
371 color: @grey4;
371 color: @grey4;
372 }
372 }
373
373
374 .menu_items_container {
374 .menu_items_container {
375 position: absolute;
375 position: absolute;
376 top: 0;
376 top: 0;
377 left: 100%;
377 left: 100%;
378 margin: 0;
378 margin: 0;
379 padding: 0;
379 padding: 0;
380 list-style: none;
380 list-style: none;
381 background-color: @grey6;
381 background-color: @grey6;
382 z-index: 999;
382 z-index: 999;
383 text-align: left;
383 text-align: left;
384
384
385 a {
385 a {
386 color: @grey2;
386 color: @grey2;
387 }
387 }
388
388
389 ul.menu_items {
389 ul.menu_items {
390 margin: 0;
390 margin: 0;
391 padding: 0;
391 padding: 0;
392 }
392 }
393
393
394 li {
394 li {
395 margin: 0;
395 margin: 0;
396 padding: 0;
396 padding: 0;
397 line-height: 1em;
397 line-height: 1em;
398 list-style-type: none;
398 list-style-type: none;
399
399
400 a {
400 a {
401 display: block;
401 display: block;
402 height: 16px;
402 height: 16px;
403 padding: 8px; //must add up to td height (28px)
403 padding: 8px; //must add up to td height (28px)
404 width: 120px; // set width
404 width: 120px; // set width
405
405
406 &:hover {
406 &:hover {
407 background-color: @grey5;
407 background-color: @grey5;
408 -webkit-transition: background .3s;
408 -webkit-transition: background .3s;
409 -moz-transition: background .3s;
409 -moz-transition: background .3s;
410 -o-transition: background .3s;
410 -o-transition: background .3s;
411 transition: background .3s;
411 transition: background .3s;
412 }
412 }
413 }
413 }
414 }
414 }
415 }
415 }
416 }
416 }
417
417
418 // Header Repository Switcher
418 // Header Repository Switcher
419 // Select2 Dropdown
419 // Select2 Dropdown
420 #select2-drop.select2-drop.repo-switcher-dropdown {
420 #select2-drop.select2-drop.repo-switcher-dropdown {
421 width: auto !important;
421 width: auto !important;
422 margin-top: 5px;
422 margin-top: 5px;
423 padding: 1em 0;
423 padding: 1em 0;
424 text-align: left;
424 text-align: left;
425 .border-radius-bottom(@border-radius);
425 .border-radius-bottom(@border-radius);
426 border-color: transparent;
426 border-color: transparent;
427 color: @grey6;
427 color: @grey6;
428 background-color: @grey2;
428 background-color: @grey2;
429
429
430 input {
430 input {
431 min-width: 90%;
431 min-width: 90%;
432 }
432 }
433
433
434 ul.select2-result-sub {
434 ul.select2-result-sub {
435
435
436 li {
436 li {
437 line-height: 1em;
437 line-height: 1em;
438
438
439 &:hover,
439 &:hover,
440 &.select2-highlighted {
440 &.select2-highlighted {
441 background-color: @grey3;
441 background-color: @grey3;
442 }
442 }
443 }
443 }
444
444
445 &:before { content: none; }
445 &:before { content: none; }
446 }
446 }
447
447
448 ul.select2-results {
448 ul.select2-results {
449 min-width: 200px;
449 min-width: 200px;
450 margin: 0;
450 margin: 0;
451 padding: 0;
451 padding: 0;
452 list-style-type: none;
452 list-style-type: none;
453 overflow-x: visible;
453 overflow-x: visible;
454 overflow-y: scroll;
454 overflow-y: scroll;
455
455
456 li {
456 li {
457 padding: 0 8px;
457 padding: 0 8px;
458 line-height: 1em;
458 line-height: 1em;
459 color: @grey6;
459 color: @grey6;
460
460
461 &>.select2-result-label {
461 &>.select2-result-label {
462 padding: 8px 0;
462 padding: 8px 0;
463 border-bottom: @border-thickness solid @grey3;
463 border-bottom: @border-thickness solid @grey3;
464 white-space: nowrap;
464 white-space: nowrap;
465 color: @grey5;
465 color: @grey5;
466 cursor: pointer;
466 cursor: pointer;
467 }
467 }
468
468
469 &.select2-result-with-children {
469 &.select2-result-with-children {
470 margin: 0;
470 margin: 0;
471 padding: 0;
471 padding: 0;
472 }
472 }
473
473
474 &.select2-result-unselectable > .select2-result-label {
474 &.select2-result-unselectable > .select2-result-label {
475 margin: 0 8px;
475 margin: 0 8px;
476 }
476 }
477
477
478 }
478 }
479 }
479 }
480
480
481 ul.select2-result-sub {
481 ul.select2-result-sub {
482 margin: 0;
482 margin: 0;
483 padding: 0;
483 padding: 0;
484
484
485 li {
485 li {
486 display: block;
486 display: block;
487 margin: 0;
487 margin: 0;
488 border-right: none;
488 border-right: none;
489 line-height: 1em;
489 line-height: 1em;
490 font-family: @text-light;
490 font-family: @text-light;
491 color: @grey2;
491 color: @grey2;
492 list-style-type: none;
492 list-style-type: none;
493
493
494 &:hover {
494 &:hover {
495 background-color: @grey3;
495 background-color: @grey3;
496 }
496 }
497 }
497 }
498 }
498 }
499 }
499 }
500
500
501
501
502 #context-bar {
502 #context-bar {
503 display: block;
503 display: block;
504 margin: 0 auto;
504 margin: 0 auto;
505 padding: 0 @header-padding;
505 padding: 0 @header-padding;
506 background-color: @grey6;
506 background-color: @grey6;
507 border-bottom: @border-thickness solid @grey5;
507 border-bottom: @border-thickness solid @grey5;
508
508
509 .clear {
509 .clear {
510 clear: both;
510 clear: both;
511 }
511 }
512 }
512 }
513
513
514 ul#context-pages {
514 ul#context-pages {
515 li {
515 li {
516 line-height: 1em;
516 line-height: 1em;
517 list-style-type: none;
517 list-style-type: none;
518
518
519 a {
519 a {
520 color: @grey3;
520 color: @grey3;
521 }
521 }
522
522
523 &.active {
523 &.active {
524 // special case, non-variable color
524 // special case, non-variable color
525 border-bottom: 4px solid @nav-grey;
525 border-bottom: 4px solid @nav-grey;
526
526
527 a {
527 a {
528 color: @grey1;
528 color: @grey1;
529 }
529 }
530 }
530 }
531 }
531 }
532 }
532 }
533
533
534 // PAGINATION
534 // PAGINATION
535
535
536 .pagination {
536 .pagination {
537 border: @border-thickness solid @rcblue;
537 border: @border-thickness solid @rcblue;
538 color: @rcblue;
538 color: @rcblue;
539
539
540 .current {
540 .current {
541 color: @grey4;
541 color: @grey4;
542 }
542 }
543 }
543 }
544
544
545 .dataTables_processing {
545 .dataTables_processing {
546 text-align: center;
546 text-align: center;
547 font-size: 1.1em;
547 font-size: 1.1em;
548 position: relative;
548 position: relative;
549 top: 95px;
549 top: 95px;
550 }
550 }
551
551
552 .dataTables_paginate, .pagination-wh {
552 .dataTables_paginate, .pagination-wh {
553 text-align: left;
553 text-align: left;
554 display: inline-block;
554 display: inline-block;
555 border-left: 1px solid @rcblue;
555 border-left: 1px solid @rcblue;
556 float: none;
556 float: none;
557 overflow: hidden;
557 overflow: hidden;
558
558
559 .paginate_button, .pager_curpage,
559 .paginate_button, .pager_curpage,
560 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
560 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
561 display: inline-block;
561 display: inline-block;
562 padding: @menupadding/4 @menupadding;
562 padding: @menupadding/4 @menupadding;
563 border: 1px solid @rcblue;
563 border: 1px solid @rcblue;
564 border-left: 0;
564 border-left: 0;
565 color: @rcblue;
565 color: @rcblue;
566 cursor: pointer;
566 cursor: pointer;
567 float: left;
567 float: left;
568 }
568 }
569
569
570 .pager_curpage, .pager_dotdot,
570 .pager_curpage, .pager_dotdot,
571 .paginate_button.current, .paginate_button.disabled,
571 .paginate_button.current, .paginate_button.disabled,
572 .disabled {
572 .disabled {
573 color: @grey3;
573 color: @grey3;
574 cursor: default;
574 cursor: default;
575 }
575 }
576
576
577 .ellipsis {
577 .ellipsis {
578 display: inline-block;
578 display: inline-block;
579 text-align: left;
579 text-align: left;
580 padding: @menupadding/4 @menupadding;
580 padding: @menupadding/4 @menupadding;
581 border: 1px solid @rcblue;
581 border: 1px solid @rcblue;
582 border-left: 0;
582 border-left: 0;
583 float: left;
583 float: left;
584 }
584 }
585 }
585 }
586
586
587 // SIDEBAR
587 // SIDEBAR
588
588
589 .sidebar {
589 .sidebar {
590 .block-left;
590 .block-left;
591 clear: left;
591 clear: left;
592 max-width: @sidebar-width;
592 max-width: @sidebar-width;
593 margin-right: @sidebarpadding;
593 margin-right: @sidebarpadding;
594 padding-right: @sidebarpadding;
594 padding-right: @sidebarpadding;
595 font-family: @text-regular;
595 font-family: @text-regular;
596 color: @grey1;
596 color: @grey1;
597
597
598 &#graph_nodes {
598 &#graph_nodes {
599 clear:both;
599 clear:both;
600 width: auto;
600 width: auto;
601 margin-left: -100px;
601 margin-left: -100px;
602 padding: 0;
602 padding: 0;
603 border: none;
603 border: none;
604 }
604 }
605
605
606 .nav-pills {
606 .nav-pills {
607 margin: 0;
607 margin: 0;
608 }
608 }
609
609
610 .nav {
610 .nav {
611 list-style: none;
611 list-style: none;
612 padding: 0;
612 padding: 0;
613
613
614 li {
614 li {
615 padding-bottom: @menupadding;
615 padding-bottom: @menupadding;
616 line-height: 1em;
616 line-height: 1em;
617 color: @grey4;
617 color: @grey4;
618 list-style-type: none;
618 list-style-type: none;
619
619
620 &.active a {
620 &.active a {
621 color: @grey2;
621 color: @grey2;
622 }
622 }
623
623
624 a {
624 a {
625 color: @grey4;
625 color: @grey4;
626 }
626 }
627 }
627 }
628
628
629 }
629 }
630 }
630 }
631
631
632 .main_filter_help_box {
632 .main_filter_help_box {
633 padding: 7px 7px;
633 padding: 7px 7px;
634 border-top: 1px solid @grey4;
634 border-top: 1px solid @grey4;
635 border-right: 1px solid @grey4;
635 border-right: 1px solid @grey4;
636 border-bottom: 1px solid @grey4;
636 border-bottom: 1px solid @grey4;
637 display: inline-block;
637 display: inline-block;
638 vertical-align: top;
638 vertical-align: top;
639 background: inherit;
639 background: inherit;
640 position: absolute;
640 position: absolute;
641 right: 8px;
641 right: 8px;
642 top: 9px;
642 top: 9px;
643 }
643 }
644
644
645 .main_filter_input_box {
645 .main_filter_input_box {
646 display: inline-block;
646 display: inline-block;
647
648 .searchItems {
649 display:flex;
650 background: #666666;
651 padding: 0px;
652
653 a {
654 border: none !important;
655 }
656 }
657
658 .searchTag {
659 line-height: 28px;
660 padding: 0px 4px;
661
662 .tag {
663 color: @nav-grey;
664 border-color: @nav-grey;
665 }
666 }
667
668 .searchTagFilter {
669 background-color: @grey3 !important;
670 }
671
672 .searchTagHelp {
673 background-color: @grey2 !important;
674 }
675 .searchTagHelp:hover {
676 background-color: @grey2 !important;
677 }
678 .searchTagInput {
679 background-color: @grey3 !important;
680 }
647 }
681 }
648
682
649 .main_filter_box {
683 .main_filter_box {
650 margin: 9px 0 0 0;
684 margin: 9px 0 0 0;
651 }
685 }
652
686
653 #main_filter_help {
687 #main_filter_help {
654 background: @grey3;
688 background: @grey3;
655 border: 1px solid black;
689 border: 1px solid black;
656 position: absolute;
690 position: absolute;
657 white-space: pre;
691 white-space: pre;
658 z-index: 9999;
692 z-index: 9999;
659 color: @nav-grey;
693 color: @nav-grey;
660 margin: 1px 7px;
694 margin: 1px 7px;
661 padding: 0 10px;
695 padding: 0 10px;
662 }
696 }
663
697
664 .main_filter_input {
698 .main_filter_input {
665 padding: 5px;
699 padding: 5px;
666 min-width: 260px;
700 min-width: 260px;
667 color: @nav-grey;
701 color: @nav-grey;
668 background: @grey3;
702 background: @grey3;
669 min-height: 18px;
703 min-height: 18px;
670
704 border:none;
705 border-radius: 0;
671
706
672 &:active {
707 &:active {
673 color: @grey2 !important;
708 color: @grey2 !important;
674 background: white !important;
709 background: white !important;
675 }
710 }
676 &:focus {
711 &:focus {
677 color: @grey2 !important;
712 color: @grey2 !important;
678 background: white !important;
713 background: white !important;
679 }
714 }
680 }
715 }
681
716
682
717
683
718
684 .main_filter_input::placeholder {
719 .main_filter_input::placeholder {
685 color: @nav-grey;
720 color: @nav-grey;
686 opacity: 1;
721 opacity: 1;
687 }
722 }
688
723
689 .notice-box {
724 .notice-box {
690 display:block !important;
725 display:block !important;
691 padding: 9px 0 !important;
726 padding: 9px 0 !important;
692 }
727 }
693
728
694 .menulabel-notice {
729 .menulabel-notice {
695 border: 1px solid @color5;
730 border: 1px solid @color5;
696 padding:7px 10px;
731 padding:7px 10px;
697 color: @color5;
732 color: @color5;
698 }
733 }
@@ -1,936 +1,948 b''
1 /**
1 /**
2 * Ajax Autocomplete for jQuery, version dev
2 * Ajax Autocomplete for jQuery, version dev
3 * RhodeCode additions
3 * RhodeCode additions
4 * (c) 2014 Tomas Kirda
4 * (c) 2014 Tomas Kirda
5 * (c) 2014 Marcin Kuzminski
5 * (c) 2014 Marcin Kuzminski
6 *
6 *
7 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
7 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
8 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
8 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
9 */
9 */
10 // Expose plugin as an AMD module if AMD loader is present:
10 // Expose plugin as an AMD module if AMD loader is present:
11 (function (factory) {
11 (function (factory) {
12 'use strict';
12 'use strict';
13 if (typeof define === 'function' && define.amd) {
13 if (typeof define === 'function' && define.amd) {
14 // AMD. Register as an anonymous module.
14 // AMD. Register as an anonymous module.
15 define(['jquery'], factory);
15 define(['jquery'], factory);
16 } else if (typeof exports === 'object' && typeof require === 'function') {
16 } else if (typeof exports === 'object' && typeof require === 'function') {
17 // Browserify
17 // Browserify
18 factory(require('jquery'));
18 factory(require('jquery'));
19 } else {
19 } else {
20 // Browser globals
20 // Browser globals
21 factory(jQuery);
21 factory(jQuery);
22 }
22 }
23 }(function ($) {
23 }(function ($) {
24 'use strict';
24 'use strict';
25
25
26 var
26 var
27 utils = (function () {
27 utils = (function () {
28 return {
28 return {
29 escapeRegExChars: function (value) {
29 escapeRegExChars: function (value) {
30 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
30 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
31 },
31 },
32 createNode: function (containerClass) {
32 createNode: function (containerClass) {
33 var div = document.createElement('div');
33 var div = document.createElement('div');
34 div.className = containerClass;
34 div.className = containerClass;
35 div.style.position = 'absolute';
35 div.style.position = 'absolute';
36 div.style.display = 'none';
36 div.style.display = 'none';
37 return div;
37 return div;
38 }
38 }
39 };
39 };
40 }()),
40 }()),
41
41
42 keys = {
42 keys = {
43 ESC: 27,
43 ESC: 27,
44 TAB: 9,
44 TAB: 9,
45 RETURN: 13,
45 RETURN: 13,
46 LEFT: 37,
46 LEFT: 37,
47 UP: 38,
47 UP: 38,
48 RIGHT: 39,
48 RIGHT: 39,
49 DOWN: 40
49 DOWN: 40
50 };
50 };
51
51
52 function Autocomplete(el, options) {
52 function Autocomplete(el, options) {
53 var noop = function () { },
53 var noop = function () { },
54 that = this,
54 that = this,
55 defaults = {
55 defaults = {
56 ajaxSettings: {},
56 ajaxSettings: {},
57 autoSelectFirst: false,
57 autoSelectFirst: false,
58 appendTo: document.body,
58 appendTo: document.body,
59 serviceUrl: null,
59 serviceUrl: null,
60 lookup: null,
60 lookup: null,
61 width: 'auto',
61 width: 'auto',
62 minChars: 1,
62 minChars: 1,
63 maxHeight: 300,
63 maxHeight: 300,
64 deferRequestBy: 0,
64 deferRequestBy: 0,
65 params: {},
65 params: {},
66 formatResult: Autocomplete.formatResult,
66 formatResult: Autocomplete.formatResult,
67 lookupFilter: Autocomplete.lookupFilter,
67 lookupFilter: Autocomplete.lookupFilter,
68 delimiter: null,
68 delimiter: null,
69 zIndex: 9999,
69 zIndex: 9999,
70 type: 'GET',
70 type: 'GET',
71 noCache: false,
71 noCache: false,
72 onSelect: noop,
72 onSelect: noop,
73 onSearchStart: noop,
73 onSearchStart: noop,
74 onSearchComplete: noop,
74 onSearchComplete: noop,
75 onSearchError: noop,
75 onSearchError: noop,
76 containerClass: 'autocomplete-suggestions',
76 containerClass: 'autocomplete-suggestions',
77 tabDisabled: false,
77 tabDisabled: false,
78 dataType: 'text',
78 dataType: 'text',
79 currentRequest: null,
79 currentRequest: null,
80 triggerSelectOnValidInput: false,
80 triggerSelectOnValidInput: false,
81 preventBadQueries: true,
81 preventBadQueries: true,
82 paramName: 'query',
82 paramName: 'query',
83 transformResult: function (response) {
83 transformResult: function (response) {
84 return typeof response === 'string' ? $.parseJSON(response) : response;
84 return typeof response === 'string' ? $.parseJSON(response) : response;
85 },
85 },
86 showNoSuggestionNotice: false,
86 showNoSuggestionNotice: false,
87 noSuggestionNotice: _gettext('No results'),
87 noSuggestionNotice: _gettext('No results'),
88 orientation: 'bottom',
88 orientation: 'bottom',
89 forceFixPosition: false,
89 forceFixPosition: false,
90 replaceOnArrowKey: true
90 replaceOnArrowKey: true
91 };
91 };
92
92
93 // Shared variables:
93 // Shared variables:
94 that.element = el;
94 that.element = el;
95 that.el = $(el);
95 that.el = $(el);
96 that.suggestions = [];
96 that.suggestions = [];
97 that.badQueries = [];
97 that.badQueries = [];
98 that.selectedIndex = -1;
98 that.selectedIndex = -1;
99 that.currentValue = that.element.value;
99 that.currentValue = that.element.value;
100 that.intervalId = 0;
100 that.intervalId = 0;
101 that.cachedResponse = {};
101 that.cachedResponse = {};
102 that.onChangeInterval = null;
102 that.onChangeInterval = null;
103 that.onChange = null;
103 that.onChange = null;
104 that.isLocal = false;
104 that.isLocal = false;
105 that.suggestionsContainer = null;
105 that.suggestionsContainer = null;
106 that.noSuggestionsContainer = null;
106 that.noSuggestionsContainer = null;
107 that.options = $.extend({}, defaults, options);
107 that.options = $.extend({}, defaults, options);
108 that.classes = {
108 that.classes = {
109 selected: 'autocomplete-selected',
109 selected: 'autocomplete-selected',
110 suggestion: 'autocomplete-suggestion'
110 suggestion: 'autocomplete-suggestion'
111 };
111 };
112 that.hint = null;
112 that.hint = null;
113 that.hintValue = '';
113 that.hintValue = '';
114 that.selection = null;
114 that.selection = null;
115
115
116 // Initialize and set options:
116 // Initialize and set options:
117 that.initialize();
117 that.initialize();
118 that.setOptions(options);
118 that.setOptions(options);
119 }
119 }
120
120
121 Autocomplete.utils = utils;
121 Autocomplete.utils = utils;
122
122
123 $.Autocomplete = Autocomplete;
123 $.Autocomplete = Autocomplete;
124
124
125 Autocomplete.formatResult = function (suggestion, currentValue) {
125 Autocomplete.formatResult = function (suggestion, currentValue) {
126 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
126 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
127 return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
127 return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
128 };
128 };
129 Autocomplete.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
129 Autocomplete.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
130 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
130 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
131 };
131 };
132
132
133 Autocomplete.prototype = {
133 Autocomplete.prototype = {
134
134
135 killerFn: null,
135 killerFn: null,
136
136
137 initialize: function () {
137 initialize: function () {
138 var that = this,
138 var that = this,
139 suggestionSelector = '.' + that.classes.suggestion,
139 suggestionSelector = '.' + that.classes.suggestion,
140 selected = that.classes.selected,
140 selected = that.classes.selected,
141 options = that.options,
141 options = that.options,
142 container;
142 container;
143
143
144 // Remove autocomplete attribute to prevent native suggestions:
144 // Remove autocomplete attribute to prevent native suggestions:
145 that.element.setAttribute('autocomplete', 'off');
145 that.element.setAttribute('autocomplete', 'off');
146
146
147 that.killerFn = function (e) {
147 that.killerFn = function (e) {
148 if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
148 if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
149 that.killSuggestions();
149 that.killSuggestions();
150 that.disableKillerFn();
150 that.disableKillerFn();
151 }
151 }
152 };
152 };
153
153
154 // html() deals with many types: htmlString or Element or Array or jQuery
154 // html() deals with many types: htmlString or Element or Array or jQuery
155 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
155 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
156 .html(this.options.noSuggestionNotice).get(0);
156 .html(this.options.noSuggestionNotice).get(0);
157
157
158 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
158 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
159
159
160 container = $(that.suggestionsContainer);
160 container = $(that.suggestionsContainer);
161
161
162 container.appendTo(options.appendTo);
162 container.appendTo(options.appendTo);
163
163
164 // Only set width if it was provided:
164 // Only set width if it was provided:
165 if (options.width !== 'auto') {
165 if (options.width !== 'auto') {
166 container.width(options.width);
166 container.width(options.width);
167 }
167 }
168
168
169 // Listen for mouse over event on suggestions list:
169 // Listen for mouse over event on suggestions list:
170 container.on('mouseover.autocomplete', suggestionSelector, function () {
170 container.on('mouseover.autocomplete', suggestionSelector, function () {
171 that.activate($(this).data('index'));
171 that.activate($(this).data('index'));
172 });
172 });
173
173
174 // Deselect active element when mouse leaves suggestions container:
174 // Deselect active element when mouse leaves suggestions container:
175 container.on('mouseout.autocomplete', function () {
175 container.on('mouseout.autocomplete', function () {
176 that.selectedIndex = -1;
176 that.selectedIndex = -1;
177 container.children('.' + selected).removeClass(selected);
177 container.children('.' + selected).removeClass(selected);
178 });
178 });
179
179
180 // Listen for click event on suggestions list:
180 // Listen for click event on suggestions list:
181 container.on('click.autocomplete', suggestionSelector, function () {
181 container.on('click.autocomplete', suggestionSelector, function () {
182 that.select($(this).data('index'));
182 that.select($(this).data('index'));
183 });
183 });
184
184
185 that.fixPositionCapture = function () {
185 that.fixPositionCapture = function () {
186 if (that.visible) {
186 if (that.visible) {
187 that.fixPosition();
187 that.fixPosition();
188 }
188 }
189 };
189 };
190
190
191 $(window).on('resize.autocomplete', that.fixPositionCapture);
191 $(window).on('resize.autocomplete', that.fixPositionCapture);
192
192
193 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
193 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
194 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
194 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
195 that.el.on('blur.autocomplete', function () { that.onBlur(); });
195 that.el.on('blur.autocomplete', function () { that.onBlur(); });
196 that.el.on('focus.autocomplete', function () { that.onFocus(); });
196 that.el.on('focus.autocomplete', function () { that.onFocus(); });
197 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
197 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
198 },
198 },
199
199
200 onFocus: function () {
200 onFocus: function () {
201 var that = this;
201 var that = this;
202 that.fixPosition();
202 that.fixPosition();
203 if (that.options.minChars <= that.el.val().length) {
203 if (that.options.minChars <= that.el.val().length) {
204 that.onValueChange();
204 that.onValueChange();
205 }
205 }
206 },
206 },
207
207
208 onBlur: function () {
208 onBlur: function () {
209 this.enableKillerFn();
209 this.enableKillerFn();
210 },
210 },
211
211
212 setOptions: function (suppliedOptions) {
212 setOptions: function (suppliedOptions) {
213 var that = this,
213 var that = this,
214 options = that.options;
214 options = that.options;
215
215
216 $.extend(options, suppliedOptions);
216 $.extend(options, suppliedOptions);
217
217
218 that.isLocal = $.isArray(options.lookup);
218 that.isLocal = $.isArray(options.lookup);
219
219
220 if (that.isLocal) {
220 if (that.isLocal) {
221 options.lookup = that.verifySuggestionsFormat(options.lookup);
221 options.lookup = that.verifySuggestionsFormat(options.lookup);
222 }
222 }
223
223
224 options.orientation = that.validateOrientation(options.orientation, 'bottom');
224 options.orientation = that.validateOrientation(options.orientation, 'bottom');
225
225
226 // Adjust height, width and z-index:
226 // Adjust height, width and z-index:
227 $(that.suggestionsContainer).css({
227 $(that.suggestionsContainer).css({
228 'max-height': options.maxHeight + 'px',
228 'max-height': options.maxHeight + 'px',
229 'width': options.width + 'px',
229 'width': options.width + 'px',
230 'z-index': options.zIndex
230 'z-index': options.zIndex
231 });
231 });
232 },
232 },
233
233
234 clearCache: function () {
234 clearCache: function () {
235 this.cachedResponse = {};
235 this.cachedResponse = {};
236 this.badQueries = [];
236 this.badQueries = [];
237 },
237 },
238
238
239 clear: function () {
239 clear: function () {
240 this.clearCache();
240 this.clearCache();
241 this.currentValue = '';
241 this.currentValue = '';
242 this.suggestions = [];
242 this.suggestions = [];
243 },
243 },
244
244
245 disable: function () {
245 disable: function () {
246 var that = this;
246 var that = this;
247 that.disabled = true;
247 that.disabled = true;
248 if (that.currentRequest) {
248 if (that.currentRequest) {
249 that.currentRequest.abort();
249 that.currentRequest.abort();
250 }
250 }
251 },
251 },
252
252
253 enable: function () {
253 enable: function () {
254 this.disabled = false;
254 this.disabled = false;
255 },
255 },
256
256
257 fixPosition: function () {
257 fixPosition: function () {
258 // Use only when container has already its content
258 // Use only when container has already its content
259
259
260 var that = this,
260 var that = this,
261 $container = $(that.suggestionsContainer),
261 $container = $(that.suggestionsContainer),
262 containerParent = $container.parent().get(0);
262 containerParent = $container.parent().get(0);
263 // Fix position automatically when appended to body.
263 // Fix position automatically when appended to body.
264 // In other cases force parameter must be given.
264 // In other cases force parameter must be given.
265 if (containerParent !== document.body && !that.options.forceFixPosition)
265 if (containerParent !== document.body && !that.options.forceFixPosition)
266 return;
266 return;
267
267
268 // Choose orientation
268 // Choose orientation
269 var orientation = that.options.orientation,
269 var orientation = that.options.orientation,
270 containerHeight = $container.outerHeight(),
270 containerHeight = $container.outerHeight(),
271 height = that.el.outerHeight(),
271 height = that.el.outerHeight(),
272 offset = that.el.offset(),
272 offset = that.el.offset(),
273 styles = { 'top': offset.top, 'left': offset.left };
273 styles = { 'top': offset.top, 'left': offset.left };
274
274
275 if (orientation == 'auto') {
275 if (orientation == 'auto') {
276 var viewPortHeight = $(window).height(),
276 var viewPortHeight = $(window).height(),
277 scrollTop = $(window).scrollTop(),
277 scrollTop = $(window).scrollTop(),
278 topOverflow = -scrollTop + offset.top - containerHeight,
278 topOverflow = -scrollTop + offset.top - containerHeight,
279 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
279 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
280
280
281 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
281 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
282 ? 'top'
282 ? 'top'
283 : 'bottom';
283 : 'bottom';
284 }
284 }
285
285
286 if (orientation === 'top') {
286 if (orientation === 'top') {
287 styles.top += -containerHeight;
287 styles.top += -containerHeight;
288 } else {
288 } else {
289 styles.top += height;
289 styles.top += height;
290 }
290 }
291
291
292 // If container is not positioned to body,
292 // If container is not positioned to body,
293 // correct its position using offset parent offset
293 // correct its position using offset parent offset
294 if(containerParent !== document.body) {
294 if(containerParent !== document.body) {
295 var opacity = $container.css('opacity'),
295 var opacity = $container.css('opacity'),
296 parentOffsetDiff;
296 parentOffsetDiff;
297
297
298 if (!that.visible){
298 if (!that.visible){
299 $container.css('opacity', 0).show();
299 $container.css('opacity', 0).show();
300 }
300 }
301
301
302 parentOffsetDiff = $container.offsetParent().offset();
302 parentOffsetDiff = $container.offsetParent().offset();
303 styles.top -= parentOffsetDiff.top;
303 styles.top -= parentOffsetDiff.top;
304 styles.left -= parentOffsetDiff.left;
304 styles.left -= parentOffsetDiff.left;
305
305
306 if (!that.visible){
306 if (!that.visible){
307 $container.css('opacity', opacity).hide();
307 $container.css('opacity', opacity).hide();
308 }
308 }
309 }
309 }
310
310
311 // -2px to account for suggestions border.
311 // -2px to account for suggestions border.
312 if (that.options.width === 'auto') {
312 if (that.options.width === 'auto') {
313 styles.width = (that.el.outerWidth() - 2) + 'px';
313 styles.width = (that.el.outerWidth() - 2) + 'px';
314 }
314 }
315
315
316 $container.css(styles);
316 $container.css(styles);
317 },
317 },
318
318
319 enableKillerFn: function () {
319 enableKillerFn: function () {
320 var that = this;
320 var that = this;
321 $(document).on('click.autocomplete', that.killerFn);
321 $(document).on('click.autocomplete', that.killerFn);
322 },
322 },
323
323
324 disableKillerFn: function () {
324 disableKillerFn: function () {
325 var that = this;
325 var that = this;
326 $(document).off('click.autocomplete', that.killerFn);
326 $(document).off('click.autocomplete', that.killerFn);
327 },
327 },
328
328
329 killSuggestions: function () {
329 killSuggestions: function () {
330 var that = this;
330 var that = this;
331 that.stopKillSuggestions();
331 that.stopKillSuggestions();
332 that.intervalId = window.setInterval(function () {
332 that.intervalId = window.setInterval(function () {
333 that.hide();
333 that.hide();
334 that.stopKillSuggestions();
334 that.stopKillSuggestions();
335 }, 50);
335 }, 50);
336 },
336 },
337
337
338 stopKillSuggestions: function () {
338 stopKillSuggestions: function () {
339 window.clearInterval(this.intervalId);
339 window.clearInterval(this.intervalId);
340 },
340 },
341
341
342 isCursorAtEnd: function () {
342 isCursorAtEnd: function () {
343 var that = this,
343 var that = this,
344 valLength = that.el.val().length,
344 valLength = that.el.val().length,
345 selectionStart = that.element.selectionStart,
345 selectionStart = that.element.selectionStart,
346 range;
346 range;
347
347
348 if (typeof selectionStart === 'number') {
348 if (typeof selectionStart === 'number') {
349 return selectionStart === valLength;
349 return selectionStart === valLength;
350 }
350 }
351 if (document.selection) {
351 if (document.selection) {
352 range = document.selection.createRange();
352 range = document.selection.createRange();
353 range.moveStart('character', -valLength);
353 range.moveStart('character', -valLength);
354 return valLength === range.text.length;
354 return valLength === range.text.length;
355 }
355 }
356 return true;
356 return true;
357 },
357 },
358
358
359 onKeyPress: function (e) {
359 onKeyPress: function (e) {
360 var that = this;
360 var that = this;
361
361
362 // If suggestions are hidden and user presses arrow down, display suggestions:
362 // If suggestions are hidden and user presses arrow down, display suggestions:
363 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
363 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
364 that.suggest();
364 that.suggest();
365 return;
365 return;
366 }
366 }
367
367
368 if (that.disabled || !that.visible) {
368 if (that.disabled || !that.visible) {
369 return;
369 return;
370 }
370 }
371
371
372 switch (e.which) {
372 switch (e.which) {
373 case keys.ESC:
373 case keys.ESC:
374 that.el.val(that.currentValue);
374 that.el.val(that.currentValue);
375 that.hide();
375 that.hide();
376 break;
376 break;
377 case keys.RIGHT:
377 case keys.RIGHT:
378 if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
378 if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
379 that.selectHint();
379 that.selectHint();
380 break;
380 break;
381 }
381 }
382 return;
382 return;
383 case keys.TAB:
383 case keys.TAB:
384 if (that.hint && that.options.onHint) {
384 if (that.hint && that.options.onHint) {
385 that.selectHint();
385 that.selectHint();
386 return;
386 return;
387 }
387 }
388 // Fall through to RETURN
388 // Fall through to RETURN
389 case keys.RETURN:
389 case keys.RETURN:
390 if (that.selectedIndex === -1) {
390 if (that.selectedIndex === -1) {
391 that.hide();
391 that.hide();
392 return;
392 return;
393 }
393 }
394 that.select(that.selectedIndex);
394 that.select(that.selectedIndex);
395 if (e.which === keys.TAB && that.options.tabDisabled === false) {
395 if (e.which === keys.TAB && that.options.tabDisabled === false) {
396 return;
396 return;
397 }
397 }
398 break;
398 break;
399 case keys.UP:
399 case keys.UP:
400 that.moveUp();
400 that.moveUp();
401 break;
401 break;
402 case keys.DOWN:
402 case keys.DOWN:
403 that.moveDown();
403 that.moveDown();
404 break;
404 break;
405 default:
405 default:
406 return;
406 return;
407 }
407 }
408
408
409 // Cancel event if function did not return:
409 // Cancel event if function did not return:
410 e.stopImmediatePropagation();
410 e.stopImmediatePropagation();
411 e.preventDefault();
411 e.preventDefault();
412 },
412 },
413
413
414 onKeyUp: function (e) {
414 onKeyUp: function (e) {
415 var that = this;
415 var that = this;
416
416
417 if (that.disabled) {
417 if (that.disabled) {
418 return;
418 return;
419 }
419 }
420
420
421 switch (e.which) {
421 switch (e.which) {
422 case keys.UP:
422 case keys.UP:
423 case keys.DOWN:
423 case keys.DOWN:
424 return;
424 return;
425 }
425 }
426
426
427 clearInterval(that.onChangeInterval);
427 clearInterval(that.onChangeInterval);
428
428
429 if (that.currentValue !== that.el.val()) {
429 if (that.currentValue !== that.el.val()) {
430 that.findBestHint();
430 that.findBestHint();
431 if (that.options.deferRequestBy > 0) {
431 if (that.options.deferRequestBy > 0) {
432 // Defer lookup in case when value changes very quickly:
432 // Defer lookup in case when value changes very quickly:
433 that.onChangeInterval = setInterval(function () {
433 that.onChangeInterval = setInterval(function () {
434 that.onValueChange();
434 that.onValueChange();
435 }, that.options.deferRequestBy);
435 }, that.options.deferRequestBy);
436 } else {
436 } else {
437 that.onValueChange();
437 that.onValueChange();
438 }
438 }
439 }
439 }
440 },
440 },
441
441
442 onValueChange: function () {
442 onValueChange: function () {
443 var that = this,
443 var that = this,
444 options = that.options,
444 options = that.options,
445 value = that.el.val(),
445 value = that.el.val(),
446 query = that.getQuery(value),
446 query = that.getQuery(value),
447 index;
447 index;
448
448
449 if (that.selection && that.currentValue !== query) {
449 if (that.selection && that.currentValue !== query) {
450 that.selection = null;
450 that.selection = null;
451 (options.onInvalidateSelection || $.noop).call(that.element);
451 (options.onInvalidateSelection || $.noop).call(that.element);
452 }
452 }
453
453
454 clearInterval(that.onChangeInterval);
454 clearInterval(that.onChangeInterval);
455 that.currentValue = value;
455 that.currentValue = value;
456 that.selectedIndex = -1;
456 that.selectedIndex = -1;
457
457
458 // Check existing suggestion for the match before proceeding:
458 // Check existing suggestion for the match before proceeding:
459 if (options.triggerSelectOnValidInput) {
459 if (options.triggerSelectOnValidInput) {
460 index = that.findSuggestionIndex(query);
460 index = that.findSuggestionIndex(query);
461 if (index !== -1) {
461 if (index !== -1) {
462 that.select(index);
462 that.select(index);
463 return;
463 return;
464 }
464 }
465 }
465 }
466
466
467 if (query.length < options.minChars) {
467 if (query.length < options.minChars) {
468 that.hide();
468 that.hide();
469 } else {
469 } else {
470 that.getSuggestions(query);
470 that.getSuggestions(query);
471 }
471 }
472 },
472 },
473
473
474 findSuggestionIndex: function (query) {
474 findSuggestionIndex: function (query) {
475 var that = this,
475 var that = this,
476 index = -1,
476 index = -1,
477 queryLowerCase = query.toLowerCase();
477 queryLowerCase = query.toLowerCase();
478
478
479 $.each(that.suggestions, function (i, suggestion) {
479 $.each(that.suggestions, function (i, suggestion) {
480 if (suggestion.value.toLowerCase() === queryLowerCase) {
480 if (suggestion.value.toLowerCase() === queryLowerCase) {
481 index = i;
481 index = i;
482 return false;
482 return false;
483 }
483 }
484 });
484 });
485
485
486 return index;
486 return index;
487 },
487 },
488
488
489 getQuery: function (value) {
489 getQuery: function (value) {
490 var delimiter = this.options.delimiter,
490 var delimiter = this.options.delimiter,
491 parts;
491 parts;
492
492
493 if (!delimiter) {
493 if (!delimiter) {
494 return value;
494 return value;
495 }
495 }
496 parts = value.split(delimiter);
496 parts = value.split(delimiter);
497 return $.trim(parts[parts.length - 1]);
497 return $.trim(parts[parts.length - 1]);
498 },
498 },
499
499
500 getSuggestionsLocal: function (query) {
500 getSuggestionsLocal: function (query) {
501 var that = this,
501 var that = this,
502 options = that.options,
502 options = that.options,
503 queryLowerCase = query.toLowerCase(),
503 queryLowerCase = query.toLowerCase(),
504 data;
504 data;
505
505
506 // re-pack the data as it was comming from AJAX
506 // re-pack the data as it was comming from AJAX
507 data = {
507 data = {
508 suggestions: data
508 suggestions: data
509 };
509 };
510 return data;
510 return data;
511 },
511 },
512
512
513 getSuggestions: function (query) {
513 getSuggestions: function (query) {
514 var response,
514 var response,
515 that = this,
515 that = this,
516 options = that.options,
516 options = that.options,
517 serviceUrl = options.serviceUrl,
517 serviceUrl = options.serviceUrl,
518 params,
518 params,
519 cacheKey,
519 cacheKey,
520 ajaxSettings;
520 ajaxSettings;
521
521
522 options.params[options.paramName] = query;
522 options.params[options.paramName] = query;
523 params = options.ignoreParams ? null : options.params;
523 params = options.ignoreParams ? null : options.params;
524
524
525 if (that.isLocal) {
525 if (that.isLocal) {
526 response = that.getSuggestionsLocal(query);
526 response = that.getSuggestionsLocal(query);
527 } else {
527 } else {
528 if ($.isFunction(serviceUrl)) {
528 if ($.isFunction(serviceUrl)) {
529 serviceUrl = serviceUrl.call(that.element, query);
529 serviceUrl = serviceUrl.call(that.element, query);
530 }
530 }
531 cacheKey = serviceUrl + '?' + $.param(params || {});
531
532 var callParams = {};
533 //make an evaluated copy of params
534 $.each(params, function(index, value) {
535 if($.isFunction(value)){
536 callParams[index] = value();
537 }
538 else {
539 callParams[index] = value;
540 }
541 });
542
543 cacheKey = serviceUrl + '?' + $.param(callParams);
532 response = that.cachedResponse[cacheKey];
544 response = that.cachedResponse[cacheKey];
533 }
545 }
534
546
535 if (response && $.isArray(response.suggestions)) {
547 if (response && $.isArray(response.suggestions)) {
536 that.suggestions = response.suggestions;
548 that.suggestions = response.suggestions;
537 that.suggest();
549 that.suggest();
538 } else if (!that.isBadQuery(query)) {
550 } else if (!that.isBadQuery(query)) {
539 if (options.onSearchStart.call(that.element, options.params) === false) {
551 if (options.onSearchStart.call(that.element, params) === false) {
540 return;
552 return;
541 }
553 }
542 if (that.currentRequest) {
554 if (that.currentRequest) {
543 that.currentRequest.abort();
555 that.currentRequest.abort();
544 }
556 }
545
557
546 ajaxSettings = {
558 ajaxSettings = {
547 url: serviceUrl,
559 url: serviceUrl,
548 data: params,
560 data: params,
549 type: options.type,
561 type: options.type,
550 dataType: options.dataType
562 dataType: options.dataType
551 };
563 };
552
564
553 $.extend(ajaxSettings, options.ajaxSettings);
565 $.extend(ajaxSettings, options.ajaxSettings);
554
566
555 that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
567 that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
556 var result;
568 var result;
557 that.currentRequest = null;
569 that.currentRequest = null;
558 result = options.transformResult(data);
570 result = options.transformResult(data);
559 that.processResponse(result, query, cacheKey);
571 that.processResponse(result, query, cacheKey);
560 options.onSearchComplete.call(that.element, query, result.suggestions);
572 options.onSearchComplete.call(that.element, query, result.suggestions);
561 }).fail(function (jqXHR, textStatus, errorThrown) {
573 }).fail(function (jqXHR, textStatus, errorThrown) {
562 options.onSearchError.call(that.element, query, jqXHR, textStatus, errorThrown);
574 options.onSearchError.call(that.element, query, jqXHR, textStatus, errorThrown);
563 });
575 });
564 }
576 }
565 },
577 },
566
578
567 isBadQuery: function (q) {
579 isBadQuery: function (q) {
568 if (!this.options.preventBadQueries){
580 if (!this.options.preventBadQueries){
569 return false;
581 return false;
570 }
582 }
571
583
572 var badQueries = this.badQueries,
584 var badQueries = this.badQueries,
573 i = badQueries.length;
585 i = badQueries.length;
574
586
575 while (i--) {
587 while (i--) {
576 if (q.indexOf(badQueries[i]) === 0) {
588 if (q.indexOf(badQueries[i]) === 0) {
577 return true;
589 return true;
578 }
590 }
579 }
591 }
580
592
581 return false;
593 return false;
582 },
594 },
583
595
584 hide: function () {
596 hide: function () {
585 var that = this;
597 var that = this;
586 that.visible = false;
598 that.visible = false;
587 that.selectedIndex = -1;
599 that.selectedIndex = -1;
588 $(that.suggestionsContainer).hide();
600 $(that.suggestionsContainer).hide();
589 that.signalHint(null);
601 that.signalHint(null);
590 },
602 },
591
603
592 suggest: function () {
604 suggest: function () {
593
605
594 var that = this,
606 var that = this,
595 options = that.options,
607 options = that.options,
596 formatResult = options.formatResult,
608 formatResult = options.formatResult,
597 filterResult = options.lookupFilter,
609 filterResult = options.lookupFilter,
598 value = that.getQuery(that.currentValue),
610 value = that.getQuery(that.currentValue),
599 className = that.classes.suggestion,
611 className = that.classes.suggestion,
600 classSelected = that.classes.selected,
612 classSelected = that.classes.selected,
601 container = $(that.suggestionsContainer),
613 container = $(that.suggestionsContainer),
602 noSuggestionsContainer = $(that.noSuggestionsContainer),
614 noSuggestionsContainer = $(that.noSuggestionsContainer),
603 beforeRender = options.beforeRender,
615 beforeRender = options.beforeRender,
604 limit = parseInt(that.options.lookupLimit, 10),
616 limit = parseInt(that.options.lookupLimit, 10),
605 html = '',
617 html = '',
606 index;
618 index;
607
619
608 // filter and limit given results
620 // filter and limit given results
609 var filtered_suggestions = $.grep(that.suggestions, function (suggestion) {
621 var filtered_suggestions = $.grep(that.suggestions, function (suggestion) {
610 return filterResult(suggestion, value, value.toLowerCase(), that.element);
622 return filterResult(suggestion, value, value.toLowerCase(), that.element);
611 });
623 });
612
624
613 if (limit && filtered_suggestions.length > limit) {
625 if (limit && filtered_suggestions.length > limit) {
614 filtered_suggestions = filtered_suggestions.slice(0, limit);
626 filtered_suggestions = filtered_suggestions.slice(0, limit);
615 }
627 }
616
628
617 if (filtered_suggestions.length === 0) {
629 if (filtered_suggestions.length === 0) {
618 this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();
630 this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();
619 return;
631 return;
620 }
632 }
621
633
622 if (options.triggerSelectOnValidInput) {
634 if (options.triggerSelectOnValidInput) {
623 index = that.findSuggestionIndex(value);
635 index = that.findSuggestionIndex(value);
624 if (index !== -1) {
636 if (index !== -1) {
625 that.select(index);
637 that.select(index);
626 return;
638 return;
627 }
639 }
628 }
640 }
629
641
630 // Build suggestions inner HTML:
642 // Build suggestions inner HTML:
631 $.each(filtered_suggestions, function (i, suggestion) {
643 $.each(filtered_suggestions, function (i, suggestion) {
632 html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, Autocomplete.formatResult, that.element) + '</div>';
644 html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, Autocomplete.formatResult, that.element) + '</div>';
633 });
645 });
634 // set internal suggestion for INDEX pick to work correctly
646 // set internal suggestion for INDEX pick to work correctly
635 that.suggestions = filtered_suggestions;
647 that.suggestions = filtered_suggestions;
636 this.adjustContainerWidth();
648 this.adjustContainerWidth();
637
649
638 noSuggestionsContainer.detach();
650 noSuggestionsContainer.detach();
639 container.html(html);
651 container.html(html);
640
652
641 // Select first value by default:
653 // Select first value by default:
642 if (options.autoSelectFirst) {
654 if (options.autoSelectFirst) {
643 that.selectedIndex = 0;
655 that.selectedIndex = 0;
644 container.children().first().addClass(classSelected);
656 container.children().first().addClass(classSelected);
645 }
657 }
646
658
647 if ($.isFunction(beforeRender)) {
659 if ($.isFunction(beforeRender)) {
648 beforeRender.call(that.element, container);
660 beforeRender.call(that.element, container);
649 }
661 }
650
662
651 that.fixPosition();
663 that.fixPosition();
652
664
653 container.show();
665 container.show();
654 that.visible = true;
666 that.visible = true;
655
667
656 that.findBestHint();
668 that.findBestHint();
657 },
669 },
658
670
659 noSuggestions: function() {
671 noSuggestions: function() {
660 var that = this,
672 var that = this,
661 container = $(that.suggestionsContainer),
673 container = $(that.suggestionsContainer),
662 noSuggestionsContainer = $(that.noSuggestionsContainer);
674 noSuggestionsContainer = $(that.noSuggestionsContainer);
663
675
664 this.adjustContainerWidth();
676 this.adjustContainerWidth();
665
677
666 // Some explicit steps. Be careful here as it easy to get
678 // Some explicit steps. Be careful here as it easy to get
667 // noSuggestionsContainer removed from DOM if not detached properly.
679 // noSuggestionsContainer removed from DOM if not detached properly.
668 noSuggestionsContainer.detach();
680 noSuggestionsContainer.detach();
669 container.empty(); // clean suggestions if any
681 container.empty(); // clean suggestions if any
670 container.append(noSuggestionsContainer);
682 container.append(noSuggestionsContainer);
671
683
672 that.fixPosition();
684 that.fixPosition();
673
685
674 container.show();
686 container.show();
675 that.visible = true;
687 that.visible = true;
676 },
688 },
677
689
678 adjustContainerWidth: function() {
690 adjustContainerWidth: function() {
679 var that = this,
691 var that = this,
680 options = that.options,
692 options = that.options,
681 width,
693 width,
682 container = $(that.suggestionsContainer);
694 container = $(that.suggestionsContainer);
683
695
684 // If width is auto, adjust width before displaying suggestions,
696 // If width is auto, adjust width before displaying suggestions,
685 // because if instance was created before input had width, it will be zero.
697 // because if instance was created before input had width, it will be zero.
686 // Also it adjusts if input width has changed.
698 // Also it adjusts if input width has changed.
687 // -2px to account for suggestions border.
699 // -2px to account for suggestions border.
688 if (options.width === 'auto') {
700 if (options.width === 'auto') {
689 width = that.el.outerWidth() - 2;
701 width = that.el.outerWidth() - 2;
690 container.width(width > 0 ? width : 300);
702 container.width(width > 0 ? width : 300);
691 }
703 }
692 },
704 },
693
705
694 findBestHint: function () {
706 findBestHint: function () {
695 var that = this,
707 var that = this,
696 value = that.el.val().toLowerCase(),
708 value = that.el.val().toLowerCase(),
697 bestMatch = null;
709 bestMatch = null;
698
710
699 if (!value) {
711 if (!value) {
700 return;
712 return;
701 }
713 }
702
714
703 $.each(that.suggestions, function (i, suggestion) {
715 $.each(that.suggestions, function (i, suggestion) {
704 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
716 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
705 if (foundMatch) {
717 if (foundMatch) {
706 bestMatch = suggestion;
718 bestMatch = suggestion;
707 }
719 }
708 return !foundMatch;
720 return !foundMatch;
709 });
721 });
710 that.signalHint(bestMatch);
722 that.signalHint(bestMatch);
711 },
723 },
712
724
713 signalHint: function (suggestion) {
725 signalHint: function (suggestion) {
714 var hintValue = '',
726 var hintValue = '',
715 that = this;
727 that = this;
716 if (suggestion) {
728 if (suggestion) {
717 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
729 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
718 }
730 }
719 if (that.hintValue !== hintValue) {
731 if (that.hintValue !== hintValue) {
720 that.hintValue = hintValue;
732 that.hintValue = hintValue;
721 that.hint = suggestion;
733 that.hint = suggestion;
722 (this.options.onHint || $.noop)(hintValue);
734 (this.options.onHint || $.noop)(hintValue);
723 }
735 }
724 },
736 },
725
737
726 verifySuggestionsFormat: function (suggestions) {
738 verifySuggestionsFormat: function (suggestions) {
727 // If suggestions is string array, convert them to supported format:
739 // If suggestions is string array, convert them to supported format:
728 if (suggestions.length && typeof suggestions[0] === 'string') {
740 if (suggestions.length && typeof suggestions[0] === 'string') {
729 return $.map(suggestions, function (value) {
741 return $.map(suggestions, function (value) {
730 return { value: value, data: null };
742 return { value: value, data: null };
731 });
743 });
732 }
744 }
733
745
734 return suggestions;
746 return suggestions;
735 },
747 },
736
748
737 validateOrientation: function(orientation, fallback) {
749 validateOrientation: function(orientation, fallback) {
738 orientation = $.trim(orientation || '').toLowerCase();
750 orientation = $.trim(orientation || '').toLowerCase();
739
751
740 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
752 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
741 orientation = fallback;
753 orientation = fallback;
742 }
754 }
743
755
744 return orientation;
756 return orientation;
745 },
757 },
746
758
747 processResponse: function (result, originalQuery, cacheKey) {
759 processResponse: function (result, originalQuery, cacheKey) {
748 var that = this,
760 var that = this,
749 options = that.options;
761 options = that.options;
750
762
751 result.suggestions = that.verifySuggestionsFormat(result.suggestions);
763 result.suggestions = that.verifySuggestionsFormat(result.suggestions);
752
764
753 // Cache results if cache is not disabled:
765 // Cache results if cache is not disabled:
754 if (!options.noCache) {
766 if (!options.noCache) {
755 that.cachedResponse[cacheKey] = result;
767 that.cachedResponse[cacheKey] = result;
756 if (options.preventBadQueries && result.suggestions.length === 0) {
768 if (options.preventBadQueries && result.suggestions.length === 0) {
757 that.badQueries.push(originalQuery);
769 that.badQueries.push(originalQuery);
758 }
770 }
759 }
771 }
760
772
761 // Return if originalQuery is not matching current query:
773 // Return if originalQuery is not matching current query:
762 if (originalQuery !== that.getQuery(that.currentValue)) {
774 if (originalQuery !== that.getQuery(that.currentValue)) {
763 return;
775 return;
764 }
776 }
765
777
766 that.suggestions = result.suggestions;
778 that.suggestions = result.suggestions;
767 that.suggest();
779 that.suggest();
768 },
780 },
769
781
770 activate: function (index) {
782 activate: function (index) {
771 var that = this,
783 var that = this,
772 activeItem,
784 activeItem,
773 selected = that.classes.selected,
785 selected = that.classes.selected,
774 container = $(that.suggestionsContainer),
786 container = $(that.suggestionsContainer),
775 children = container.find('.' + that.classes.suggestion);
787 children = container.find('.' + that.classes.suggestion);
776
788
777 container.find('.' + selected).removeClass(selected);
789 container.find('.' + selected).removeClass(selected);
778
790
779 that.selectedIndex = index;
791 that.selectedIndex = index;
780
792
781 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
793 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
782 activeItem = children.get(that.selectedIndex);
794 activeItem = children.get(that.selectedIndex);
783 $(activeItem).addClass(selected);
795 $(activeItem).addClass(selected);
784 return activeItem;
796 return activeItem;
785 }
797 }
786
798
787 return null;
799 return null;
788 },
800 },
789
801
790 selectHint: function () {
802 selectHint: function () {
791 var that = this,
803 var that = this,
792 i = $.inArray(that.hint, that.suggestions);
804 i = $.inArray(that.hint, that.suggestions);
793 that.select(i);
805 that.select(i);
794 },
806 },
795
807
796 select: function (index) {
808 select: function (index) {
797 var that = this;
809 var that = this;
798 that.hide();
810 that.hide();
799 that.onSelect(index);
811 that.onSelect(index);
800 },
812 },
801
813
802 moveUp: function () {
814 moveUp: function () {
803 var that = this;
815 var that = this;
804
816
805 if (that.selectedIndex === -1) {
817 if (that.selectedIndex === -1) {
806 return;
818 return;
807 }
819 }
808
820
809 if (that.selectedIndex === 0) {
821 if (that.selectedIndex === 0) {
810 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
822 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
811 that.selectedIndex = -1;
823 that.selectedIndex = -1;
812 that.el.val(that.currentValue);
824 that.el.val(that.currentValue);
813 that.findBestHint();
825 that.findBestHint();
814 return;
826 return;
815 }
827 }
816
828
817 that.adjustScroll(that.selectedIndex - 1);
829 that.adjustScroll(that.selectedIndex - 1);
818 },
830 },
819
831
820 moveDown: function () {
832 moveDown: function () {
821 var that = this;
833 var that = this;
822
834
823 if (that.selectedIndex === (that.suggestions.length - 1)) {
835 if (that.selectedIndex === (that.suggestions.length - 1)) {
824 return;
836 return;
825 }
837 }
826
838
827 that.adjustScroll(that.selectedIndex + 1);
839 that.adjustScroll(that.selectedIndex + 1);
828 },
840 },
829
841
830 adjustScroll: function (index) {
842 adjustScroll: function (index) {
831 var that = this,
843 var that = this,
832 activeItem = that.activate(index),
844 activeItem = that.activate(index),
833 offsetTop,
845 offsetTop,
834 upperBound,
846 upperBound,
835 lowerBound,
847 lowerBound,
836 heightDelta = 25;
848 heightDelta = 25;
837
849
838 if (!activeItem) {
850 if (!activeItem) {
839 return;
851 return;
840 }
852 }
841
853
842 offsetTop = activeItem.offsetTop;
854 offsetTop = activeItem.offsetTop;
843 upperBound = $(that.suggestionsContainer).scrollTop();
855 upperBound = $(that.suggestionsContainer).scrollTop();
844 lowerBound = upperBound + that.options.maxHeight - heightDelta;
856 lowerBound = upperBound + that.options.maxHeight - heightDelta;
845
857
846 if (offsetTop < upperBound) {
858 if (offsetTop < upperBound) {
847 $(that.suggestionsContainer).scrollTop(offsetTop);
859 $(that.suggestionsContainer).scrollTop(offsetTop);
848 } else if (offsetTop > lowerBound) {
860 } else if (offsetTop > lowerBound) {
849 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
861 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
850 }
862 }
851
863
852 if (that.options.replaceOnArrowKey) {
864 if (that.options.replaceOnArrowKey) {
853 that.el.val(that.getValue(that.suggestions[index].value));
865 that.el.val(that.getValue(that.suggestions[index].value));
854 }
866 }
855 that.signalHint(null);
867 that.signalHint(null);
856 },
868 },
857
869
858 onSelect: function (index) {
870 onSelect: function (index) {
859 var that = this,
871 var that = this,
860 onSelectCallback = that.options.onSelect,
872 onSelectCallback = that.options.onSelect,
861 suggestion = that.suggestions[index];
873 suggestion = that.suggestions[index];
862
874
863 that.currentValue = that.getValue(suggestion.value);
875 that.currentValue = that.getValue(suggestion.value);
864 var prevElem = {'value': that.el.val(),
876 var prevElem = {'value': that.el.val(),
865 'caret': that.element.selectionStart}
877 'caret': that.element.selectionStart}
866
878
867 if (that.currentValue !== that.el.val()) {
879 if (that.currentValue !== that.el.val()) {
868 that.el.val(that.currentValue);
880 that.el.val(that.currentValue);
869 }
881 }
870
882
871 that.signalHint(null);
883 that.signalHint(null);
872 that.suggestions = [];
884 that.suggestions = [];
873 that.selection = suggestion;
885 that.selection = suggestion;
874
886
875 if ($.isFunction(onSelectCallback)) {
887 if ($.isFunction(onSelectCallback)) {
876 onSelectCallback.call(this, that.element, suggestion, prevElem);
888 onSelectCallback.call(this, that.element, suggestion, prevElem);
877 }
889 }
878 },
890 },
879
891
880 getValue: function (value) {
892 getValue: function (value) {
881 var that = this,
893 var that = this,
882 delimiter = that.options.delimiter,
894 delimiter = that.options.delimiter,
883 currentValue,
895 currentValue,
884 parts;
896 parts;
885
897
886 if (!delimiter) {
898 if (!delimiter) {
887 return value;
899 return value;
888 }
900 }
889
901
890 currentValue = that.currentValue;
902 currentValue = that.currentValue;
891 parts = currentValue.split(delimiter);
903 parts = currentValue.split(delimiter);
892
904
893 if (parts.length === 1) {
905 if (parts.length === 1) {
894 return value;
906 return value;
895 }
907 }
896
908
897 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
909 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
898 },
910 },
899
911
900 dispose: function () {
912 dispose: function () {
901 var that = this;
913 var that = this;
902 that.el.off('.autocomplete').removeData('autocomplete');
914 that.el.off('.autocomplete').removeData('autocomplete');
903 that.disableKillerFn();
915 that.disableKillerFn();
904 $(window).off('resize.autocomplete', that.fixPositionCapture);
916 $(window).off('resize.autocomplete', that.fixPositionCapture);
905 $(that.suggestionsContainer).remove();
917 $(that.suggestionsContainer).remove();
906 }
918 }
907 };
919 };
908
920
909 // Create chainable jQuery plugin:
921 // Create chainable jQuery plugin:
910 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
922 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
911 var dataKey = 'autocomplete';
923 var dataKey = 'autocomplete';
912 // If function invoked without argument return
924 // If function invoked without argument return
913 // instance of the first matched element:
925 // instance of the first matched element:
914 if (arguments.length === 0) {
926 if (arguments.length === 0) {
915 return this.first().data(dataKey);
927 return this.first().data(dataKey);
916 }
928 }
917
929
918 return this.each(function () {
930 return this.each(function () {
919 var inputElement = $(this),
931 var inputElement = $(this),
920 instance = inputElement.data(dataKey);
932 instance = inputElement.data(dataKey);
921
933
922 if (typeof options === 'string') {
934 if (typeof options === 'string') {
923 if (instance && typeof instance[options] === 'function') {
935 if (instance && typeof instance[options] === 'function') {
924 instance[options](args);
936 instance[options](args);
925 }
937 }
926 } else {
938 } else {
927 // If instance already exists, destroy it:
939 // If instance already exists, destroy it:
928 if (instance && instance.dispose) {
940 if (instance && instance.dispose) {
929 instance.dispose();
941 instance.dispose();
930 }
942 }
931 instance = new Autocomplete(this, options);
943 instance = new Autocomplete(this, options);
932 inputElement.data(dataKey, instance);
944 inputElement.data(dataKey, instance);
933 }
945 }
934 });
946 });
935 };
947 };
936 }));
948 }));
@@ -1,832 +1,910 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 % if c.rhodecode_name:
14 % if c.rhodecode_name:
15 <div class="branding">
15 <div class="branding">
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
16 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
17 </div>
17 </div>
18 % endif
18 % endif
19 </div>
19 </div>
20 <!-- MENU BAR NAV -->
20 <!-- MENU BAR NAV -->
21 ${self.menu_bar_nav()}
21 ${self.menu_bar_nav()}
22 <!-- END MENU BAR NAV -->
22 <!-- END MENU BAR NAV -->
23 </div>
23 </div>
24 </div>
24 </div>
25 ${self.menu_bar_subnav()}
25 ${self.menu_bar_subnav()}
26 <!-- END HEADER -->
26 <!-- END HEADER -->
27
27
28 <!-- CONTENT -->
28 <!-- CONTENT -->
29 <div id="content" class="wrapper">
29 <div id="content" class="wrapper">
30
30
31 <rhodecode-toast id="notifications"></rhodecode-toast>
31 <rhodecode-toast id="notifications"></rhodecode-toast>
32
32
33 <div class="main">
33 <div class="main">
34 ${next.main()}
34 ${next.main()}
35 </div>
35 </div>
36 </div>
36 </div>
37 <!-- END CONTENT -->
37 <!-- END CONTENT -->
38
38
39 </div>
39 </div>
40 <!-- FOOTER -->
40 <!-- FOOTER -->
41 <div id="footer">
41 <div id="footer">
42 <div id="footer-inner" class="title wrapper">
42 <div id="footer-inner" class="title wrapper">
43 <div>
43 <div>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 % if c.visual.show_version:
45 % if c.visual.show_version:
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
46 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
47 % endif
47 % endif
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
48 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
49 % if c.visual.rhodecode_support_url:
49 % if c.visual.rhodecode_support_url:
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
50 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
51 % endif
51 % endif
52 </p>
52 </p>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
53 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
54 <p class="server-instance" style="display:${sid}">
54 <p class="server-instance" style="display:${sid}">
55 ## display hidden instance ID if specially defined
55 ## display hidden instance ID if specially defined
56 % if c.rhodecode_instanceid:
56 % if c.rhodecode_instanceid:
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
57 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
58 % endif
58 % endif
59 </p>
59 </p>
60 </div>
60 </div>
61 </div>
61 </div>
62 </div>
62 </div>
63
63
64 <!-- END FOOTER -->
64 <!-- END FOOTER -->
65
65
66 ### MAKO DEFS ###
66 ### MAKO DEFS ###
67
67
68 <%def name="menu_bar_subnav()">
68 <%def name="menu_bar_subnav()">
69 </%def>
69 </%def>
70
70
71 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <%def name="breadcrumbs(class_='breadcrumbs')">
72 <div class="${class_}">
72 <div class="${class_}">
73 ${self.breadcrumbs_links()}
73 ${self.breadcrumbs_links()}
74 </div>
74 </div>
75 </%def>
75 </%def>
76
76
77 <%def name="admin_menu()">
77 <%def name="admin_menu()">
78 <ul class="admin_menu submenu">
78 <ul class="admin_menu submenu">
79 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
79 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
80 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
80 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
81 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
81 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
82 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
82 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
83 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
83 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
84 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
84 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
85 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
86 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
86 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
87 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
87 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
88 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
88 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
89 </ul>
89 </ul>
90 </%def>
90 </%def>
91
91
92
92
93 <%def name="dt_info_panel(elements)">
93 <%def name="dt_info_panel(elements)">
94 <dl class="dl-horizontal">
94 <dl class="dl-horizontal">
95 %for dt, dd, title, show_items in elements:
95 %for dt, dd, title, show_items in elements:
96 <dt>${dt}:</dt>
96 <dt>${dt}:</dt>
97 <dd title="${h.tooltip(title)}">
97 <dd title="${h.tooltip(title)}">
98 %if callable(dd):
98 %if callable(dd):
99 ## allow lazy evaluation of elements
99 ## allow lazy evaluation of elements
100 ${dd()}
100 ${dd()}
101 %else:
101 %else:
102 ${dd}
102 ${dd}
103 %endif
103 %endif
104 %if show_items:
104 %if show_items:
105 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
105 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
106 %endif
106 %endif
107 </dd>
107 </dd>
108
108
109 %if show_items:
109 %if show_items:
110 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
110 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
111 %for item in show_items:
111 %for item in show_items:
112 <dt></dt>
112 <dt></dt>
113 <dd>${item}</dd>
113 <dd>${item}</dd>
114 %endfor
114 %endfor
115 </div>
115 </div>
116 %endif
116 %endif
117
117
118 %endfor
118 %endfor
119 </dl>
119 </dl>
120 </%def>
120 </%def>
121
121
122
122
123 <%def name="gravatar(email, size=16)">
123 <%def name="gravatar(email, size=16)">
124 <%
124 <%
125 if (size > 16):
125 if (size > 16):
126 gravatar_class = 'gravatar gravatar-large'
126 gravatar_class = 'gravatar gravatar-large'
127 else:
127 else:
128 gravatar_class = 'gravatar'
128 gravatar_class = 'gravatar'
129 %>
129 %>
130 <%doc>
130 <%doc>
131 TODO: johbo: For now we serve double size images to make it smooth
131 TODO: johbo: For now we serve double size images to make it smooth
132 for retina. This is how it worked until now. Should be replaced
132 for retina. This is how it worked until now. Should be replaced
133 with a better solution at some point.
133 with a better solution at some point.
134 </%doc>
134 </%doc>
135 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
135 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
136 </%def>
136 </%def>
137
137
138
138
139 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
139 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
140 <% email = h.email_or_none(contact) %>
140 <% email = h.email_or_none(contact) %>
141 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
141 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
142 ${self.gravatar(email, size)}
142 ${self.gravatar(email, size)}
143 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
143 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
144 </div>
144 </div>
145 </%def>
145 </%def>
146
146
147
147
148 ## admin menu used for people that have some admin resources
148 ## admin menu used for people that have some admin resources
149 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
149 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
150 <ul class="submenu">
150 <ul class="submenu">
151 %if repositories:
151 %if repositories:
152 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
152 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
153 %endif
153 %endif
154 %if repository_groups:
154 %if repository_groups:
155 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
155 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
156 %endif
156 %endif
157 %if user_groups:
157 %if user_groups:
158 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
158 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
159 %endif
159 %endif
160 </ul>
160 </ul>
161 </%def>
161 </%def>
162
162
163 <%def name="repo_page_title(repo_instance)">
163 <%def name="repo_page_title(repo_instance)">
164 <div class="title-content">
164 <div class="title-content">
165 <div class="title-main">
165 <div class="title-main">
166 ## SVN/HG/GIT icons
166 ## SVN/HG/GIT icons
167 %if h.is_hg(repo_instance):
167 %if h.is_hg(repo_instance):
168 <i class="icon-hg"></i>
168 <i class="icon-hg"></i>
169 %endif
169 %endif
170 %if h.is_git(repo_instance):
170 %if h.is_git(repo_instance):
171 <i class="icon-git"></i>
171 <i class="icon-git"></i>
172 %endif
172 %endif
173 %if h.is_svn(repo_instance):
173 %if h.is_svn(repo_instance):
174 <i class="icon-svn"></i>
174 <i class="icon-svn"></i>
175 %endif
175 %endif
176
176
177 ## public/private
177 ## public/private
178 %if repo_instance.private:
178 %if repo_instance.private:
179 <i class="icon-repo-private"></i>
179 <i class="icon-repo-private"></i>
180 %else:
180 %else:
181 <i class="icon-repo-public"></i>
181 <i class="icon-repo-public"></i>
182 %endif
182 %endif
183
183
184 ## repo name with group name
184 ## repo name with group name
185 ${h.breadcrumb_repo_link(repo_instance)}
185 ${h.breadcrumb_repo_link(repo_instance)}
186
186
187 </div>
187 </div>
188
188
189 ## FORKED
189 ## FORKED
190 %if repo_instance.fork:
190 %if repo_instance.fork:
191 <p>
191 <p>
192 <i class="icon-code-fork"></i> ${_('Fork of')}
192 <i class="icon-code-fork"></i> ${_('Fork of')}
193 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
193 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
194 </p>
194 </p>
195 %endif
195 %endif
196
196
197 ## IMPORTED FROM REMOTE
197 ## IMPORTED FROM REMOTE
198 %if repo_instance.clone_uri:
198 %if repo_instance.clone_uri:
199 <p>
199 <p>
200 <i class="icon-code-fork"></i> ${_('Clone from')}
200 <i class="icon-code-fork"></i> ${_('Clone from')}
201 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
201 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
202 </p>
202 </p>
203 %endif
203 %endif
204
204
205 ## LOCKING STATUS
205 ## LOCKING STATUS
206 %if repo_instance.locked[0]:
206 %if repo_instance.locked[0]:
207 <p class="locking_locked">
207 <p class="locking_locked">
208 <i class="icon-repo-lock"></i>
208 <i class="icon-repo-lock"></i>
209 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
209 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
210 </p>
210 </p>
211 %elif repo_instance.enable_locking:
211 %elif repo_instance.enable_locking:
212 <p class="locking_unlocked">
212 <p class="locking_unlocked">
213 <i class="icon-repo-unlock"></i>
213 <i class="icon-repo-unlock"></i>
214 ${_('Repository not locked. Pull repository to lock it.')}
214 ${_('Repository not locked. Pull repository to lock it.')}
215 </p>
215 </p>
216 %endif
216 %endif
217
217
218 </div>
218 </div>
219 </%def>
219 </%def>
220
220
221 <%def name="repo_menu(active=None)">
221 <%def name="repo_menu(active=None)">
222 <%
222 <%
223 def is_active(selected):
223 def is_active(selected):
224 if selected == active:
224 if selected == active:
225 return "active"
225 return "active"
226 %>
226 %>
227
227
228 <!--- CONTEXT BAR -->
228 <!--- CONTEXT BAR -->
229 <div id="context-bar">
229 <div id="context-bar">
230 <div class="wrapper">
230 <div class="wrapper">
231 <ul id="context-pages" class="navigation horizontal-list">
231 <ul id="context-pages" class="navigation horizontal-list">
232 <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>
232 <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>
233 <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>
233 <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>
234 <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>
234 <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>
235 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
235 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
236 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Search')}</div></a></li>
237
236
238 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
237 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
239 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
238 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
240 <li class="${is_active('showpullrequest')}">
239 <li class="${is_active('showpullrequest')}">
241 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
240 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
242 %if c.repository_pull_requests:
241 %if c.repository_pull_requests:
243 <span class="pr_notifications">${c.repository_pull_requests}</span>
242 <span class="pr_notifications">${c.repository_pull_requests}</span>
244 %endif
243 %endif
245 <div class="menulabel">${_('Pull Requests')}</div>
244 <div class="menulabel">${_('Pull Requests')}</div>
246 </a>
245 </a>
247 </li>
246 </li>
248 %endif
247 %endif
249
248
250 <li class="${is_active('options')}">
249 <li class="${is_active('options')}">
251 <a class="menulink dropdown">
250 <a class="menulink dropdown">
252 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
251 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
253 </a>
252 </a>
254 <ul class="submenu">
253 <ul class="submenu">
255 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
254 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
256 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
255 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
257 %endif
256 %endif
258 %if c.rhodecode_db_repo.fork:
257 %if c.rhodecode_db_repo.fork:
259 <li>
258 <li>
260 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
259 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
261 href="${h.route_path('repo_compare',
260 href="${h.route_path('repo_compare',
262 repo_name=c.rhodecode_db_repo.fork.repo_name,
261 repo_name=c.rhodecode_db_repo.fork.repo_name,
263 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
262 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
264 source_ref=c.rhodecode_db_repo.landing_rev[1],
263 source_ref=c.rhodecode_db_repo.landing_rev[1],
265 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
264 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
266 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
265 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
267 _query=dict(merge=1))}"
266 _query=dict(merge=1))}"
268 >
267 >
269 ${_('Compare fork')}
268 ${_('Compare fork')}
270 </a>
269 </a>
271 </li>
270 </li>
272 %endif
271 %endif
273
272
274 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
273 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
275 %if c.rhodecode_db_repo.locked[0]:
274 %if c.rhodecode_db_repo.locked[0]:
276 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
275 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
277 %else:
276 %else:
278 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
277 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
279 %endif
278 %endif
280 %endif
279 %endif
281 %if c.rhodecode_user.username != h.DEFAULT_USER:
280 %if c.rhodecode_user.username != h.DEFAULT_USER:
282 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
281 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
283 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
282 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
284 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
283 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
285 %endif
284 %endif
286 %endif
285 %endif
287 </ul>
286 </ul>
288 </li>
287 </li>
289 </ul>
288 </ul>
290 </div>
289 </div>
291 <div class="clear"></div>
290 <div class="clear"></div>
292 </div>
291 </div>
293 % if c.rhodecode_db_repo.archived:
292 % if c.rhodecode_db_repo.archived:
294 <div class="alert alert-warning text-center">
293 <div class="alert alert-warning text-center">
295 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
294 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
296 </div>
295 </div>
297 % endif
296 % endif
298 <!--- END CONTEXT BAR -->
297 <!--- END CONTEXT BAR -->
299
298
300 </%def>
299 </%def>
301
300
302 <%def name="repo_group_page_title(repo_group_instance)">
301 <%def name="repo_group_page_title(repo_group_instance)">
303 <div class="title-content">
302 <div class="title-content">
304 <div class="title-main">
303 <div class="title-main">
305 ## Repository Group icon
304 ## Repository Group icon
306 <i class="icon-folder-close"></i>
305 <i class="icon-folder-close"></i>
307
306
308 ## repo name with group name
307 ## repo name with group name
309 ${h.breadcrumb_repo_group_link(repo_group_instance)}
308 ${h.breadcrumb_repo_group_link(repo_group_instance)}
310 </div>
309 </div>
311
310
312 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
311 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
313 <div class="repo-group-desc">
312 <div class="repo-group-desc">
314 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
313 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
315 </div>
314 </div>
316
315
317 </div>
316 </div>
318 </%def>
317 </%def>
319
318
320 <%def name="repo_group_menu(active=None)">
319 <%def name="repo_group_menu(active=None)">
321 <%
320 <%
322 def is_active(selected):
321 def is_active(selected):
323 if selected == active:
322 if selected == active:
324 return "active"
323 return "active"
325
324
326 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
325 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
327
326
328 gr_name = c.repo_group.group_name if c.repo_group else None
327 gr_name = c.repo_group.group_name if c.repo_group else None
329 # create repositories with write permission on group is set to true
328 # create repositories with write permission on group is set to true
330 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
329 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
331 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
330 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
332 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
331 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
333
332
334 %>
333 %>
335
334
336 <!--- CONTEXT BAR -->
335 <!--- CONTEXT BAR -->
337 <div id="context-bar">
336 <div id="context-bar">
338 <div class="wrapper">
337 <div class="wrapper">
339 <ul id="context-pages" class="navigation horizontal-list">
338 <ul id="context-pages" class="navigation horizontal-list">
340 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
339 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
341 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo_group', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Search')}</div></a></li>
342
340
343 <li class="${is_active('options')}">
341 <li class="${is_active('options')}">
344 <a class="menulink dropdown">
342 <a class="menulink dropdown">
345 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
343 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
346 </a>
344 </a>
347 <ul class="submenu">
345 <ul class="submenu">
348 %if is_admin or group_admin:
346 %if is_admin or group_admin:
349 <li><a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}">${_('Group Settings')}</a></li>
347 <li><a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}">${_('Group Settings')}</a></li>
350 %endif
348 %endif
351 %if is_admin or group_admin or (group_write and create_on_write):
349 %if is_admin or group_admin or (group_write and create_on_write):
352 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
350 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
353 %endif
351 %endif
354 %if is_admin or group_admin:
352 %if is_admin or group_admin:
355 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
353 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
356 %endif
354 %endif
357 </ul>
355 </ul>
358 </li>
356 </li>
359 </ul>
357 </ul>
360 </div>
358 </div>
361 <div class="clear"></div>
359 <div class="clear"></div>
362 </div>
360 </div>
363
361
364 <!--- END CONTEXT BAR -->
362 <!--- END CONTEXT BAR -->
365
363
366 </%def>
364 </%def>
367
365
368
366
369 <%def name="usermenu(active=False)">
367 <%def name="usermenu(active=False)">
370 ## USER MENU
368 ## USER MENU
371 <li id="quick_login_li" class="${'active' if active else ''}">
369 <li id="quick_login_li" class="${'active' if active else ''}">
372 % if c.rhodecode_user.username == h.DEFAULT_USER:
370 % if c.rhodecode_user.username == h.DEFAULT_USER:
373 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
371 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
374 ${gravatar(c.rhodecode_user.email, 20)}
372 ${gravatar(c.rhodecode_user.email, 20)}
375 <span class="user">
373 <span class="user">
376 <span>${_('Sign in')}</span>
374 <span>${_('Sign in')}</span>
377 </span>
375 </span>
378 </a>
376 </a>
379 % else:
377 % else:
380 ## logged in user
378 ## logged in user
381 <a id="quick_login_link" class="menulink childs">
379 <a id="quick_login_link" class="menulink childs">
382 ${gravatar(c.rhodecode_user.email, 20)}
380 ${gravatar(c.rhodecode_user.email, 20)}
383 <span class="user">
381 <span class="user">
384 <span class="menu_link_user">${c.rhodecode_user.username}</span>
382 <span class="menu_link_user">${c.rhodecode_user.username}</span>
385 <div class="show_more"></div>
383 <div class="show_more"></div>
386 </span>
384 </span>
387 </a>
385 </a>
388 ## subnav with menu for logged in user
386 ## subnav with menu for logged in user
389 <div class="user-menu submenu">
387 <div class="user-menu submenu">
390 <div id="quick_login">
388 <div id="quick_login">
391 %if c.rhodecode_user.username != h.DEFAULT_USER:
389 %if c.rhodecode_user.username != h.DEFAULT_USER:
392 <div class="">
390 <div class="">
393 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
391 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
394 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
392 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
395 <div class="email">${c.rhodecode_user.email}</div>
393 <div class="email">${c.rhodecode_user.email}</div>
396 </div>
394 </div>
397 <div class="">
395 <div class="">
398 <ol class="links">
396 <ol class="links">
399 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
397 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
400 % if c.rhodecode_user.personal_repo_group:
398 % if c.rhodecode_user.personal_repo_group:
401 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
399 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
402 % endif
400 % endif
403 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
401 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
404 ## bookmark-items
402 ## bookmark-items
405 <li class="bookmark-items">
403 <li class="bookmark-items">
406 ${_('Bookmarks')}
404 ${_('Bookmarks')}
407 <div class="pull-right">
405 <div class="pull-right">
408 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
406 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
409 </div>
407 </div>
410 </li>
408 </li>
411 % if not c.bookmark_items:
409 % if not c.bookmark_items:
412 <li>
410 <li>
413 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
411 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
414 </li>
412 </li>
415 % endif
413 % endif
416 % for item in c.bookmark_items:
414 % for item in c.bookmark_items:
417 <li>
415 <li>
418 % if item.repository:
416 % if item.repository:
419 <div>
417 <div>
420 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
418 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
421 <code>${item.position}</code>
419 <code>${item.position}</code>
422 % if item.repository.repo_type == 'hg':
420 % if item.repository.repo_type == 'hg':
423 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
421 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
424 % elif item.repository.repo_type == 'git':
422 % elif item.repository.repo_type == 'git':
425 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
423 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
426 % elif item.repository.repo_type == 'svn':
424 % elif item.repository.repo_type == 'svn':
427 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
425 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
428 % endif
426 % endif
429 ${(item.title or h.shorter(item.repository.repo_name, 30))}
427 ${(item.title or h.shorter(item.repository.repo_name, 30))}
430 </a>
428 </a>
431 </div>
429 </div>
432 % elif item.repository_group:
430 % elif item.repository_group:
433 <div>
431 <div>
434 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
432 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
435 <code>${item.position}</code>
433 <code>${item.position}</code>
436 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
434 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
437 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
435 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
438 </a>
436 </a>
439 </div>
437 </div>
440 % else:
438 % else:
441 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
439 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
442 <code>${item.position}</code>
440 <code>${item.position}</code>
443 ${item.title}
441 ${item.title}
444 </a>
442 </a>
445 % endif
443 % endif
446 </li>
444 </li>
447 % endfor
445 % endfor
448
446
449 <li class="logout">
447 <li class="logout">
450 ${h.secure_form(h.route_path('logout'), request=request)}
448 ${h.secure_form(h.route_path('logout'), request=request)}
451 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
449 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
452 ${h.end_form()}
450 ${h.end_form()}
453 </li>
451 </li>
454 </ol>
452 </ol>
455 </div>
453 </div>
456 %endif
454 %endif
457 </div>
455 </div>
458 </div>
456 </div>
459 ## unread counter
457 ## unread counter
460 <div class="pill_container">
458 <div class="pill_container">
461 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
459 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
462 </div>
460 </div>
463 % endif
461 % endif
464 </li>
462 </li>
465 </%def>
463 </%def>
466
464
467 <%def name="menu_items(active=None)">
465 <%def name="menu_items(active=None)">
468 <%
466 <%
469 def is_active(selected):
467 def is_active(selected):
470 if selected == active:
468 if selected == active:
471 return "active"
469 return "active"
472 return ""
470 return ""
473 %>
471 %>
474
472
475 <ul id="quick" class="main_nav navigation horizontal-list">
473 <ul id="quick" class="main_nav navigation horizontal-list">
476 ## notice box for important system messages
474 ## notice box for important system messages
477 <li style="display: none">
475 <li style="display: none">
478 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
476 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
479 <div class="menulabel-notice" >
477 <div class="menulabel-notice" >
480 0
478 0
481 </div>
479 </div>
482 </a>
480 </a>
483 </li>
481 </li>
484
482
485 ## Main filter
483 ## Main filter
486 <li>
484 <li>
487 <div class="menulabel main_filter_box">
485 <div class="menulabel main_filter_box">
488 <div class="main_filter_input_box">
486 <div class="main_filter_input_box">
489 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
487 <ul class="searchItems">
490 </div>
488
491 <div class="main_filter_help_box">
489 % if c.template_context['search_context']['repo_id']:
492 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
490 <li class="searchTag searchTagFilter searchTagHidable" >
491 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
492 <span class="tag">
493 This repo
494 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
495 </span>
496 ##</a>
497 </li>
498 % elif c.template_context['search_context']['repo_group_id']:
499 <li class="searchTag searchTagFilter searchTagHidable">
500 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
501 <span class="tag">
502 This group
503 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-delete"></i></a>
504 </span>
505 ##</a>
506 </li>
507 % endif
508
509 <li class="searchTagInput">
510 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
511 </li>
512 <li class="searchTag searchTagHelp">
513 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
514 </li>
515 </ul>
493 </div>
516 </div>
494 </div>
517 </div>
495
518
496 <div id="main_filter_help" style="display: none">
519 <div id="main_filter_help" style="display: none">
497 - Use '/' key to quickly access this field.
520 - Use '/' key to quickly access this field.
498
521
499 - Enter a name of repository, or repository group for quick search.
522 - Enter a name of repository, or repository group for quick search.
500
523
501 - Prefix query to allow special search:
524 - Prefix query to allow special search:
502
525
503 user:admin, to search for usernames
526 user:admin, to search for usernames, always global
527
528 user_group:devops, to search for user groups, always global
504
529
505 user_group:devops, to search for user groups
530 commit:efced4, to search for commits, scoped to repositories or groups
531
532 file:models.py, to search for file paths, scoped to repositories or groups
506
533
507 commit:efced4, to search for commits
534 % if c.template_context['search_context']['repo_id']:
508
535 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
509 file:models.py, to search for file paths
536 % elif c.template_context['search_context']['repo_group_id']:
537 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
538 % else:
539 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
540 % endif
510 </div>
541 </div>
511 </li>
542 </li>
512
543
513 ## ROOT MENU
544 ## ROOT MENU
514 <li class="${is_active('home')}">
545 <li class="${is_active('home')}">
515 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
546 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
516 <div class="menulabel">${_('Home')}</div>
547 <div class="menulabel">${_('Home')}</div>
517 </a>
548 </a>
518 </li>
549 </li>
519
550
520 %if c.rhodecode_user.username != h.DEFAULT_USER:
551 %if c.rhodecode_user.username != h.DEFAULT_USER:
521 <li class="${is_active('journal')}">
552 <li class="${is_active('journal')}">
522 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
553 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
523 <div class="menulabel">${_('Journal')}</div>
554 <div class="menulabel">${_('Journal')}</div>
524 </a>
555 </a>
525 </li>
556 </li>
526 %else:
557 %else:
527 <li class="${is_active('journal')}">
558 <li class="${is_active('journal')}">
528 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
559 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
529 <div class="menulabel">${_('Public journal')}</div>
560 <div class="menulabel">${_('Public journal')}</div>
530 </a>
561 </a>
531 </li>
562 </li>
532 %endif
563 %endif
533
564
534 <li class="${is_active('gists')}">
565 <li class="${is_active('gists')}">
535 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
566 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
536 <div class="menulabel">${_('Gists')}</div>
567 <div class="menulabel">${_('Gists')}</div>
537 </a>
568 </a>
538 </li>
569 </li>
539
570
540 % if h.HasPermissionAll('hg.admin')('access admin main page'):
571 % if h.HasPermissionAll('hg.admin')('access admin main page'):
541 <li class="${is_active('admin')}">
572 <li class="${is_active('admin')}">
542 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
573 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
543 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
574 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
544 </a>
575 </a>
545 ${admin_menu()}
576 ${admin_menu()}
546 </li>
577 </li>
547 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
578 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
548 <li class="${is_active('admin')}">
579 <li class="${is_active('admin')}">
549 <a class="menulink childs" title="${_('Delegated Admin settings')}">
580 <a class="menulink childs" title="${_('Delegated Admin settings')}">
550 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
581 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
551 </a>
582 </a>
552 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
583 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
553 c.rhodecode_user.repository_groups_admin,
584 c.rhodecode_user.repository_groups_admin,
554 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
585 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
555 </li>
586 </li>
556 % endif
587 % endif
557 ## render extra user menu
588 ## render extra user menu
558 ${usermenu(active=(active=='my_account'))}
589 ${usermenu(active=(active=='my_account'))}
559
590
560 % if c.debug_style:
591 % if c.debug_style:
561 <li>
592 <li>
562 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
593 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
563 <div class="menulabel">${_('[Style]')}</div>
594 <div class="menulabel">${_('[Style]')}</div>
564 </a>
595 </a>
565 </li>
596 </li>
566 % endif
597 % endif
567 </ul>
598 </ul>
568
599
569 <script type="text/javascript">
600 <script type="text/javascript">
570 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
601 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
571
602
572 var formatRepoResult = function(result, container, query, escapeMarkup) {
603 var formatRepoResult = function(result, container, query, escapeMarkup) {
573 return function(data, escapeMarkup) {
604 return function(data, escapeMarkup) {
574 if (!data.repo_id){
605 if (!data.repo_id){
575 return data.text; // optgroup text Repositories
606 return data.text; // optgroup text Repositories
576 }
607 }
577
608
578 var tmpl = '';
609 var tmpl = '';
579 var repoType = data['repo_type'];
610 var repoType = data['repo_type'];
580 var repoName = data['text'];
611 var repoName = data['text'];
581
612
582 if(data && data.type == 'repo'){
613 if(data && data.type == 'repo'){
583 if(repoType === 'hg'){
614 if(repoType === 'hg'){
584 tmpl += '<i class="icon-hg"></i> ';
615 tmpl += '<i class="icon-hg"></i> ';
585 }
616 }
586 else if(repoType === 'git'){
617 else if(repoType === 'git'){
587 tmpl += '<i class="icon-git"></i> ';
618 tmpl += '<i class="icon-git"></i> ';
588 }
619 }
589 else if(repoType === 'svn'){
620 else if(repoType === 'svn'){
590 tmpl += '<i class="icon-svn"></i> ';
621 tmpl += '<i class="icon-svn"></i> ';
591 }
622 }
592 if(data['private']){
623 if(data['private']){
593 tmpl += '<i class="icon-lock" ></i> ';
624 tmpl += '<i class="icon-lock" ></i> ';
594 }
625 }
595 else if(visualShowPublicIcon){
626 else if(visualShowPublicIcon){
596 tmpl += '<i class="icon-unlock-alt"></i> ';
627 tmpl += '<i class="icon-unlock-alt"></i> ';
597 }
628 }
598 }
629 }
599 tmpl += escapeMarkup(repoName);
630 tmpl += escapeMarkup(repoName);
600 return tmpl;
631 return tmpl;
601
632
602 }(result, escapeMarkup);
633 }(result, escapeMarkup);
603 };
634 };
604
635
605 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
636 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
606 return function(data, escapeMarkup) {
637 return function(data, escapeMarkup) {
607 if (!data.repo_group_id){
638 if (!data.repo_group_id){
608 return data.text; // optgroup text Repositories
639 return data.text; // optgroup text Repositories
609 }
640 }
610
641
611 var tmpl = '';
642 var tmpl = '';
612 var repoGroupName = data['text'];
643 var repoGroupName = data['text'];
613
644
614 if(data){
645 if(data){
615
646
616 tmpl += '<i class="icon-folder-close"></i> ';
647 tmpl += '<i class="icon-folder-close"></i> ';
617
648
618 }
649 }
619 tmpl += escapeMarkup(repoGroupName);
650 tmpl += escapeMarkup(repoGroupName);
620 return tmpl;
651 return tmpl;
621
652
622 }(result, escapeMarkup);
653 }(result, escapeMarkup);
623 };
654 };
624
655
656 var escapeRegExChars = function (value) {
657 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
658 };
659
660 var getRepoIcon = function(repo_type) {
661 if (repo_type === 'hg') {
662 return '<i class="icon-hg"></i> ';
663 }
664 else if (repo_type === 'git') {
665 return '<i class="icon-git"></i> ';
666 }
667 else if (repo_type === 'svn') {
668 return '<i class="icon-svn"></i> ';
669 }
670 return ''
671 };
625
672
626 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
673 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
627
674
628 if (value.split(':').length === 2) {
675 if (value.split(':').length === 2) {
629 value = value.split(':')[1]
676 value = value.split(':')[1]
630 }
677 }
631
678
632 var searchType = data['type'];
679 var searchType = data['type'];
633 var valueDisplay = data['value_display'];
680 var valueDisplay = data['value_display'];
634
681
635 var escapeRegExChars = function (value) {
636 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
637 };
638 var pattern = '(' + escapeRegExChars(value) + ')';
682 var pattern = '(' + escapeRegExChars(value) + ')';
639
683
640 var getRepoIcon = function(repo_type) {
684 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
641 if (repo_type === 'hg') {
642 return '<i class="icon-hg"></i> ';
643 }
644 else if (repo_type === 'git') {
645 return '<i class="icon-git"></i> ';
646 }
647 else if (repo_type === 'svn') {
648 return '<i class="icon-svn"></i> ';
649 }
650 return ''
651 };
652
685
653 // highlight match
686 // highlight match
654 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
687 if (searchType != 'text') {
655 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
688 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
689 }
656
690
657 var icon = '';
691 var icon = '';
658
692
659 if (searchType === 'hint') {
693 if (searchType === 'hint') {
660 icon += '<i class="icon-folder-close"></i> ';
694 icon += '<i class="icon-folder-close"></i> ';
661 }
695 }
662 // full text search
696 // full text search
663 else if (searchType === 'search') {
697 else if (searchType === 'search') {
664 icon += '<i class="icon-more"></i> ';
698 icon += '<i class="icon-more"></i> ';
665 }
699 }
666 // repository
700 // repository
667 else if (searchType === 'repo') {
701 else if (searchType === 'repo') {
668
702
669 var repoIcon = getRepoIcon(data['repo_type']);
703 var repoIcon = getRepoIcon(data['repo_type']);
670 icon += repoIcon;
704 icon += repoIcon;
671
705
672 if (data['private']) {
706 if (data['private']) {
673 icon += '<i class="icon-lock" ></i> ';
707 icon += '<i class="icon-lock" ></i> ';
674 }
708 }
675 else if (visualShowPublicIcon) {
709 else if (visualShowPublicIcon) {
676 icon += '<i class="icon-unlock-alt"></i> ';
710 icon += '<i class="icon-unlock-alt"></i> ';
677 }
711 }
678 }
712 }
679 // repository groups
713 // repository groups
680 else if (searchType === 'repo_group') {
714 else if (searchType === 'repo_group') {
681 icon += '<i class="icon-folder-close"></i> ';
715 icon += '<i class="icon-folder-close"></i> ';
682 }
716 }
683 // user group
717 // user group
684 else if (searchType === 'user_group') {
718 else if (searchType === 'user_group') {
685 icon += '<i class="icon-group"></i> ';
719 icon += '<i class="icon-group"></i> ';
686 }
720 }
721 // user
687 else if (searchType === 'user') {
722 else if (searchType === 'user') {
688 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
723 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
689 }
724 }
690 // commit
725 // commit
691 else if (searchType === 'commit') {
726 else if (searchType === 'commit') {
692 var repo_data = data['repo_data'];
727 var repo_data = data['repo_data'];
693 var repoIcon = getRepoIcon(repo_data['repository_type']);
728 var repoIcon = getRepoIcon(repo_data['repository_type']);
694 if (repoIcon) {
729 if (repoIcon) {
695 icon += repoIcon;
730 icon += repoIcon;
696 } else {
731 } else {
697 icon += '<i class="icon-tag"></i>';
732 icon += '<i class="icon-tag"></i>';
698 }
733 }
699 }
734 }
700 // file
735 // file
701 else if (searchType === 'file') {
736 else if (searchType === 'file') {
702 var repo_data = data['repo_data'];
737 var repo_data = data['repo_data'];
703 var repoIcon = getRepoIcon(repo_data['repository_type']);
738 var repoIcon = getRepoIcon(repo_data['repository_type']);
704 if (repoIcon) {
739 if (repoIcon) {
705 icon += repoIcon;
740 icon += repoIcon;
706 } else {
741 } else {
707 icon += '<i class="icon-tag"></i>';
742 icon += '<i class="icon-tag"></i>';
708 }
743 }
709 }
744 }
745 // generic text
746 else if (searchType === 'text') {
747 icon = '';
748 }
710
749
711 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
750 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
712 return tmpl.format(icon, valueDisplay);
751 return tmpl.format(icon, valueDisplay);
713 };
752 };
714
753
715 var handleSelect = function(element, suggestion) {
754 var handleSelect = function(element, suggestion) {
716 if (suggestion.type === "hint") {
755 if (suggestion.type === "hint") {
717 // we skip action
756 // we skip action
718 $('#main_filter').focus();
757 $('#main_filter').focus();
758 }
759 else if (suggestion.type === "text") {
760 // we skip action
761 $('#main_filter').focus();
762
719 } else {
763 } else {
720 window.location = suggestion['url'];
764 window.location = suggestion['url'];
721 }
765 }
722 };
766 };
767
723 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
768 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
724 if (queryLowerCase.split(':').length === 2) {
769 if (queryLowerCase.split(':').length === 2) {
725 queryLowerCase = queryLowerCase.split(':')[1]
770 queryLowerCase = queryLowerCase.split(':')[1]
726 }
771 }
772 if (suggestion.type === "text") {
773 // special case we don't want to "skip" display for
774 return true
775 }
727 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
776 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
728 };
777 };
729
778
779 var cleanContext = {
780 repo_view_type: null,
781
782 repo_id: null,
783 repo_name: "",
784
785 repo_group_id: null,
786 repo_group_name: null
787 };
788 var removeGoToFilter = function () {
789 $('.searchTagHidable').hide();
790 $('#main_filter').autocomplete(
791 'setOptions', {params:{search_context: cleanContext}});
792 };
793
730 $('#main_filter').autocomplete({
794 $('#main_filter').autocomplete({
731 serviceUrl: pyroutes.url('goto_switcher_data'),
795 serviceUrl: pyroutes.url('goto_switcher_data'),
732 params: {"search_context": templateContext.search_context},
796 params: {
797 "search_context": templateContext.search_context
798 },
733 minChars:2,
799 minChars:2,
734 maxHeight:400,
800 maxHeight:400,
735 deferRequestBy: 300, //miliseconds
801 deferRequestBy: 300, //miliseconds
736 tabDisabled: true,
802 tabDisabled: true,
737 autoSelectFirst: true,
803 autoSelectFirst: false,
738 formatResult: autocompleteMainFilterFormatResult,
804 formatResult: autocompleteMainFilterFormatResult,
739 lookupFilter: autocompleteMainFilterResult,
805 lookupFilter: autocompleteMainFilterResult,
740 onSelect: function (element, suggestion) {
806 onSelect: function (element, suggestion) {
741 handleSelect(element, suggestion);
807 handleSelect(element, suggestion);
742 return false;
808 return false;
743 },
809 },
744 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
810 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
745 if (jqXHR !== 'abort') {
811 if (jqXHR !== 'abort') {
746 alert("Error during search.\nError code: {0}".format(textStatus));
812 alert("Error during search.\nError code: {0}".format(textStatus));
747 window.location = '';
813 window.location = '';
748 }
814 }
749 }
815 }
750 });
816 });
751
817
752 showMainFilterBox = function () {
818 showMainFilterBox = function () {
753 $('#main_filter_help').toggle();
819 $('#main_filter_help').toggle();
754 };
820 };
755
821
822 $('#main_filter').on('keydown.autocomplete', function (e) {
823
824 var BACKSPACE = 8;
825 var el = $(e.currentTarget);
826 if(e.which === BACKSPACE){
827 var inputVal = el.val();
828 if (inputVal === ""){
829 removeGoToFilter()
830 }
831 }
832 });
833
756 </script>
834 </script>
757 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
835 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
758 </%def>
836 </%def>
759
837
760 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
838 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
761 <div class="modal-dialog">
839 <div class="modal-dialog">
762 <div class="modal-content">
840 <div class="modal-content">
763 <div class="modal-header">
841 <div class="modal-header">
764 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
842 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
765 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
843 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
766 </div>
844 </div>
767 <div class="modal-body">
845 <div class="modal-body">
768 <div class="block-left">
846 <div class="block-left">
769 <table class="keyboard-mappings">
847 <table class="keyboard-mappings">
770 <tbody>
848 <tbody>
771 <tr>
849 <tr>
772 <th></th>
850 <th></th>
773 <th>${_('Site-wide shortcuts')}</th>
851 <th>${_('Site-wide shortcuts')}</th>
774 </tr>
852 </tr>
775 <%
853 <%
776 elems = [
854 elems = [
777 ('/', 'Use quick search box'),
855 ('/', 'Use quick search box'),
778 ('g h', 'Goto home page'),
856 ('g h', 'Goto home page'),
779 ('g g', 'Goto my private gists page'),
857 ('g g', 'Goto my private gists page'),
780 ('g G', 'Goto my public gists page'),
858 ('g G', 'Goto my public gists page'),
781 ('g 0-9', 'Goto bookmarked items from 0-9'),
859 ('g 0-9', 'Goto bookmarked items from 0-9'),
782 ('n r', 'New repository page'),
860 ('n r', 'New repository page'),
783 ('n g', 'New gist page'),
861 ('n g', 'New gist page'),
784 ]
862 ]
785 %>
863 %>
786 %for key, desc in elems:
864 %for key, desc in elems:
787 <tr>
865 <tr>
788 <td class="keys">
866 <td class="keys">
789 <span class="key tag">${key}</span>
867 <span class="key tag">${key}</span>
790 </td>
868 </td>
791 <td>${desc}</td>
869 <td>${desc}</td>
792 </tr>
870 </tr>
793 %endfor
871 %endfor
794 </tbody>
872 </tbody>
795 </table>
873 </table>
796 </div>
874 </div>
797 <div class="block-left">
875 <div class="block-left">
798 <table class="keyboard-mappings">
876 <table class="keyboard-mappings">
799 <tbody>
877 <tbody>
800 <tr>
878 <tr>
801 <th></th>
879 <th></th>
802 <th>${_('Repositories')}</th>
880 <th>${_('Repositories')}</th>
803 </tr>
881 </tr>
804 <%
882 <%
805 elems = [
883 elems = [
806 ('g s', 'Goto summary page'),
884 ('g s', 'Goto summary page'),
807 ('g c', 'Goto changelog page'),
885 ('g c', 'Goto changelog page'),
808 ('g f', 'Goto files page'),
886 ('g f', 'Goto files page'),
809 ('g F', 'Goto files page with file search activated'),
887 ('g F', 'Goto files page with file search activated'),
810 ('g p', 'Goto pull requests page'),
888 ('g p', 'Goto pull requests page'),
811 ('g o', 'Goto repository settings'),
889 ('g o', 'Goto repository settings'),
812 ('g O', 'Goto repository permissions settings'),
890 ('g O', 'Goto repository permissions settings'),
813 ]
891 ]
814 %>
892 %>
815 %for key, desc in elems:
893 %for key, desc in elems:
816 <tr>
894 <tr>
817 <td class="keys">
895 <td class="keys">
818 <span class="key tag">${key}</span>
896 <span class="key tag">${key}</span>
819 </td>
897 </td>
820 <td>${desc}</td>
898 <td>${desc}</td>
821 </tr>
899 </tr>
822 %endfor
900 %endfor
823 </tbody>
901 </tbody>
824 </table>
902 </table>
825 </div>
903 </div>
826 </div>
904 </div>
827 <div class="modal-footer">
905 <div class="modal-footer">
828 </div>
906 </div>
829 </div><!-- /.modal-content -->
907 </div><!-- /.modal-content -->
830 </div><!-- /.modal-dialog -->
908 </div><!-- /.modal-dialog -->
831 </div><!-- /.modal -->
909 </div><!-- /.modal -->
832
910
@@ -1,158 +1,159 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 go_import_header = ''
6 go_import_header = ''
7 if hasattr(c, 'rhodecode_db_repo'):
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
10 ## check repo context
10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
12
12
13 if getattr(c, 'repo_group', None):
13 if getattr(c, 'repo_group', None):
14 c.template_context['repo_group_id'] = c.repo_group.group_id
14 c.template_context['repo_group_id'] = c.repo_group.group_id
15 c.template_context['repo_group_name'] = c.repo_group.group_name
15 c.template_context['repo_group_name'] = c.repo_group.group_name
16
16
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
18 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
19 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
20 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
21 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
21 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
22 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
22 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
23
23
24 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
24 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
25 c.template_context['default_user'] = {
25 c.template_context['default_user'] = {
26 'username': h.DEFAULT_USER,
26 'username': h.DEFAULT_USER,
27 'user_id': 1
27 'user_id': 1
28 }
28 }
29 c.template_context['search_context'] = {
29 c.template_context['search_context'] = {
30 'repo_group_id': c.template_context.get('repo_group_id'),
30 'repo_group_id': c.template_context.get('repo_group_id'),
31 'repo_group_name': c.template_context.get('repo_group_name'),
31 'repo_group_name': c.template_context.get('repo_group_name'),
32 'repo_id': c.template_context.get('repo_id'),
32 'repo_name': c.template_context.get('repo_name'),
33 'repo_name': c.template_context.get('repo_name'),
33 'repo_view_type': c.template_context.get('repo_view_type'),
34 'repo_view_type': c.template_context.get('repo_view_type'),
34 }
35 }
35
36
36 %>
37 %>
37 <html xmlns="http://www.w3.org/1999/xhtml">
38 <html xmlns="http://www.w3.org/1999/xhtml">
38 <head>
39 <head>
39 <title>${self.title()}</title>
40 <title>${self.title()}</title>
40 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
41 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
41
42
42 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
43 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
43
44
44 % if 'safari' in (request.user_agent or '').lower():
45 % if 'safari' in (request.user_agent or '').lower():
45 <meta name="referrer" content="origin">
46 <meta name="referrer" content="origin">
46 % else:
47 % else:
47 <meta name="referrer" content="origin-when-cross-origin">
48 <meta name="referrer" content="origin-when-cross-origin">
48 % endif
49 % endif
49
50
50 <%def name="robots()">
51 <%def name="robots()">
51 <meta name="robots" content="index, nofollow"/>
52 <meta name="robots" content="index, nofollow"/>
52 </%def>
53 </%def>
53 ${self.robots()}
54 ${self.robots()}
54 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
55 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
55 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
56 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
56 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
57 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
57
58
58 ## CSS definitions
59 ## CSS definitions
59 <%def name="css()">
60 <%def name="css()">
60 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
61 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
61 ## EXTRA FOR CSS
62 ## EXTRA FOR CSS
62 ${self.css_extra()}
63 ${self.css_extra()}
63 </%def>
64 </%def>
64 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
65 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
65 <%def name="css_extra()">
66 <%def name="css_extra()">
66 </%def>
67 </%def>
67
68
68 ${self.css()}
69 ${self.css()}
69
70
70 ## JAVASCRIPT
71 ## JAVASCRIPT
71 <%def name="js()">
72 <%def name="js()">
72
73
73 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
74 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
74 <script type="text/javascript">
75 <script type="text/javascript">
75 // register templateContext to pass template variables to JS
76 // register templateContext to pass template variables to JS
76 var templateContext = ${h.json.dumps(c.template_context)|n};
77 var templateContext = ${h.json.dumps(c.template_context)|n};
77
78
78 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
79 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
79 var APPLICATION_PLUGINS = [];
80 var APPLICATION_PLUGINS = [];
80 var ASSET_URL = "${h.asset('')}";
81 var ASSET_URL = "${h.asset('')}";
81 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
82 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
82 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
83 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
83
84
84 var APPENLIGHT = {
85 var APPENLIGHT = {
85 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
86 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
86 key: '${getattr(c, "appenlight_api_public_key", "")}',
87 key: '${getattr(c, "appenlight_api_public_key", "")}',
87 % if getattr(c, 'appenlight_server_url', None):
88 % if getattr(c, 'appenlight_server_url', None):
88 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
89 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
89 % endif
90 % endif
90 requestInfo: {
91 requestInfo: {
91 % if getattr(c, 'rhodecode_user', None):
92 % if getattr(c, 'rhodecode_user', None):
92 ip: '${c.rhodecode_user.ip_addr}',
93 ip: '${c.rhodecode_user.ip_addr}',
93 username: '${c.rhodecode_user.username}'
94 username: '${c.rhodecode_user.username}'
94 % endif
95 % endif
95 },
96 },
96 tags: {
97 tags: {
97 rhodecode_version: '${c.rhodecode_version}',
98 rhodecode_version: '${c.rhodecode_version}',
98 rhodecode_edition: '${c.rhodecode_edition}'
99 rhodecode_edition: '${c.rhodecode_edition}'
99 }
100 }
100 };
101 };
101
102
102 </script>
103 </script>
103 <%include file="/base/plugins_base.mako"/>
104 <%include file="/base/plugins_base.mako"/>
104 <!--[if lt IE 9]>
105 <!--[if lt IE 9]>
105 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
106 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
106 <![endif]-->
107 <![endif]-->
107 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
108 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
108 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
109 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
109 ## avoide escaping the %N
110 ## avoide escaping the %N
110 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
111 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
111 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
112 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
112
113
113
114
114 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
115 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
115 ${self.js_extra()}
116 ${self.js_extra()}
116
117
117 <script type="text/javascript">
118 <script type="text/javascript">
118 Rhodecode = (function() {
119 Rhodecode = (function() {
119 function _Rhodecode() {
120 function _Rhodecode() {
120 this.comments = new CommentsController();
121 this.comments = new CommentsController();
121 }
122 }
122 return new _Rhodecode();
123 return new _Rhodecode();
123 })();
124 })();
124
125
125 $(document).ready(function(){
126 $(document).ready(function(){
126 show_more_event();
127 show_more_event();
127 timeagoActivate();
128 timeagoActivate();
128 clipboardActivate();
129 clipboardActivate();
129 })
130 })
130 </script>
131 </script>
131
132
132 </%def>
133 </%def>
133
134
134 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
135 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
135 <%def name="js_extra()"></%def>
136 <%def name="js_extra()"></%def>
136 ${self.js()}
137 ${self.js()}
137
138
138 <%def name="head_extra()"></%def>
139 <%def name="head_extra()"></%def>
139 ${self.head_extra()}
140 ${self.head_extra()}
140 ## extra stuff
141 ## extra stuff
141 %if c.pre_code:
142 %if c.pre_code:
142 ${c.pre_code|n}
143 ${c.pre_code|n}
143 %endif
144 %endif
144 </head>
145 </head>
145 <body id="body">
146 <body id="body">
146 <noscript>
147 <noscript>
147 <div class="noscript-error">
148 <div class="noscript-error">
148 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
149 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
149 </div>
150 </div>
150 </noscript>
151 </noscript>
151
152
152 ${next.body()}
153 ${next.body()}
153 %if c.post_code:
154 %if c.post_code:
154 ${c.post_code|n}
155 ${c.post_code|n}
155 %endif
156 %endif
156 <rhodecode-app></rhodecode-app>
157 <rhodecode-app></rhodecode-app>
157 </body>
158 </body>
158 </html>
159 </html>
@@ -1,222 +1,222 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 %if c.repo_name:
5 %if c.repo_name:
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
7 %elif c.repo_group_name:
7 %elif c.repo_group_name:
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
9 %else:
9 %else:
10 ${_('Search inside all accessible repositories')}
10 ${_('Search inside all accessible repositories')}
11 %endif
11 %endif
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="breadcrumbs_links()">
17 <%def name="breadcrumbs_links()">
18 %if c.repo_name:
18 %if c.repo_name:
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
20 %elif c.repo_group_name:
20 %elif c.repo_group_name:
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
22 %else:
22 %else:
23 ${_('Search inside all accessible repositories')}
23 ${_('Search inside all accessible repositories')}
24 %endif
24 %endif
25
25
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_nav()">
28 <%def name="menu_bar_nav()">
29 %if c.repo_name:
29 %if c.repo_name:
30 ${self.menu_items(active='search')}
30 ${self.menu_items(active='search')}
31 %elif c.repo_group_name:
31 %elif c.repo_group_name:
32 ${self.menu_items(active='search')}
32 ${self.menu_items(active='search')}
33 %else:
33 %else:
34 ${self.menu_items(active='search')}
34 ${self.menu_items(active='search')}
35 %endif
35 %endif
36 </%def>
36 </%def>
37
37
38 <%def name="menu_bar_subnav()">
38 <%def name="menu_bar_subnav()">
39 %if c.repo_name:
39 %if c.repo_name:
40 ${self.repo_menu(active='search')}
40 ${self.repo_menu(active='summary')}
41 %elif c.repo_group_name:
41 %elif c.repo_group_name:
42 ${self.repo_group_menu(active='search')}
42 ${self.repo_group_menu(active='home')}
43 %endif
43 %endif
44 </%def>
44 </%def>
45
45
46 <%def name="repo_icon(db_repo)">
46 <%def name="repo_icon(db_repo)">
47 %if h.is_hg(db_repo):
47 %if h.is_hg(db_repo):
48 <i class="icon-hg"></i>
48 <i class="icon-hg"></i>
49 %endif
49 %endif
50 %if h.is_git(db_repo):
50 %if h.is_git(db_repo):
51 <i class="icon-git"></i>
51 <i class="icon-git"></i>
52 %endif
52 %endif
53 %if h.is_svn(db_repo):
53 %if h.is_svn(db_repo):
54 <i class="icon-svn"></i>
54 <i class="icon-svn"></i>
55 %endif
55 %endif
56 </%def>
56 </%def>
57
57
58 <%def name="repo_group_icon()">
58 <%def name="repo_group_icon()">
59 <i class="icon-folder-close"></i>
59 <i class="icon-folder-close"></i>
60 </%def>
60 </%def>
61
61
62 <%def name="main()">
62 <%def name="main()">
63 <div class="box">
63 <div class="box">
64 %if c.repo_name:
64 %if c.repo_name:
65 <!-- box / title -->
65 <!-- box / title -->
66 <div class="title">
66 <div class="title">
67 ${self.repo_page_title(c.rhodecode_db_repo)}
67 ${self.repo_page_title(c.rhodecode_db_repo)}
68 </div>
68 </div>
69 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
69 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
70 %elif c.repo_group_name:
70 %elif c.repo_group_name:
71 <!-- box / title -->
71 <!-- box / title -->
72 <div class="title">
72 <div class="title">
73 ${self.repo_group_page_title(c.repo_group)}
73 ${self.repo_group_page_title(c.repo_group)}
74 </div>
74 </div>
75 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
75 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
76 %else:
76 %else:
77 <!-- box / title -->
77 <!-- box / title -->
78 <div class="title">
78 <div class="title">
79 ${self.breadcrumbs()}
79 ${self.breadcrumbs()}
80 <ul class="links">&nbsp;</ul>
80 <ul class="links">&nbsp;</ul>
81 </div>
81 </div>
82 <!-- end box / title -->
82 <!-- end box / title -->
83 ${h.form(h.route_path('search'), method='get')}
83 ${h.form(h.route_path('search'), method='get')}
84 %endif
84 %endif
85 <div class="form search-form">
85 <div class="form search-form">
86 <div class="fields">
86 <div class="fields">
87
87
88 ${h.text('q', c.cur_query, placeholder="Enter query...")}
88 ${h.text('q', c.cur_query, placeholder="Enter query...")}
89
89
90 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
90 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
91 ${h.hidden('max_lines', '10')}
91 ${h.hidden('max_lines', '10')}
92
92
93 <input type="submit" value="${_('Search')}" class="btn"/>
93 <input type="submit" value="${_('Search')}" class="btn"/>
94 <br/>
94 <br/>
95
95
96 <div class="search-tags">
96 <div class="search-tags">
97 <span class="tag tag8">
97 <span class="tag tag8">
98 %if c.repo_name:
98 %if c.repo_name:
99 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
99 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
100 %elif c.repo_group_name:
100 %elif c.repo_group_name:
101 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
101 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
102 % else:
102 % else:
103 ${_('Global Search')}
103 ${_('Global Search')}
104 %endif
104 %endif
105 </span>
105 </span>
106
106
107 %if c.repo_name:
107 %if c.repo_name:
108 Β»
108 Β»
109 <span class="tag tag8">
109 <span class="tag tag8">
110 ${repo_icon(c.rhodecode_db_repo)}
110 ${repo_icon(c.rhodecode_db_repo)}
111 ${c.repo_name}
111 ${c.repo_name}
112 </span>
112 </span>
113
113
114 %elif c.repo_group_name:
114 %elif c.repo_group_name:
115 Β»
115 Β»
116 <span class="tag tag8">
116 <span class="tag tag8">
117 ${repo_group_icon()}
117 ${repo_group_icon()}
118 ${c.repo_group_name}
118 ${c.repo_group_name}
119 </span>
119 </span>
120 %endif
120 %endif
121
121
122
122
123 % for search_tag in c.search_tags:
123 % for search_tag in c.search_tags:
124 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
124 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
125 % endfor
125 % endfor
126
126
127 </div>
127 </div>
128
128
129 <div class="search-feedback-items">
129 <div class="search-feedback-items">
130 % for error in c.errors:
130 % for error in c.errors:
131 <span class="error-message">
131 <span class="error-message">
132 % for k,v in error.asdict().items():
132 % for k,v in error.asdict().items():
133 ${k} - ${v}
133 ${k} - ${v}
134 % endfor
134 % endfor
135 </span>
135 </span>
136 % endfor
136 % endfor
137 <div class="field">
137 <div class="field">
138 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
138 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
139 <pre id="search-help" style="display: none">\
139 <pre id="search-help" style="display: none">\
140
140
141 % if c.searcher.name == 'whoosh':
141 % if c.searcher.name == 'whoosh':
142 Example filter terms for `Whoosh` search:
142 Example filter terms for `Whoosh` search:
143 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
143 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
144 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
144 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
145
145
146 Generate wildcards using '*' character:
146 Generate wildcards using '*' character:
147 "repo_name:vcs*" - search everything starting with 'vcs'
147 "repo_name:vcs*" - search everything starting with 'vcs'
148 "repo_name:*vcs*" - search for repository containing 'vcs'
148 "repo_name:*vcs*" - search for repository containing 'vcs'
149
149
150 Optional AND / OR operators in queries
150 Optional AND / OR operators in queries
151 "repo_name:vcs OR repo_name:test"
151 "repo_name:vcs OR repo_name:test"
152 "owner:test AND repo_name:test*" AND extension:py
152 "owner:test AND repo_name:test*" AND extension:py
153
153
154 Move advanced search is available via ElasticSearch6 backend in EE edition.
154 Move advanced search is available via ElasticSearch6 backend in EE edition.
155 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
155 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
156 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
156 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
157 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
157 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
158
158
159 search type: content (File Content)
159 search type: content (File Content)
160 indexed fields: content
160 indexed fields: content
161
161
162 # search for `fix` string in all files
162 # search for `fix` string in all files
163 fix
163 fix
164
164
165 search type: commit (Commit message)
165 search type: commit (Commit message)
166 indexed fields: message
166 indexed fields: message
167
167
168 search type: path (File name)
168 search type: path (File name)
169 indexed fields: path
169 indexed fields: path
170
170
171 % else:
171 % else:
172 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
172 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
173 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
173 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
174 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
174 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
175 % for handler in c.searcher.get_handlers().values():
175 % for handler in c.searcher.get_handlers().values():
176
176
177 search type: ${handler.search_type_label}
177 search type: ${handler.search_type_label}
178 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
178 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
179 % for entry in handler.es_6_example_queries:
179 % for entry in handler.es_6_example_queries:
180 ${entry.rstrip()}
180 ${entry.rstrip()}
181 % endfor
181 % endfor
182 % endfor
182 % endfor
183
183
184 % endif
184 % endif
185 </pre>
185 </pre>
186 </div>
186 </div>
187
187
188 <div class="field">${c.runtime}</div>
188 <div class="field">${c.runtime}</div>
189 </div>
189 </div>
190 </div>
190 </div>
191 </div>
191 </div>
192
192
193 ${h.end_form()}
193 ${h.end_form()}
194 <div class="search">
194 <div class="search">
195 % if c.search_type == 'content':
195 % if c.search_type == 'content':
196 <%include file='search_content.mako'/>
196 <%include file='search_content.mako'/>
197 % elif c.search_type == 'path':
197 % elif c.search_type == 'path':
198 <%include file='search_path.mako'/>
198 <%include file='search_path.mako'/>
199 % elif c.search_type == 'commit':
199 % elif c.search_type == 'commit':
200 <%include file='search_commit.mako'/>
200 <%include file='search_commit.mako'/>
201 % elif c.search_type == 'repository':
201 % elif c.search_type == 'repository':
202 <%include file='search_repository.mako'/>
202 <%include file='search_repository.mako'/>
203 % endif
203 % endif
204 </div>
204 </div>
205 </div>
205 </div>
206 <script>
206 <script>
207 $(document).ready(function(){
207 $(document).ready(function(){
208 $("#id_search_type").select2({
208 $("#id_search_type").select2({
209 'containerCssClass': "drop-menu",
209 'containerCssClass': "drop-menu",
210 'dropdownCssClass': "drop-menu-dropdown",
210 'dropdownCssClass': "drop-menu-dropdown",
211 'dropdownAutoWidth': true,
211 'dropdownAutoWidth': true,
212 'minimumResultsForSearch': -1
212 'minimumResultsForSearch': -1
213 });
213 });
214
214
215 $('#q').autoGrowInput({maxWidth: 920});
215 $('#q').autoGrowInput({maxWidth: 920});
216
216
217 setTimeout(function() {
217 setTimeout(function() {
218 $('#q').keyup()
218 $('#q').keyup()
219 }, 1);
219 }, 1);
220 })
220 })
221 </script>
221 </script>
222 </%def>
222 </%def>
General Comments 0
You need to be logged in to leave comments. Login now