##// END OF EJS Templates
controllers: remove old auth_token checks - it was only partial CSRF protection
Mads Kiilerich -
r4990:959a9fa7 default
parent child Browse files
Show More
@@ -1,681 +1,674 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.admin.repos
15 kallithea.controllers.admin.repos
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Repositories controller for Kallithea
18 Repositories controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 7, 2010
22 :created_on: Apr 7, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import formencode
30 import formencode
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound
32 from webob.exc import HTTPInternalServerError, HTTPForbidden, HTTPNotFound
33 from pylons import request, tmpl_context as c, url
33 from pylons import request, tmpl_context as c, url
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36 from sqlalchemy.sql.expression import func
36 from sqlalchemy.sql.expression import func
37
37
38 from kallithea.lib import helpers as h
38 from kallithea.lib import helpers as h
39 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasRepoPermissionAllDecorator, NotAnonymous,HasPermissionAny, \
40 HasRepoPermissionAllDecorator, NotAnonymous,HasPermissionAny, \
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator
42 from kallithea.lib.base import BaseRepoController, render
42 from kallithea.lib.base import BaseRepoController, render
43 from kallithea.lib.utils import action_logger, repo_name_slug, jsonify
43 from kallithea.lib.utils import action_logger, repo_name_slug, jsonify
44 from kallithea.lib.helpers import get_token
45 from kallithea.lib.vcs import RepositoryError
44 from kallithea.lib.vcs import RepositoryError
46 from kallithea.model.meta import Session
45 from kallithea.model.meta import Session
47 from kallithea.model.db import User, Repository, UserFollowing, RepoGroup,\
46 from kallithea.model.db import User, Repository, UserFollowing, RepoGroup,\
48 Setting, RepositoryField
47 Setting, RepositoryField
49 from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
48 from kallithea.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
50 from kallithea.model.scm import ScmModel, RepoGroupList, RepoList
49 from kallithea.model.scm import ScmModel, RepoGroupList, RepoList
51 from kallithea.model.repo import RepoModel
50 from kallithea.model.repo import RepoModel
52 from kallithea.lib.compat import json
51 from kallithea.lib.compat import json
53 from kallithea.lib.exceptions import AttachedForksError
52 from kallithea.lib.exceptions import AttachedForksError
54 from kallithea.lib.utils2 import safe_int
53 from kallithea.lib.utils2 import safe_int
55
54
56 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
57
56
58
57
59 class ReposController(BaseRepoController):
58 class ReposController(BaseRepoController):
60 """
59 """
61 REST Controller styled on the Atom Publishing Protocol"""
60 REST Controller styled on the Atom Publishing Protocol"""
62 # To properly map this controller, ensure your config/routing.py
61 # To properly map this controller, ensure your config/routing.py
63 # file has a resource setup:
62 # file has a resource setup:
64 # map.resource('repo', 'repos')
63 # map.resource('repo', 'repos')
65
64
66 @LoginRequired()
65 @LoginRequired()
67 def __before__(self):
66 def __before__(self):
68 super(ReposController, self).__before__()
67 super(ReposController, self).__before__()
69
68
70 def _load_repo(self, repo_name):
69 def _load_repo(self, repo_name):
71 repo_obj = Repository.get_by_repo_name(repo_name)
70 repo_obj = Repository.get_by_repo_name(repo_name)
72
71
73 if repo_obj is None:
72 if repo_obj is None:
74 h.not_mapped_error(repo_name)
73 h.not_mapped_error(repo_name)
75 return redirect(url('repos'))
74 return redirect(url('repos'))
76
75
77 return repo_obj
76 return repo_obj
78
77
79 def __load_defaults(self, repo=None):
78 def __load_defaults(self, repo=None):
80 acl_groups = RepoGroupList(RepoGroup.query().all(),
79 acl_groups = RepoGroupList(RepoGroup.query().all(),
81 perm_set=['group.write', 'group.admin'])
80 perm_set=['group.write', 'group.admin'])
82 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
81 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
83 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
84
83
85 # in case someone no longer have a group.write access to a repository
84 # in case someone no longer have a group.write access to a repository
86 # pre fill the list with this entry, we don't care if this is the same
85 # pre fill the list with this entry, we don't care if this is the same
87 # but it will allow saving repo data properly.
86 # but it will allow saving repo data properly.
88
87
89 repo_group = None
88 repo_group = None
90 if repo:
89 if repo:
91 repo_group = repo.group
90 repo_group = repo.group
92 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
91 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
93 c.repo_groups_choices.append(unicode(repo_group.group_id))
92 c.repo_groups_choices.append(unicode(repo_group.group_id))
94 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
93 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
95
94
96 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
95 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
97 c.landing_revs_choices = choices
96 c.landing_revs_choices = choices
98
97
99 def __load_data(self, repo_name=None):
98 def __load_data(self, repo_name=None):
100 """
99 """
101 Load defaults settings for edit, and update
100 Load defaults settings for edit, and update
102
101
103 :param repo_name:
102 :param repo_name:
104 """
103 """
105 c.repo_info = self._load_repo(repo_name)
104 c.repo_info = self._load_repo(repo_name)
106 self.__load_defaults(c.repo_info)
105 self.__load_defaults(c.repo_info)
107
106
108 ##override defaults for exact repo info here git/hg etc
107 ##override defaults for exact repo info here git/hg etc
109 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
108 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
110 c.landing_revs_choices = choices
109 c.landing_revs_choices = choices
111 defaults = RepoModel()._get_defaults(repo_name)
110 defaults = RepoModel()._get_defaults(repo_name)
112
111
113 return defaults
112 return defaults
114
113
115 def index(self, format='html'):
114 def index(self, format='html'):
116 """GET /repos: All items in the collection"""
115 """GET /repos: All items in the collection"""
117 # url('repos')
116 # url('repos')
118 _list = Repository.query()\
117 _list = Repository.query()\
119 .order_by(func.lower(Repository.repo_name))\
118 .order_by(func.lower(Repository.repo_name))\
120 .all()
119 .all()
121
120
122 c.repos_list = RepoList(_list, perm_set=['repository.admin'])
121 c.repos_list = RepoList(_list, perm_set=['repository.admin'])
123 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
122 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
124 admin=True,
123 admin=True,
125 super_user_actions=True)
124 super_user_actions=True)
126 #json used to render the grid
125 #json used to render the grid
127 c.data = json.dumps(repos_data)
126 c.data = json.dumps(repos_data)
128
127
129 return render('admin/repos/repos.html')
128 return render('admin/repos/repos.html')
130
129
131 @NotAnonymous()
130 @NotAnonymous()
132 def create(self):
131 def create(self):
133 """
132 """
134 POST /repos: Create a new item"""
133 POST /repos: Create a new item"""
135 # url('repos')
134 # url('repos')
136
135
137 self.__load_defaults()
136 self.__load_defaults()
138 form_result = {}
137 form_result = {}
139 task_id = None
138 task_id = None
140 try:
139 try:
141 # CanWriteToGroup validators checks permissions of this POST
140 # CanWriteToGroup validators checks permissions of this POST
142 form_result = RepoForm(repo_groups=c.repo_groups_choices,
141 form_result = RepoForm(repo_groups=c.repo_groups_choices,
143 landing_revs=c.landing_revs_choices)()\
142 landing_revs=c.landing_revs_choices)()\
144 .to_python(dict(request.POST))
143 .to_python(dict(request.POST))
145
144
146 # create is done sometimes async on celery, db transaction
145 # create is done sometimes async on celery, db transaction
147 # management is handled there.
146 # management is handled there.
148 task = RepoModel().create(form_result, self.authuser.user_id)
147 task = RepoModel().create(form_result, self.authuser.user_id)
149 from celery.result import BaseAsyncResult
148 from celery.result import BaseAsyncResult
150 if isinstance(task, BaseAsyncResult):
149 if isinstance(task, BaseAsyncResult):
151 task_id = task.task_id
150 task_id = task.task_id
152 except formencode.Invalid, errors:
151 except formencode.Invalid, errors:
153 return htmlfill.render(
152 return htmlfill.render(
154 render('admin/repos/repo_add.html'),
153 render('admin/repos/repo_add.html'),
155 defaults=errors.value,
154 defaults=errors.value,
156 errors=errors.error_dict or {},
155 errors=errors.error_dict or {},
157 prefix_error=False,
156 prefix_error=False,
158 force_defaults=False,
157 force_defaults=False,
159 encoding="UTF-8")
158 encoding="UTF-8")
160
159
161 except Exception:
160 except Exception:
162 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
163 msg = (_('Error creating repository %s')
162 msg = (_('Error creating repository %s')
164 % form_result.get('repo_name'))
163 % form_result.get('repo_name'))
165 h.flash(msg, category='error')
164 h.flash(msg, category='error')
166 return redirect(url('home'))
165 return redirect(url('home'))
167
166
168 return redirect(h.url('repo_creating_home',
167 return redirect(h.url('repo_creating_home',
169 repo_name=form_result['repo_name_full'],
168 repo_name=form_result['repo_name_full'],
170 task_id=task_id))
169 task_id=task_id))
171
170
172 @NotAnonymous()
171 @NotAnonymous()
173 def create_repository(self):
172 def create_repository(self):
174 """GET /_admin/create_repository: Form to create a new item"""
173 """GET /_admin/create_repository: Form to create a new item"""
175 new_repo = request.GET.get('repo', '')
174 new_repo = request.GET.get('repo', '')
176 parent_group = request.GET.get('parent_group')
175 parent_group = request.GET.get('parent_group')
177 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
176 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
178 #you're not super admin nor have global create permissions,
177 #you're not super admin nor have global create permissions,
179 #but maybe you have at least write permission to a parent group ?
178 #but maybe you have at least write permission to a parent group ?
180 _gr = RepoGroup.get(parent_group)
179 _gr = RepoGroup.get(parent_group)
181 gr_name = _gr.group_name if _gr else None
180 gr_name = _gr.group_name if _gr else None
182 # create repositories with write permission on group is set to true
181 # create repositories with write permission on group is set to true
183 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
182 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
184 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
183 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
185 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
184 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
186 if not (group_admin or (group_write and create_on_write)):
185 if not (group_admin or (group_write and create_on_write)):
187 raise HTTPForbidden
186 raise HTTPForbidden
188
187
189 acl_groups = RepoGroupList(RepoGroup.query().all(),
188 acl_groups = RepoGroupList(RepoGroup.query().all(),
190 perm_set=['group.write', 'group.admin'])
189 perm_set=['group.write', 'group.admin'])
191 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
190 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
192 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
191 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
193 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
192 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
194
193
195 c.new_repo = repo_name_slug(new_repo)
194 c.new_repo = repo_name_slug(new_repo)
196
195
197 ## apply the defaults from defaults page
196 ## apply the defaults from defaults page
198 defaults = Setting.get_default_repo_settings(strip_prefix=True)
197 defaults = Setting.get_default_repo_settings(strip_prefix=True)
199 if parent_group:
198 if parent_group:
200 defaults.update({'repo_group': parent_group})
199 defaults.update({'repo_group': parent_group})
201
200
202 return htmlfill.render(
201 return htmlfill.render(
203 render('admin/repos/repo_add.html'),
202 render('admin/repos/repo_add.html'),
204 defaults=defaults,
203 defaults=defaults,
205 errors={},
204 errors={},
206 prefix_error=False,
205 prefix_error=False,
207 encoding="UTF-8",
206 encoding="UTF-8",
208 force_defaults=False)
207 force_defaults=False)
209
208
210 @LoginRequired()
209 @LoginRequired()
211 @NotAnonymous()
210 @NotAnonymous()
212 def repo_creating(self, repo_name):
211 def repo_creating(self, repo_name):
213 c.repo = repo_name
212 c.repo = repo_name
214 c.task_id = request.GET.get('task_id')
213 c.task_id = request.GET.get('task_id')
215 if not c.repo:
214 if not c.repo:
216 raise HTTPNotFound()
215 raise HTTPNotFound()
217 return render('admin/repos/repo_creating.html')
216 return render('admin/repos/repo_creating.html')
218
217
219 @LoginRequired()
218 @LoginRequired()
220 @NotAnonymous()
219 @NotAnonymous()
221 @jsonify
220 @jsonify
222 def repo_check(self, repo_name):
221 def repo_check(self, repo_name):
223 c.repo = repo_name
222 c.repo = repo_name
224 task_id = request.GET.get('task_id')
223 task_id = request.GET.get('task_id')
225
224
226 if task_id and task_id not in ['None']:
225 if task_id and task_id not in ['None']:
227 from kallithea import CELERY_ON
226 from kallithea import CELERY_ON
228 from celery.result import AsyncResult
227 from celery.result import AsyncResult
229 if CELERY_ON:
228 if CELERY_ON:
230 task = AsyncResult(task_id)
229 task = AsyncResult(task_id)
231 if task.failed():
230 if task.failed():
232 raise HTTPInternalServerError(task.traceback)
231 raise HTTPInternalServerError(task.traceback)
233
232
234 repo = Repository.get_by_repo_name(repo_name)
233 repo = Repository.get_by_repo_name(repo_name)
235 if repo and repo.repo_state == Repository.STATE_CREATED:
234 if repo and repo.repo_state == Repository.STATE_CREATED:
236 if repo.clone_uri:
235 if repo.clone_uri:
237 clone_uri = repo.clone_uri_hidden
236 clone_uri = repo.clone_uri_hidden
238 h.flash(_('Created repository %s from %s')
237 h.flash(_('Created repository %s from %s')
239 % (repo.repo_name, clone_uri), category='success')
238 % (repo.repo_name, clone_uri), category='success')
240 else:
239 else:
241 repo_url = h.link_to(repo.repo_name,
240 repo_url = h.link_to(repo.repo_name,
242 h.url('summary_home',
241 h.url('summary_home',
243 repo_name=repo.repo_name))
242 repo_name=repo.repo_name))
244 fork = repo.fork
243 fork = repo.fork
245 if fork:
244 if fork:
246 fork_name = fork.repo_name
245 fork_name = fork.repo_name
247 h.flash(h.literal(_('Forked repository %s as %s')
246 h.flash(h.literal(_('Forked repository %s as %s')
248 % (fork_name, repo_url)), category='success')
247 % (fork_name, repo_url)), category='success')
249 else:
248 else:
250 h.flash(h.literal(_('Created repository %s') % repo_url),
249 h.flash(h.literal(_('Created repository %s') % repo_url),
251 category='success')
250 category='success')
252 return {'result': True}
251 return {'result': True}
253 return {'result': False}
252 return {'result': False}
254
253
255 @HasRepoPermissionAllDecorator('repository.admin')
254 @HasRepoPermissionAllDecorator('repository.admin')
256 def update(self, repo_name):
255 def update(self, repo_name):
257 """
256 """
258 PUT /repos/repo_name: Update an existing item"""
257 PUT /repos/repo_name: Update an existing item"""
259 # Forms posted to this method should contain a hidden field:
258 # Forms posted to this method should contain a hidden field:
260 # <input type="hidden" name="_method" value="PUT" />
259 # <input type="hidden" name="_method" value="PUT" />
261 # Or using helpers:
260 # Or using helpers:
262 # h.form(url('repo', repo_name=ID),
261 # h.form(url('repo', repo_name=ID),
263 # method='put')
262 # method='put')
264 # url('repo', repo_name=ID)
263 # url('repo', repo_name=ID)
265 c.repo_info = self._load_repo(repo_name)
264 c.repo_info = self._load_repo(repo_name)
266 c.active = 'settings'
265 c.active = 'settings'
267 c.repo_fields = RepositoryField.query()\
266 c.repo_fields = RepositoryField.query()\
268 .filter(RepositoryField.repository == c.repo_info).all()
267 .filter(RepositoryField.repository == c.repo_info).all()
269 self.__load_defaults(c.repo_info)
268 self.__load_defaults(c.repo_info)
270
269
271 repo_model = RepoModel()
270 repo_model = RepoModel()
272 changed_name = repo_name
271 changed_name = repo_name
273 #override the choices with extracted revisions !
272 #override the choices with extracted revisions !
274 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
273 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
275 c.landing_revs_choices = choices
274 c.landing_revs_choices = choices
276 repo = Repository.get_by_repo_name(repo_name)
275 repo = Repository.get_by_repo_name(repo_name)
277 old_data = {
276 old_data = {
278 'repo_name': repo_name,
277 'repo_name': repo_name,
279 'repo_group': repo.group.get_dict() if repo.group else {},
278 'repo_group': repo.group.get_dict() if repo.group else {},
280 'repo_type': repo.repo_type,
279 'repo_type': repo.repo_type,
281 }
280 }
282 _form = RepoForm(edit=True, old_data=old_data,
281 _form = RepoForm(edit=True, old_data=old_data,
283 repo_groups=c.repo_groups_choices,
282 repo_groups=c.repo_groups_choices,
284 landing_revs=c.landing_revs_choices)()
283 landing_revs=c.landing_revs_choices)()
285
284
286 try:
285 try:
287 form_result = _form.to_python(dict(request.POST))
286 form_result = _form.to_python(dict(request.POST))
288 repo = repo_model.update(repo_name, **form_result)
287 repo = repo_model.update(repo_name, **form_result)
289 ScmModel().mark_for_invalidation(repo_name)
288 ScmModel().mark_for_invalidation(repo_name)
290 h.flash(_('Repository %s updated successfully') % repo_name,
289 h.flash(_('Repository %s updated successfully') % repo_name,
291 category='success')
290 category='success')
292 changed_name = repo.repo_name
291 changed_name = repo.repo_name
293 action_logger(self.authuser, 'admin_updated_repo',
292 action_logger(self.authuser, 'admin_updated_repo',
294 changed_name, self.ip_addr, self.sa)
293 changed_name, self.ip_addr, self.sa)
295 Session().commit()
294 Session().commit()
296 except formencode.Invalid, errors:
295 except formencode.Invalid, errors:
297 defaults = self.__load_data(repo_name)
296 defaults = self.__load_data(repo_name)
298 defaults.update(errors.value)
297 defaults.update(errors.value)
299 return htmlfill.render(
298 return htmlfill.render(
300 render('admin/repos/repo_edit.html'),
299 render('admin/repos/repo_edit.html'),
301 defaults=defaults,
300 defaults=defaults,
302 errors=errors.error_dict or {},
301 errors=errors.error_dict or {},
303 prefix_error=False,
302 prefix_error=False,
304 encoding="UTF-8",
303 encoding="UTF-8",
305 force_defaults=False)
304 force_defaults=False)
306
305
307 except Exception:
306 except Exception:
308 log.error(traceback.format_exc())
307 log.error(traceback.format_exc())
309 h.flash(_('Error occurred during update of repository %s') \
308 h.flash(_('Error occurred during update of repository %s') \
310 % repo_name, category='error')
309 % repo_name, category='error')
311 return redirect(url('edit_repo', repo_name=changed_name))
310 return redirect(url('edit_repo', repo_name=changed_name))
312
311
313 @HasRepoPermissionAllDecorator('repository.admin')
312 @HasRepoPermissionAllDecorator('repository.admin')
314 def delete(self, repo_name):
313 def delete(self, repo_name):
315 """
314 """
316 DELETE /repos/repo_name: Delete an existing item"""
315 DELETE /repos/repo_name: Delete an existing item"""
317 # Forms posted to this method should contain a hidden field:
316 # Forms posted to this method should contain a hidden field:
318 # <input type="hidden" name="_method" value="DELETE" />
317 # <input type="hidden" name="_method" value="DELETE" />
319 # Or using helpers:
318 # Or using helpers:
320 # h.form(url('repo', repo_name=ID),
319 # h.form(url('repo', repo_name=ID),
321 # method='delete')
320 # method='delete')
322 # url('repo', repo_name=ID)
321 # url('repo', repo_name=ID)
323
322
324 repo_model = RepoModel()
323 repo_model = RepoModel()
325 repo = repo_model.get_by_repo_name(repo_name)
324 repo = repo_model.get_by_repo_name(repo_name)
326 if not repo:
325 if not repo:
327 h.not_mapped_error(repo_name)
326 h.not_mapped_error(repo_name)
328 return redirect(url('repos'))
327 return redirect(url('repos'))
329 try:
328 try:
330 _forks = repo.forks.count()
329 _forks = repo.forks.count()
331 handle_forks = None
330 handle_forks = None
332 if _forks and request.POST.get('forks'):
331 if _forks and request.POST.get('forks'):
333 do = request.POST['forks']
332 do = request.POST['forks']
334 if do == 'detach_forks':
333 if do == 'detach_forks':
335 handle_forks = 'detach'
334 handle_forks = 'detach'
336 h.flash(_('Detached %s forks') % _forks, category='success')
335 h.flash(_('Detached %s forks') % _forks, category='success')
337 elif do == 'delete_forks':
336 elif do == 'delete_forks':
338 handle_forks = 'delete'
337 handle_forks = 'delete'
339 h.flash(_('Deleted %s forks') % _forks, category='success')
338 h.flash(_('Deleted %s forks') % _forks, category='success')
340 repo_model.delete(repo, forks=handle_forks)
339 repo_model.delete(repo, forks=handle_forks)
341 action_logger(self.authuser, 'admin_deleted_repo',
340 action_logger(self.authuser, 'admin_deleted_repo',
342 repo_name, self.ip_addr, self.sa)
341 repo_name, self.ip_addr, self.sa)
343 ScmModel().mark_for_invalidation(repo_name)
342 ScmModel().mark_for_invalidation(repo_name)
344 h.flash(_('Deleted repository %s') % repo_name, category='success')
343 h.flash(_('Deleted repository %s') % repo_name, category='success')
345 Session().commit()
344 Session().commit()
346 except AttachedForksError:
345 except AttachedForksError:
347 h.flash(_('Cannot delete %s it still contains attached forks')
346 h.flash(_('Cannot delete %s it still contains attached forks')
348 % repo_name, category='warning')
347 % repo_name, category='warning')
349
348
350 except Exception:
349 except Exception:
351 log.error(traceback.format_exc())
350 log.error(traceback.format_exc())
352 h.flash(_('An error occurred during deletion of %s') % repo_name,
351 h.flash(_('An error occurred during deletion of %s') % repo_name,
353 category='error')
352 category='error')
354
353
355 if repo.group:
354 if repo.group:
356 return redirect(url('repos_group_home', group_name=repo.group.group_name))
355 return redirect(url('repos_group_home', group_name=repo.group.group_name))
357 return redirect(url('repos'))
356 return redirect(url('repos'))
358
357
359 @HasPermissionAllDecorator('hg.admin')
358 @HasPermissionAllDecorator('hg.admin')
360 def show(self, repo_name, format='html'):
359 def show(self, repo_name, format='html'):
361 """GET /repos/repo_name: Show a specific item"""
360 """GET /repos/repo_name: Show a specific item"""
362 # url('repo', repo_name=ID)
361 # url('repo', repo_name=ID)
363
362
364 @HasRepoPermissionAllDecorator('repository.admin')
363 @HasRepoPermissionAllDecorator('repository.admin')
365 def edit(self, repo_name):
364 def edit(self, repo_name):
366 """GET /repo_name/settings: Form to edit an existing item"""
365 """GET /repo_name/settings: Form to edit an existing item"""
367 # url('edit_repo', repo_name=ID)
366 # url('edit_repo', repo_name=ID)
368 defaults = self.__load_data(repo_name)
367 defaults = self.__load_data(repo_name)
369 if 'clone_uri' in defaults:
368 if 'clone_uri' in defaults:
370 del defaults['clone_uri']
369 del defaults['clone_uri']
371
370
372 c.repo_fields = RepositoryField.query()\
371 c.repo_fields = RepositoryField.query()\
373 .filter(RepositoryField.repository == c.repo_info).all()
372 .filter(RepositoryField.repository == c.repo_info).all()
374 c.active = 'settings'
373 c.active = 'settings'
375 return htmlfill.render(
374 return htmlfill.render(
376 render('admin/repos/repo_edit.html'),
375 render('admin/repos/repo_edit.html'),
377 defaults=defaults,
376 defaults=defaults,
378 encoding="UTF-8",
377 encoding="UTF-8",
379 force_defaults=False)
378 force_defaults=False)
380
379
381 @HasRepoPermissionAllDecorator('repository.admin')
380 @HasRepoPermissionAllDecorator('repository.admin')
382 def edit_permissions(self, repo_name):
381 def edit_permissions(self, repo_name):
383 """GET /repo_name/settings: Form to edit an existing item"""
382 """GET /repo_name/settings: Form to edit an existing item"""
384 # url('edit_repo', repo_name=ID)
383 # url('edit_repo', repo_name=ID)
385 c.repo_info = self._load_repo(repo_name)
384 c.repo_info = self._load_repo(repo_name)
386 repo_model = RepoModel()
385 repo_model = RepoModel()
387 c.users_array = repo_model.get_users_js()
386 c.users_array = repo_model.get_users_js()
388 c.user_groups_array = repo_model.get_user_groups_js()
387 c.user_groups_array = repo_model.get_user_groups_js()
389 c.active = 'permissions'
388 c.active = 'permissions'
390 defaults = RepoModel()._get_defaults(repo_name)
389 defaults = RepoModel()._get_defaults(repo_name)
391
390
392 return htmlfill.render(
391 return htmlfill.render(
393 render('admin/repos/repo_edit.html'),
392 render('admin/repos/repo_edit.html'),
394 defaults=defaults,
393 defaults=defaults,
395 encoding="UTF-8",
394 encoding="UTF-8",
396 force_defaults=False)
395 force_defaults=False)
397
396
398 def edit_permissions_update(self, repo_name):
397 def edit_permissions_update(self, repo_name):
399 form = RepoPermsForm()().to_python(request.POST)
398 form = RepoPermsForm()().to_python(request.POST)
400 RepoModel()._update_permissions(repo_name, form['perms_new'],
399 RepoModel()._update_permissions(repo_name, form['perms_new'],
401 form['perms_updates'])
400 form['perms_updates'])
402 #TODO: implement this
401 #TODO: implement this
403 #action_logger(self.authuser, 'admin_changed_repo_permissions',
402 #action_logger(self.authuser, 'admin_changed_repo_permissions',
404 # repo_name, self.ip_addr, self.sa)
403 # repo_name, self.ip_addr, self.sa)
405 Session().commit()
404 Session().commit()
406 h.flash(_('Repository permissions updated'), category='success')
405 h.flash(_('Repository permissions updated'), category='success')
407 return redirect(url('edit_repo_perms', repo_name=repo_name))
406 return redirect(url('edit_repo_perms', repo_name=repo_name))
408
407
409 def edit_permissions_revoke(self, repo_name):
408 def edit_permissions_revoke(self, repo_name):
410 try:
409 try:
411 obj_type = request.POST.get('obj_type')
410 obj_type = request.POST.get('obj_type')
412 obj_id = None
411 obj_id = None
413 if obj_type == 'user':
412 if obj_type == 'user':
414 obj_id = safe_int(request.POST.get('user_id'))
413 obj_id = safe_int(request.POST.get('user_id'))
415 elif obj_type == 'user_group':
414 elif obj_type == 'user_group':
416 obj_id = safe_int(request.POST.get('user_group_id'))
415 obj_id = safe_int(request.POST.get('user_group_id'))
417
416
418 if obj_type == 'user':
417 if obj_type == 'user':
419 RepoModel().revoke_user_permission(repo=repo_name, user=obj_id)
418 RepoModel().revoke_user_permission(repo=repo_name, user=obj_id)
420 elif obj_type == 'user_group':
419 elif obj_type == 'user_group':
421 RepoModel().revoke_user_group_permission(
420 RepoModel().revoke_user_group_permission(
422 repo=repo_name, group_name=obj_id
421 repo=repo_name, group_name=obj_id
423 )
422 )
424 #TODO: implement this
423 #TODO: implement this
425 #action_logger(self.authuser, 'admin_revoked_repo_permissions',
424 #action_logger(self.authuser, 'admin_revoked_repo_permissions',
426 # repo_name, self.ip_addr, self.sa)
425 # repo_name, self.ip_addr, self.sa)
427 Session().commit()
426 Session().commit()
428 except Exception:
427 except Exception:
429 log.error(traceback.format_exc())
428 log.error(traceback.format_exc())
430 h.flash(_('An error occurred during revoking of permission'),
429 h.flash(_('An error occurred during revoking of permission'),
431 category='error')
430 category='error')
432 raise HTTPInternalServerError()
431 raise HTTPInternalServerError()
433
432
434 @HasRepoPermissionAllDecorator('repository.admin')
433 @HasRepoPermissionAllDecorator('repository.admin')
435 def edit_fields(self, repo_name):
434 def edit_fields(self, repo_name):
436 """GET /repo_name/settings: Form to edit an existing item"""
435 """GET /repo_name/settings: Form to edit an existing item"""
437 # url('edit_repo', repo_name=ID)
436 # url('edit_repo', repo_name=ID)
438 c.repo_info = self._load_repo(repo_name)
437 c.repo_info = self._load_repo(repo_name)
439 c.repo_fields = RepositoryField.query()\
438 c.repo_fields = RepositoryField.query()\
440 .filter(RepositoryField.repository == c.repo_info).all()
439 .filter(RepositoryField.repository == c.repo_info).all()
441 c.active = 'fields'
440 c.active = 'fields'
442 if request.POST:
441 if request.POST:
443
442
444 return redirect(url('repo_edit_fields'))
443 return redirect(url('repo_edit_fields'))
445 return render('admin/repos/repo_edit.html')
444 return render('admin/repos/repo_edit.html')
446
445
447 @HasRepoPermissionAllDecorator('repository.admin')
446 @HasRepoPermissionAllDecorator('repository.admin')
448 def create_repo_field(self, repo_name):
447 def create_repo_field(self, repo_name):
449 try:
448 try:
450 form_result = RepoFieldForm()().to_python(dict(request.POST))
449 form_result = RepoFieldForm()().to_python(dict(request.POST))
451 new_field = RepositoryField()
450 new_field = RepositoryField()
452 new_field.repository = Repository.get_by_repo_name(repo_name)
451 new_field.repository = Repository.get_by_repo_name(repo_name)
453 new_field.field_key = form_result['new_field_key']
452 new_field.field_key = form_result['new_field_key']
454 new_field.field_type = form_result['new_field_type'] # python type
453 new_field.field_type = form_result['new_field_type'] # python type
455 new_field.field_value = form_result['new_field_value'] # set initial blank value
454 new_field.field_value = form_result['new_field_value'] # set initial blank value
456 new_field.field_desc = form_result['new_field_desc']
455 new_field.field_desc = form_result['new_field_desc']
457 new_field.field_label = form_result['new_field_label']
456 new_field.field_label = form_result['new_field_label']
458 Session().add(new_field)
457 Session().add(new_field)
459 Session().commit()
458 Session().commit()
460 except Exception, e:
459 except Exception, e:
461 log.error(traceback.format_exc())
460 log.error(traceback.format_exc())
462 msg = _('An error occurred during creation of field')
461 msg = _('An error occurred during creation of field')
463 if isinstance(e, formencode.Invalid):
462 if isinstance(e, formencode.Invalid):
464 msg += ". " + e.msg
463 msg += ". " + e.msg
465 h.flash(msg, category='error')
464 h.flash(msg, category='error')
466 return redirect(url('edit_repo_fields', repo_name=repo_name))
465 return redirect(url('edit_repo_fields', repo_name=repo_name))
467
466
468 @HasRepoPermissionAllDecorator('repository.admin')
467 @HasRepoPermissionAllDecorator('repository.admin')
469 def delete_repo_field(self, repo_name, field_id):
468 def delete_repo_field(self, repo_name, field_id):
470 field = RepositoryField.get_or_404(field_id)
469 field = RepositoryField.get_or_404(field_id)
471 try:
470 try:
472 Session().delete(field)
471 Session().delete(field)
473 Session().commit()
472 Session().commit()
474 except Exception, e:
473 except Exception, e:
475 log.error(traceback.format_exc())
474 log.error(traceback.format_exc())
476 msg = _('An error occurred during removal of field')
475 msg = _('An error occurred during removal of field')
477 h.flash(msg, category='error')
476 h.flash(msg, category='error')
478 return redirect(url('edit_repo_fields', repo_name=repo_name))
477 return redirect(url('edit_repo_fields', repo_name=repo_name))
479
478
480 @HasRepoPermissionAllDecorator('repository.admin')
479 @HasRepoPermissionAllDecorator('repository.admin')
481 def edit_advanced(self, repo_name):
480 def edit_advanced(self, repo_name):
482 """GET /repo_name/settings: Form to edit an existing item"""
481 """GET /repo_name/settings: Form to edit an existing item"""
483 # url('edit_repo', repo_name=ID)
482 # url('edit_repo', repo_name=ID)
484 c.repo_info = self._load_repo(repo_name)
483 c.repo_info = self._load_repo(repo_name)
485 c.default_user_id = User.get_default_user().user_id
484 c.default_user_id = User.get_default_user().user_id
486 c.in_public_journal = UserFollowing.query()\
485 c.in_public_journal = UserFollowing.query()\
487 .filter(UserFollowing.user_id == c.default_user_id)\
486 .filter(UserFollowing.user_id == c.default_user_id)\
488 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
487 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
489
488
490 _repos = Repository.query().order_by(Repository.repo_name).all()
489 _repos = Repository.query().order_by(Repository.repo_name).all()
491 read_access_repos = RepoList(_repos)
490 read_access_repos = RepoList(_repos)
492 c.repos_list = [(None, _('-- Not a fork --'))]
491 c.repos_list = [(None, _('-- Not a fork --'))]
493 c.repos_list += [(x.repo_id, x.repo_name)
492 c.repos_list += [(x.repo_id, x.repo_name)
494 for x in read_access_repos
493 for x in read_access_repos
495 if x.repo_id != c.repo_info.repo_id]
494 if x.repo_id != c.repo_info.repo_id]
496
495
497 defaults = {
496 defaults = {
498 'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else ''
497 'id_fork_of': c.repo_info.fork.repo_id if c.repo_info.fork else ''
499 }
498 }
500
499
501 c.active = 'advanced'
500 c.active = 'advanced'
502 if request.POST:
501 if request.POST:
503 return redirect(url('repo_edit_advanced'))
502 return redirect(url('repo_edit_advanced'))
504 return htmlfill.render(
503 return htmlfill.render(
505 render('admin/repos/repo_edit.html'),
504 render('admin/repos/repo_edit.html'),
506 defaults=defaults,
505 defaults=defaults,
507 encoding="UTF-8",
506 encoding="UTF-8",
508 force_defaults=False)
507 force_defaults=False)
509
508
510 @HasRepoPermissionAllDecorator('repository.admin')
509 @HasRepoPermissionAllDecorator('repository.admin')
511 def edit_advanced_journal(self, repo_name):
510 def edit_advanced_journal(self, repo_name):
512 """
511 """
513 Sets this repository to be visible in public journal,
512 Sets this repository to be visible in public journal,
514 in other words asking default user to follow this repo
513 in other words asking default user to follow this repo
515
514
516 :param repo_name:
515 :param repo_name:
517 """
516 """
518
517
519 cur_token = request.POST.get('auth_token')
518 try:
520 token = get_token()
519 repo_id = Repository.get_by_repo_name(repo_name).repo_id
521 if cur_token == token:
520 user_id = User.get_default_user().user_id
522 try:
521 self.scm_model.toggle_following_repo(repo_id, user_id)
523 repo_id = Repository.get_by_repo_name(repo_name).repo_id
522 h.flash(_('Updated repository visibility in public journal'),
524 user_id = User.get_default_user().user_id
523 category='success')
525 self.scm_model.toggle_following_repo(repo_id, user_id)
524 Session().commit()
526 h.flash(_('Updated repository visibility in public journal'),
525 except Exception:
527 category='success')
526 h.flash(_('An error occurred during setting this'
528 Session().commit()
527 ' repository in public journal'),
529 except Exception:
528 category='error')
530 h.flash(_('An error occurred during setting this'
531 ' repository in public journal'),
532 category='error')
533
534 else:
535 h.flash(_('Token mismatch'), category='error')
536 return redirect(url('edit_repo_advanced', repo_name=repo_name))
529 return redirect(url('edit_repo_advanced', repo_name=repo_name))
537
530
538
531
539 @HasRepoPermissionAllDecorator('repository.admin')
532 @HasRepoPermissionAllDecorator('repository.admin')
540 def edit_advanced_fork(self, repo_name):
533 def edit_advanced_fork(self, repo_name):
541 """
534 """
542 Mark given repository as a fork of another
535 Mark given repository as a fork of another
543
536
544 :param repo_name:
537 :param repo_name:
545 """
538 """
546 try:
539 try:
547 fork_id = request.POST.get('id_fork_of')
540 fork_id = request.POST.get('id_fork_of')
548 repo = ScmModel().mark_as_fork(repo_name, fork_id,
541 repo = ScmModel().mark_as_fork(repo_name, fork_id,
549 self.authuser.username)
542 self.authuser.username)
550 fork = repo.fork.repo_name if repo.fork else _('Nothing')
543 fork = repo.fork.repo_name if repo.fork else _('Nothing')
551 Session().commit()
544 Session().commit()
552 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
545 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
553 category='success')
546 category='success')
554 except RepositoryError, e:
547 except RepositoryError, e:
555 log.error(traceback.format_exc())
548 log.error(traceback.format_exc())
556 h.flash(str(e), category='error')
549 h.flash(str(e), category='error')
557 except Exception, e:
550 except Exception, e:
558 log.error(traceback.format_exc())
551 log.error(traceback.format_exc())
559 h.flash(_('An error occurred during this operation'),
552 h.flash(_('An error occurred during this operation'),
560 category='error')
553 category='error')
561
554
562 return redirect(url('edit_repo_advanced', repo_name=repo_name))
555 return redirect(url('edit_repo_advanced', repo_name=repo_name))
563
556
564 @HasRepoPermissionAllDecorator('repository.admin')
557 @HasRepoPermissionAllDecorator('repository.admin')
565 def edit_advanced_locking(self, repo_name):
558 def edit_advanced_locking(self, repo_name):
566 """
559 """
567 Unlock repository when it is locked !
560 Unlock repository when it is locked !
568
561
569 :param repo_name:
562 :param repo_name:
570 """
563 """
571 try:
564 try:
572 repo = Repository.get_by_repo_name(repo_name)
565 repo = Repository.get_by_repo_name(repo_name)
573 if request.POST.get('set_lock'):
566 if request.POST.get('set_lock'):
574 Repository.lock(repo, c.authuser.user_id)
567 Repository.lock(repo, c.authuser.user_id)
575 h.flash(_('Locked repository'), category='success')
568 h.flash(_('Locked repository'), category='success')
576 elif request.POST.get('set_unlock'):
569 elif request.POST.get('set_unlock'):
577 Repository.unlock(repo)
570 Repository.unlock(repo)
578 h.flash(_('Unlocked repository'), category='success')
571 h.flash(_('Unlocked repository'), category='success')
579 except Exception, e:
572 except Exception, e:
580 log.error(traceback.format_exc())
573 log.error(traceback.format_exc())
581 h.flash(_('An error occurred during unlocking'),
574 h.flash(_('An error occurred during unlocking'),
582 category='error')
575 category='error')
583 return redirect(url('edit_repo_advanced', repo_name=repo_name))
576 return redirect(url('edit_repo_advanced', repo_name=repo_name))
584
577
585 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
578 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
586 def toggle_locking(self, repo_name):
579 def toggle_locking(self, repo_name):
587 """
580 """
588 Toggle locking of repository by simple GET call to url
581 Toggle locking of repository by simple GET call to url
589
582
590 :param repo_name:
583 :param repo_name:
591 """
584 """
592
585
593 try:
586 try:
594 repo = Repository.get_by_repo_name(repo_name)
587 repo = Repository.get_by_repo_name(repo_name)
595
588
596 if repo.enable_locking:
589 if repo.enable_locking:
597 if repo.locked[0]:
590 if repo.locked[0]:
598 Repository.unlock(repo)
591 Repository.unlock(repo)
599 action = _('Unlocked')
592 action = _('Unlocked')
600 else:
593 else:
601 Repository.lock(repo, c.authuser.user_id)
594 Repository.lock(repo, c.authuser.user_id)
602 action = _('Locked')
595 action = _('Locked')
603
596
604 h.flash(_('Repository has been %s') % action,
597 h.flash(_('Repository has been %s') % action,
605 category='success')
598 category='success')
606 except Exception, e:
599 except Exception, e:
607 log.error(traceback.format_exc())
600 log.error(traceback.format_exc())
608 h.flash(_('An error occurred during unlocking'),
601 h.flash(_('An error occurred during unlocking'),
609 category='error')
602 category='error')
610 return redirect(url('summary_home', repo_name=repo_name))
603 return redirect(url('summary_home', repo_name=repo_name))
611
604
612 @HasRepoPermissionAllDecorator('repository.admin')
605 @HasRepoPermissionAllDecorator('repository.admin')
613 def edit_caches(self, repo_name):
606 def edit_caches(self, repo_name):
614 """GET /repo_name/settings: Form to edit an existing item"""
607 """GET /repo_name/settings: Form to edit an existing item"""
615 # url('edit_repo', repo_name=ID)
608 # url('edit_repo', repo_name=ID)
616 c.repo_info = self._load_repo(repo_name)
609 c.repo_info = self._load_repo(repo_name)
617 c.active = 'caches'
610 c.active = 'caches'
618 if request.POST:
611 if request.POST:
619 try:
612 try:
620 ScmModel().mark_for_invalidation(repo_name, delete=True)
613 ScmModel().mark_for_invalidation(repo_name, delete=True)
621 Session().commit()
614 Session().commit()
622 h.flash(_('Cache invalidation successful'),
615 h.flash(_('Cache invalidation successful'),
623 category='success')
616 category='success')
624 except Exception, e:
617 except Exception, e:
625 log.error(traceback.format_exc())
618 log.error(traceback.format_exc())
626 h.flash(_('An error occurred during cache invalidation'),
619 h.flash(_('An error occurred during cache invalidation'),
627 category='error')
620 category='error')
628
621
629 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
622 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
630 return render('admin/repos/repo_edit.html')
623 return render('admin/repos/repo_edit.html')
631
624
632 @HasRepoPermissionAllDecorator('repository.admin')
625 @HasRepoPermissionAllDecorator('repository.admin')
633 def edit_remote(self, repo_name):
626 def edit_remote(self, repo_name):
634 """GET /repo_name/settings: Form to edit an existing item"""
627 """GET /repo_name/settings: Form to edit an existing item"""
635 # url('edit_repo', repo_name=ID)
628 # url('edit_repo', repo_name=ID)
636 c.repo_info = self._load_repo(repo_name)
629 c.repo_info = self._load_repo(repo_name)
637 c.active = 'remote'
630 c.active = 'remote'
638 if request.POST:
631 if request.POST:
639 try:
632 try:
640 ScmModel().pull_changes(repo_name, self.authuser.username)
633 ScmModel().pull_changes(repo_name, self.authuser.username)
641 h.flash(_('Pulled from remote location'), category='success')
634 h.flash(_('Pulled from remote location'), category='success')
642 except Exception, e:
635 except Exception, e:
643 log.error(traceback.format_exc())
636 log.error(traceback.format_exc())
644 h.flash(_('An error occurred during pull from remote location'),
637 h.flash(_('An error occurred during pull from remote location'),
645 category='error')
638 category='error')
646 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
639 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
647 return render('admin/repos/repo_edit.html')
640 return render('admin/repos/repo_edit.html')
648
641
649 @HasRepoPermissionAllDecorator('repository.admin')
642 @HasRepoPermissionAllDecorator('repository.admin')
650 def edit_statistics(self, repo_name):
643 def edit_statistics(self, repo_name):
651 """GET /repo_name/settings: Form to edit an existing item"""
644 """GET /repo_name/settings: Form to edit an existing item"""
652 # url('edit_repo', repo_name=ID)
645 # url('edit_repo', repo_name=ID)
653 c.repo_info = self._load_repo(repo_name)
646 c.repo_info = self._load_repo(repo_name)
654 repo = c.repo_info.scm_instance
647 repo = c.repo_info.scm_instance
655
648
656 if c.repo_info.stats:
649 if c.repo_info.stats:
657 # this is on what revision we ended up so we add +1 for count
650 # this is on what revision we ended up so we add +1 for count
658 last_rev = c.repo_info.stats.stat_on_revision + 1
651 last_rev = c.repo_info.stats.stat_on_revision + 1
659 else:
652 else:
660 last_rev = 0
653 last_rev = 0
661 c.stats_revision = last_rev
654 c.stats_revision = last_rev
662
655
663 c.repo_last_rev = repo.count() if repo.revisions else 0
656 c.repo_last_rev = repo.count() if repo.revisions else 0
664
657
665 if last_rev == 0 or c.repo_last_rev == 0:
658 if last_rev == 0 or c.repo_last_rev == 0:
666 c.stats_percentage = 0
659 c.stats_percentage = 0
667 else:
660 else:
668 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
661 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
669
662
670 c.active = 'statistics'
663 c.active = 'statistics'
671 if request.POST:
664 if request.POST:
672 try:
665 try:
673 RepoModel().delete_stats(repo_name)
666 RepoModel().delete_stats(repo_name)
674 Session().commit()
667 Session().commit()
675 except Exception, e:
668 except Exception, e:
676 log.error(traceback.format_exc())
669 log.error(traceback.format_exc())
677 h.flash(_('An error occurred during deletion of repository stats'),
670 h.flash(_('An error occurred during deletion of repository stats'),
678 category='error')
671 category='error')
679 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
672 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
680
673
681 return render('admin/repos/repo_edit.html')
674 return render('admin/repos/repo_edit.html')
@@ -1,379 +1,374 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.journal
15 kallithea.controllers.journal
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Journal controller for pylons
18 Journal controller for pylons
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Nov 21, 2010
22 :created_on: Nov 21, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26
26
27 """
27 """
28
28
29 import logging
29 import logging
30 import traceback
30 import traceback
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from sqlalchemy import or_
33 from sqlalchemy import or_
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35 from sqlalchemy.sql.expression import func
35 from sqlalchemy.sql.expression import func
36
36
37 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
37 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
38
38
39 from webob.exc import HTTPBadRequest
39 from webob.exc import HTTPBadRequest
40 from pylons import request, tmpl_context as c, response, url
40 from pylons import request, tmpl_context as c, response, url
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42
42
43 from kallithea.controllers.admin.admin import _journal_filter
43 from kallithea.controllers.admin.admin import _journal_filter
44 from kallithea.model.db import UserLog, UserFollowing, Repository, User
44 from kallithea.model.db import UserLog, UserFollowing, Repository, User
45 from kallithea.model.meta import Session
45 from kallithea.model.meta import Session
46 from kallithea.model.repo import RepoModel
46 from kallithea.model.repo import RepoModel
47 import kallithea.lib.helpers as h
47 import kallithea.lib.helpers as h
48 from kallithea.lib.helpers import Page
48 from kallithea.lib.helpers import Page
49 from kallithea.lib.auth import LoginRequired, NotAnonymous
49 from kallithea.lib.auth import LoginRequired, NotAnonymous
50 from kallithea.lib.base import BaseController, render
50 from kallithea.lib.base import BaseController, render
51 from kallithea.lib.utils2 import safe_int, AttributeDict
51 from kallithea.lib.utils2 import safe_int, AttributeDict
52 from kallithea.lib.compat import json
52 from kallithea.lib.compat import json
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class JournalController(BaseController):
57 class JournalController(BaseController):
58
58
59 def __before__(self):
59 def __before__(self):
60 super(JournalController, self).__before__()
60 super(JournalController, self).__before__()
61 self.language = 'en-us'
61 self.language = 'en-us'
62 self.ttl = "5"
62 self.ttl = "5"
63 self.feed_nr = 20
63 self.feed_nr = 20
64 c.search_term = request.GET.get('filter')
64 c.search_term = request.GET.get('filter')
65
65
66 def _get_daily_aggregate(self, journal):
66 def _get_daily_aggregate(self, journal):
67 groups = []
67 groups = []
68 for k, g in groupby(journal, lambda x: x.action_as_day):
68 for k, g in groupby(journal, lambda x: x.action_as_day):
69 user_group = []
69 user_group = []
70 #groupby username if it's a present value, else fallback to journal username
70 #groupby username if it's a present value, else fallback to journal username
71 for _unused, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
71 for _unused, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
72 l = list(g2)
72 l = list(g2)
73 user_group.append((l[0].user, l))
73 user_group.append((l[0].user, l))
74
74
75 groups.append((k, user_group,))
75 groups.append((k, user_group,))
76
76
77 return groups
77 return groups
78
78
79 def _get_journal_data(self, following_repos):
79 def _get_journal_data(self, following_repos):
80 repo_ids = [x.follows_repository.repo_id for x in following_repos
80 repo_ids = [x.follows_repository.repo_id for x in following_repos
81 if x.follows_repository is not None]
81 if x.follows_repository is not None]
82 user_ids = [x.follows_user.user_id for x in following_repos
82 user_ids = [x.follows_user.user_id for x in following_repos
83 if x.follows_user is not None]
83 if x.follows_user is not None]
84
84
85 filtering_criterion = None
85 filtering_criterion = None
86
86
87 if repo_ids and user_ids:
87 if repo_ids and user_ids:
88 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
88 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
89 UserLog.user_id.in_(user_ids))
89 UserLog.user_id.in_(user_ids))
90 if repo_ids and not user_ids:
90 if repo_ids and not user_ids:
91 filtering_criterion = UserLog.repository_id.in_(repo_ids)
91 filtering_criterion = UserLog.repository_id.in_(repo_ids)
92 if not repo_ids and user_ids:
92 if not repo_ids and user_ids:
93 filtering_criterion = UserLog.user_id.in_(user_ids)
93 filtering_criterion = UserLog.user_id.in_(user_ids)
94 if filtering_criterion is not None:
94 if filtering_criterion is not None:
95 journal = self.sa.query(UserLog)\
95 journal = self.sa.query(UserLog)\
96 .options(joinedload(UserLog.user))\
96 .options(joinedload(UserLog.user))\
97 .options(joinedload(UserLog.repository))
97 .options(joinedload(UserLog.repository))
98 #filter
98 #filter
99 journal = _journal_filter(journal, c.search_term)
99 journal = _journal_filter(journal, c.search_term)
100 journal = journal.filter(filtering_criterion)\
100 journal = journal.filter(filtering_criterion)\
101 .order_by(UserLog.action_date.desc())
101 .order_by(UserLog.action_date.desc())
102 else:
102 else:
103 journal = []
103 journal = []
104
104
105 return journal
105 return journal
106
106
107 def _atom_feed(self, repos, public=True):
107 def _atom_feed(self, repos, public=True):
108 journal = self._get_journal_data(repos)
108 journal = self._get_journal_data(repos)
109 if public:
109 if public:
110 _link = h.canonical_url('public_journal_atom')
110 _link = h.canonical_url('public_journal_atom')
111 _desc = '%s %s %s' % (c.site_name, _('public journal'),
111 _desc = '%s %s %s' % (c.site_name, _('public journal'),
112 'atom feed')
112 'atom feed')
113 else:
113 else:
114 _link = h.canonical_url('journal_atom')
114 _link = h.canonical_url('journal_atom')
115 _desc = '%s %s %s' % (c.site_name, _('journal'), 'atom feed')
115 _desc = '%s %s %s' % (c.site_name, _('journal'), 'atom feed')
116
116
117 feed = Atom1Feed(title=_desc,
117 feed = Atom1Feed(title=_desc,
118 link=_link,
118 link=_link,
119 description=_desc,
119 description=_desc,
120 language=self.language,
120 language=self.language,
121 ttl=self.ttl)
121 ttl=self.ttl)
122
122
123 for entry in journal[:self.feed_nr]:
123 for entry in journal[:self.feed_nr]:
124 user = entry.user
124 user = entry.user
125 if user is None:
125 if user is None:
126 #fix deleted users
126 #fix deleted users
127 user = AttributeDict({'short_contact': entry.username,
127 user = AttributeDict({'short_contact': entry.username,
128 'email': '',
128 'email': '',
129 'full_contact': ''})
129 'full_contact': ''})
130 action, action_extra, ico = h.action_parser(entry, feed=True)
130 action, action_extra, ico = h.action_parser(entry, feed=True)
131 title = "%s - %s %s" % (user.short_contact, action(),
131 title = "%s - %s %s" % (user.short_contact, action(),
132 entry.repository.repo_name)
132 entry.repository.repo_name)
133 desc = action_extra()
133 desc = action_extra()
134 _url = None
134 _url = None
135 if entry.repository is not None:
135 if entry.repository is not None:
136 _url = h.canonical_url('changelog_home',
136 _url = h.canonical_url('changelog_home',
137 repo_name=entry.repository.repo_name)
137 repo_name=entry.repository.repo_name)
138
138
139 feed.add_item(title=title,
139 feed.add_item(title=title,
140 pubdate=entry.action_date,
140 pubdate=entry.action_date,
141 link=_url or h.canonical_url(''),
141 link=_url or h.canonical_url(''),
142 author_email=user.email,
142 author_email=user.email,
143 author_name=user.full_contact,
143 author_name=user.full_contact,
144 description=desc)
144 description=desc)
145
145
146 response.content_type = feed.mime_type
146 response.content_type = feed.mime_type
147 return feed.writeString('utf-8')
147 return feed.writeString('utf-8')
148
148
149 def _rss_feed(self, repos, public=True):
149 def _rss_feed(self, repos, public=True):
150 journal = self._get_journal_data(repos)
150 journal = self._get_journal_data(repos)
151 if public:
151 if public:
152 _link = h.canonical_url('public_journal_atom')
152 _link = h.canonical_url('public_journal_atom')
153 _desc = '%s %s %s' % (c.site_name, _('public journal'),
153 _desc = '%s %s %s' % (c.site_name, _('public journal'),
154 'rss feed')
154 'rss feed')
155 else:
155 else:
156 _link = h.canonical_url('journal_atom')
156 _link = h.canonical_url('journal_atom')
157 _desc = '%s %s %s' % (c.site_name, _('journal'), 'rss feed')
157 _desc = '%s %s %s' % (c.site_name, _('journal'), 'rss feed')
158
158
159 feed = Rss201rev2Feed(title=_desc,
159 feed = Rss201rev2Feed(title=_desc,
160 link=_link,
160 link=_link,
161 description=_desc,
161 description=_desc,
162 language=self.language,
162 language=self.language,
163 ttl=self.ttl)
163 ttl=self.ttl)
164
164
165 for entry in journal[:self.feed_nr]:
165 for entry in journal[:self.feed_nr]:
166 user = entry.user
166 user = entry.user
167 if user is None:
167 if user is None:
168 #fix deleted users
168 #fix deleted users
169 user = AttributeDict({'short_contact': entry.username,
169 user = AttributeDict({'short_contact': entry.username,
170 'email': '',
170 'email': '',
171 'full_contact': ''})
171 'full_contact': ''})
172 action, action_extra, ico = h.action_parser(entry, feed=True)
172 action, action_extra, ico = h.action_parser(entry, feed=True)
173 title = "%s - %s %s" % (user.short_contact, action(),
173 title = "%s - %s %s" % (user.short_contact, action(),
174 entry.repository.repo_name)
174 entry.repository.repo_name)
175 desc = action_extra()
175 desc = action_extra()
176 _url = None
176 _url = None
177 if entry.repository is not None:
177 if entry.repository is not None:
178 _url = h.canonical_url('changelog_home',
178 _url = h.canonical_url('changelog_home',
179 repo_name=entry.repository.repo_name)
179 repo_name=entry.repository.repo_name)
180
180
181 feed.add_item(title=title,
181 feed.add_item(title=title,
182 pubdate=entry.action_date,
182 pubdate=entry.action_date,
183 link=_url or h.canonical_url(''),
183 link=_url or h.canonical_url(''),
184 author_email=user.email,
184 author_email=user.email,
185 author_name=user.full_contact,
185 author_name=user.full_contact,
186 description=desc)
186 description=desc)
187
187
188 response.content_type = feed.mime_type
188 response.content_type = feed.mime_type
189 return feed.writeString('utf-8')
189 return feed.writeString('utf-8')
190
190
191 @LoginRequired()
191 @LoginRequired()
192 @NotAnonymous()
192 @NotAnonymous()
193 def index(self):
193 def index(self):
194 # Return a rendered template
194 # Return a rendered template
195 p = safe_int(request.GET.get('page', 1), 1)
195 p = safe_int(request.GET.get('page', 1), 1)
196 c.user = User.get(self.authuser.user_id)
196 c.user = User.get(self.authuser.user_id)
197 c.following = self.sa.query(UserFollowing)\
197 c.following = self.sa.query(UserFollowing)\
198 .filter(UserFollowing.user_id == self.authuser.user_id)\
198 .filter(UserFollowing.user_id == self.authuser.user_id)\
199 .options(joinedload(UserFollowing.follows_repository))\
199 .options(joinedload(UserFollowing.follows_repository))\
200 .all()
200 .all()
201
201
202 journal = self._get_journal_data(c.following)
202 journal = self._get_journal_data(c.following)
203
203
204 def url_generator(**kw):
204 def url_generator(**kw):
205 return url.current(filter=c.search_term, **kw)
205 return url.current(filter=c.search_term, **kw)
206
206
207 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
207 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
208 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
208 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
209
209
210 if request.environ.get('HTTP_X_PARTIAL_XHR'):
210 if request.environ.get('HTTP_X_PARTIAL_XHR'):
211 return render('journal/journal_data.html')
211 return render('journal/journal_data.html')
212
212
213 repos_list = Session().query(Repository)\
213 repos_list = Session().query(Repository)\
214 .filter(Repository.user_id ==
214 .filter(Repository.user_id ==
215 self.authuser.user_id)\
215 self.authuser.user_id)\
216 .order_by(func.lower(Repository.repo_name)).all()
216 .order_by(func.lower(Repository.repo_name)).all()
217
217
218 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
218 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
219 admin=True)
219 admin=True)
220 #json used to render the grid
220 #json used to render the grid
221 c.data = json.dumps(repos_data)
221 c.data = json.dumps(repos_data)
222
222
223 watched_repos_data = []
223 watched_repos_data = []
224
224
225 ## watched repos
225 ## watched repos
226 _render = RepoModel._render_datatable
226 _render = RepoModel._render_datatable
227
227
228 def quick_menu(repo_name):
228 def quick_menu(repo_name):
229 return _render('quick_menu', repo_name)
229 return _render('quick_menu', repo_name)
230
230
231 def repo_lnk(name, rtype, rstate, private, fork_of):
231 def repo_lnk(name, rtype, rstate, private, fork_of):
232 return _render('repo_name', name, rtype, rstate, private, fork_of,
232 return _render('repo_name', name, rtype, rstate, private, fork_of,
233 short_name=False, admin=False)
233 short_name=False, admin=False)
234
234
235 def last_rev(repo_name, cs_cache):
235 def last_rev(repo_name, cs_cache):
236 return _render('revision', repo_name, cs_cache.get('revision'),
236 return _render('revision', repo_name, cs_cache.get('revision'),
237 cs_cache.get('raw_id'), cs_cache.get('author'),
237 cs_cache.get('raw_id'), cs_cache.get('author'),
238 cs_cache.get('message'))
238 cs_cache.get('message'))
239
239
240 def desc(desc):
240 def desc(desc):
241 from pylons import tmpl_context as c
241 from pylons import tmpl_context as c
242 if c.visual.stylify_metatags:
242 if c.visual.stylify_metatags:
243 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
243 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
244 else:
244 else:
245 return h.urlify_text(h.truncate(desc, 60))
245 return h.urlify_text(h.truncate(desc, 60))
246
246
247 def repo_actions(repo_name):
247 def repo_actions(repo_name):
248 return _render('repo_actions', repo_name)
248 return _render('repo_actions', repo_name)
249
249
250 def owner_actions(user_id, username):
250 def owner_actions(user_id, username):
251 return _render('user_name', user_id, username)
251 return _render('user_name', user_id, username)
252
252
253 def toogle_follow(repo_id):
253 def toogle_follow(repo_id):
254 return _render('toggle_follow', repo_id)
254 return _render('toggle_follow', repo_id)
255
255
256 for entry in c.following:
256 for entry in c.following:
257 repo = entry.follows_repository
257 repo = entry.follows_repository
258 cs_cache = repo.changeset_cache
258 cs_cache = repo.changeset_cache
259 row = {
259 row = {
260 "menu": quick_menu(repo.repo_name),
260 "menu": quick_menu(repo.repo_name),
261 "raw_name": repo.repo_name.lower(),
261 "raw_name": repo.repo_name.lower(),
262 "name": repo_lnk(repo.repo_name, repo.repo_type,
262 "name": repo_lnk(repo.repo_name, repo.repo_type,
263 repo.repo_state, repo.private, repo.fork),
263 repo.repo_state, repo.private, repo.fork),
264 "last_changeset": last_rev(repo.repo_name, cs_cache),
264 "last_changeset": last_rev(repo.repo_name, cs_cache),
265 "last_rev_raw": cs_cache.get('revision'),
265 "last_rev_raw": cs_cache.get('revision'),
266 "action": toogle_follow(repo.repo_id)
266 "action": toogle_follow(repo.repo_id)
267 }
267 }
268
268
269 watched_repos_data.append(row)
269 watched_repos_data.append(row)
270
270
271 c.watched_data = json.dumps({
271 c.watched_data = json.dumps({
272 "totalRecords": len(c.following),
272 "totalRecords": len(c.following),
273 "startIndex": 0,
273 "startIndex": 0,
274 "sort": "name",
274 "sort": "name",
275 "dir": "asc",
275 "dir": "asc",
276 "records": watched_repos_data
276 "records": watched_repos_data
277 })
277 })
278 return render('journal/journal.html')
278 return render('journal/journal.html')
279
279
280 @LoginRequired(api_access=True)
280 @LoginRequired(api_access=True)
281 @NotAnonymous()
281 @NotAnonymous()
282 def journal_atom(self):
282 def journal_atom(self):
283 """
283 """
284 Produce an atom-1.0 feed via feedgenerator module
284 Produce an atom-1.0 feed via feedgenerator module
285 """
285 """
286 following = self.sa.query(UserFollowing)\
286 following = self.sa.query(UserFollowing)\
287 .filter(UserFollowing.user_id == self.authuser.user_id)\
287 .filter(UserFollowing.user_id == self.authuser.user_id)\
288 .options(joinedload(UserFollowing.follows_repository))\
288 .options(joinedload(UserFollowing.follows_repository))\
289 .all()
289 .all()
290 return self._atom_feed(following, public=False)
290 return self._atom_feed(following, public=False)
291
291
292 @LoginRequired(api_access=True)
292 @LoginRequired(api_access=True)
293 @NotAnonymous()
293 @NotAnonymous()
294 def journal_rss(self):
294 def journal_rss(self):
295 """
295 """
296 Produce an rss feed via feedgenerator module
296 Produce an rss feed via feedgenerator module
297 """
297 """
298 following = self.sa.query(UserFollowing)\
298 following = self.sa.query(UserFollowing)\
299 .filter(UserFollowing.user_id == self.authuser.user_id)\
299 .filter(UserFollowing.user_id == self.authuser.user_id)\
300 .options(joinedload(UserFollowing.follows_repository))\
300 .options(joinedload(UserFollowing.follows_repository))\
301 .all()
301 .all()
302 return self._rss_feed(following, public=False)
302 return self._rss_feed(following, public=False)
303
303
304 @LoginRequired()
304 @LoginRequired()
305 @NotAnonymous()
305 @NotAnonymous()
306 def toggle_following(self):
306 def toggle_following(self):
307 cur_token = request.POST.get('auth_token')
307 user_id = request.POST.get('follows_user_id')
308 token = h.get_token()
308 if user_id:
309 if cur_token == token:
309 try:
310 self.scm_model.toggle_following_user(user_id,
311 self.authuser.user_id)
312 Session.commit()
313 return 'ok'
314 except Exception:
315 log.error(traceback.format_exc())
316 raise HTTPBadRequest()
310
317
311 user_id = request.POST.get('follows_user_id')
318 repo_id = request.POST.get('follows_repo_id')
312 if user_id:
319 if repo_id:
313 try:
320 try:
314 self.scm_model.toggle_following_user(user_id,
321 self.scm_model.toggle_following_repo(repo_id,
315 self.authuser.user_id)
322 self.authuser.user_id)
316 Session.commit()
323 Session.commit()
317 return 'ok'
324 return 'ok'
318 except Exception:
325 except Exception:
319 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
320 raise HTTPBadRequest()
327 raise HTTPBadRequest()
321
328
322 repo_id = request.POST.get('follows_repo_id')
323 if repo_id:
324 try:
325 self.scm_model.toggle_following_repo(repo_id,
326 self.authuser.user_id)
327 Session.commit()
328 return 'ok'
329 except Exception:
330 log.error(traceback.format_exc())
331 raise HTTPBadRequest()
332
333 log.debug('token mismatch %s vs %s' % (cur_token, token))
334 raise HTTPBadRequest()
329 raise HTTPBadRequest()
335
330
336 @LoginRequired()
331 @LoginRequired()
337 def public_journal(self):
332 def public_journal(self):
338 # Return a rendered template
333 # Return a rendered template
339 p = safe_int(request.GET.get('page', 1), 1)
334 p = safe_int(request.GET.get('page', 1), 1)
340
335
341 c.following = self.sa.query(UserFollowing)\
336 c.following = self.sa.query(UserFollowing)\
342 .filter(UserFollowing.user_id == self.authuser.user_id)\
337 .filter(UserFollowing.user_id == self.authuser.user_id)\
343 .options(joinedload(UserFollowing.follows_repository))\
338 .options(joinedload(UserFollowing.follows_repository))\
344 .all()
339 .all()
345
340
346 journal = self._get_journal_data(c.following)
341 journal = self._get_journal_data(c.following)
347
342
348 c.journal_pager = Page(journal, page=p, items_per_page=20)
343 c.journal_pager = Page(journal, page=p, items_per_page=20)
349
344
350 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
345 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
351
346
352 if request.environ.get('HTTP_X_PARTIAL_XHR'):
347 if request.environ.get('HTTP_X_PARTIAL_XHR'):
353 return render('journal/journal_data.html')
348 return render('journal/journal_data.html')
354
349
355 return render('journal/public_journal.html')
350 return render('journal/public_journal.html')
356
351
357 @LoginRequired(api_access=True)
352 @LoginRequired(api_access=True)
358 def public_journal_atom(self):
353 def public_journal_atom(self):
359 """
354 """
360 Produce an atom-1.0 feed via feedgenerator module
355 Produce an atom-1.0 feed via feedgenerator module
361 """
356 """
362 c.following = self.sa.query(UserFollowing)\
357 c.following = self.sa.query(UserFollowing)\
363 .filter(UserFollowing.user_id == self.authuser.user_id)\
358 .filter(UserFollowing.user_id == self.authuser.user_id)\
364 .options(joinedload(UserFollowing.follows_repository))\
359 .options(joinedload(UserFollowing.follows_repository))\
365 .all()
360 .all()
366
361
367 return self._atom_feed(c.following)
362 return self._atom_feed(c.following)
368
363
369 @LoginRequired(api_access=True)
364 @LoginRequired(api_access=True)
370 def public_journal_rss(self):
365 def public_journal_rss(self):
371 """
366 """
372 Produce an rss2 feed via feedgenerator module
367 Produce an rss2 feed via feedgenerator module
373 """
368 """
374 c.following = self.sa.query(UserFollowing)\
369 c.following = self.sa.query(UserFollowing)\
375 .filter(UserFollowing.user_id == self.authuser.user_id)\
370 .filter(UserFollowing.user_id == self.authuser.user_id)\
376 .options(joinedload(UserFollowing.follows_repository))\
371 .options(joinedload(UserFollowing.follows_repository))\
377 .all()
372 .all()
378
373
379 return self._rss_feed(c.following)
374 return self._rss_feed(c.following)
@@ -1,1469 +1,1452 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 Helper functions
15 Helper functions
16
16
17 Consists of functions to typically be used within templates, but also
17 Consists of functions to typically be used within templates, but also
18 available to Controllers. This module is available to both as 'h'.
18 available to Controllers. This module is available to both as 'h'.
19 """
19 """
20 import random
20 import random
21 import hashlib
21 import hashlib
22 import StringIO
22 import StringIO
23 import math
23 import math
24 import logging
24 import logging
25 import re
25 import re
26 import urlparse
26 import urlparse
27 import textwrap
27 import textwrap
28
28
29 from pygments.formatters.html import HtmlFormatter
29 from pygments.formatters.html import HtmlFormatter
30 from pygments import highlight as code_highlight
30 from pygments import highlight as code_highlight
31 from pylons import url
31 from pylons import url
32 from pylons.i18n.translation import _, ungettext
32 from pylons.i18n.translation import _, ungettext
33 from hashlib import md5
33 from hashlib import md5
34
34
35 from webhelpers.html import literal, HTML, escape
35 from webhelpers.html import literal, HTML, escape
36 from webhelpers.html.tools import *
36 from webhelpers.html.tools import *
37 from webhelpers.html.builder import make_tag
37 from webhelpers.html.builder import make_tag
38 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
38 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
39 end_form, file, form, hidden, image, javascript_link, link_to, \
39 end_form, file, form, hidden, image, javascript_link, link_to, \
40 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
40 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
41 submit, text, password, textarea, title, ul, xml_declaration, radio
41 submit, text, password, textarea, title, ul, xml_declaration, radio
42 from webhelpers.html.tools import auto_link, button_to, highlight, \
42 from webhelpers.html.tools import auto_link, button_to, highlight, \
43 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
43 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
44 from webhelpers.number import format_byte_size, format_bit_size
44 from webhelpers.number import format_byte_size, format_bit_size
45 from webhelpers.pylonslib import Flash as _Flash
45 from webhelpers.pylonslib import Flash as _Flash
46 from webhelpers.pylonslib.secure_form import secure_form
46 from webhelpers.pylonslib.secure_form import secure_form
47 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
47 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
48 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
48 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
49 replace_whitespace, urlify, truncate, wrap_paragraphs
49 replace_whitespace, urlify, truncate, wrap_paragraphs
50 from webhelpers.date import time_ago_in_words
50 from webhelpers.date import time_ago_in_words
51 from webhelpers.paginate import Page as _Page
51 from webhelpers.paginate import Page as _Page
52 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
52 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
53 convert_boolean_attrs, NotGiven, _make_safe_id_component
53 convert_boolean_attrs, NotGiven, _make_safe_id_component
54
54
55 from kallithea.lib.annotate import annotate_highlight
55 from kallithea.lib.annotate import annotate_highlight
56 from kallithea.lib.utils import repo_name_slug, get_custom_lexer
56 from kallithea.lib.utils import repo_name_slug, get_custom_lexer
57 from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str, \
57 from kallithea.lib.utils2 import str2bool, safe_unicode, safe_str, \
58 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
58 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
59 safe_int
59 safe_int
60 from kallithea.lib.markup_renderer import MarkupRenderer, url_re
60 from kallithea.lib.markup_renderer import MarkupRenderer, url_re
61 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
61 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
62 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
62 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
63 from kallithea.config.conf import DATE_FORMAT, DATETIME_FORMAT
63 from kallithea.config.conf import DATE_FORMAT, DATETIME_FORMAT
64 from kallithea.model.changeset_status import ChangesetStatusModel
64 from kallithea.model.changeset_status import ChangesetStatusModel
65 from kallithea.model.db import URL_SEP, Permission
65 from kallithea.model.db import URL_SEP, Permission
66
66
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69
69
70 def canonical_url(*args, **kargs):
70 def canonical_url(*args, **kargs):
71 '''Like url(x, qualified=True), but returns url that not only is qualified
71 '''Like url(x, qualified=True), but returns url that not only is qualified
72 but also canonical, as configured in canonical_url'''
72 but also canonical, as configured in canonical_url'''
73 from kallithea import CONFIG
73 from kallithea import CONFIG
74 try:
74 try:
75 parts = CONFIG.get('canonical_url', '').split('://', 1)
75 parts = CONFIG.get('canonical_url', '').split('://', 1)
76 kargs['host'] = parts[1].split('/', 1)[0]
76 kargs['host'] = parts[1].split('/', 1)[0]
77 kargs['protocol'] = parts[0]
77 kargs['protocol'] = parts[0]
78 except IndexError:
78 except IndexError:
79 kargs['qualified'] = True
79 kargs['qualified'] = True
80 return url(*args, **kargs)
80 return url(*args, **kargs)
81
81
82 def canonical_hostname():
82 def canonical_hostname():
83 '''Return canonical hostname of system'''
83 '''Return canonical hostname of system'''
84 from kallithea import CONFIG
84 from kallithea import CONFIG
85 try:
85 try:
86 parts = CONFIG.get('canonical_url', '').split('://', 1)
86 parts = CONFIG.get('canonical_url', '').split('://', 1)
87 return parts[1].split('/', 1)[0]
87 return parts[1].split('/', 1)[0]
88 except IndexError:
88 except IndexError:
89 parts = url('home', qualified=True).split('://', 1)
89 parts = url('home', qualified=True).split('://', 1)
90 return parts[1].split('/', 1)[0]
90 return parts[1].split('/', 1)[0]
91
91
92 def html_escape(text, html_escape_table=None):
92 def html_escape(text, html_escape_table=None):
93 """Produce entities within text."""
93 """Produce entities within text."""
94 if not html_escape_table:
94 if not html_escape_table:
95 html_escape_table = {
95 html_escape_table = {
96 "&": "&amp;",
96 "&": "&amp;",
97 '"': "&quot;",
97 '"': "&quot;",
98 "'": "&apos;",
98 "'": "&apos;",
99 ">": "&gt;",
99 ">": "&gt;",
100 "<": "&lt;",
100 "<": "&lt;",
101 }
101 }
102 return "".join(html_escape_table.get(c, c) for c in text)
102 return "".join(html_escape_table.get(c, c) for c in text)
103
103
104
104
105 def shorter(text, size=20):
105 def shorter(text, size=20):
106 postfix = '...'
106 postfix = '...'
107 if len(text) > size:
107 if len(text) > size:
108 return text[:size - len(postfix)] + postfix
108 return text[:size - len(postfix)] + postfix
109 return text
109 return text
110
110
111
111
112 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
112 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
113 """
113 """
114 Reset button
114 Reset button
115 """
115 """
116 _set_input_attrs(attrs, type, name, value)
116 _set_input_attrs(attrs, type, name, value)
117 _set_id_attr(attrs, id, name)
117 _set_id_attr(attrs, id, name)
118 convert_boolean_attrs(attrs, ["disabled"])
118 convert_boolean_attrs(attrs, ["disabled"])
119 return HTML.input(**attrs)
119 return HTML.input(**attrs)
120
120
121 reset = _reset
121 reset = _reset
122 safeid = _make_safe_id_component
122 safeid = _make_safe_id_component
123
123
124
124
125 def FID(raw_id, path):
125 def FID(raw_id, path):
126 """
126 """
127 Creates a unique ID for filenode based on it's hash of path and revision
127 Creates a unique ID for filenode based on it's hash of path and revision
128 it's safe to use in urls
128 it's safe to use in urls
129
129
130 :param raw_id:
130 :param raw_id:
131 :param path:
131 :param path:
132 """
132 """
133
133
134 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
134 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
135
135
136
136
137 def get_token():
138 """Return the current authentication token, creating one if one doesn't
139 already exist.
140 """
141 token_key = "_authentication_token"
142 from pylons import session
143 if not token_key in session:
144 try:
145 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
146 except AttributeError: # Python < 2.4
147 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
148 session[token_key] = token
149 if hasattr(session, 'save'):
150 session.save()
151 return session[token_key]
152
153
154 class _GetError(object):
137 class _GetError(object):
155 """Get error from form_errors, and represent it as span wrapped error
138 """Get error from form_errors, and represent it as span wrapped error
156 message
139 message
157
140
158 :param field_name: field to fetch errors for
141 :param field_name: field to fetch errors for
159 :param form_errors: form errors dict
142 :param form_errors: form errors dict
160 """
143 """
161
144
162 def __call__(self, field_name, form_errors):
145 def __call__(self, field_name, form_errors):
163 tmpl = """<span class="error_msg">%s</span>"""
146 tmpl = """<span class="error_msg">%s</span>"""
164 if form_errors and field_name in form_errors:
147 if form_errors and field_name in form_errors:
165 return literal(tmpl % form_errors.get(field_name))
148 return literal(tmpl % form_errors.get(field_name))
166
149
167 get_error = _GetError()
150 get_error = _GetError()
168
151
169
152
170 class _ToolTip(object):
153 class _ToolTip(object):
171
154
172 def __call__(self, tooltip_title, trim_at=50):
155 def __call__(self, tooltip_title, trim_at=50):
173 """
156 """
174 Special function just to wrap our text into nice formatted
157 Special function just to wrap our text into nice formatted
175 autowrapped text
158 autowrapped text
176
159
177 :param tooltip_title:
160 :param tooltip_title:
178 """
161 """
179 tooltip_title = escape(tooltip_title)
162 tooltip_title = escape(tooltip_title)
180 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
163 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
181 return tooltip_title
164 return tooltip_title
182 tooltip = _ToolTip()
165 tooltip = _ToolTip()
183
166
184
167
185 class _FilesBreadCrumbs(object):
168 class _FilesBreadCrumbs(object):
186
169
187 def __call__(self, repo_name, rev, paths):
170 def __call__(self, repo_name, rev, paths):
188 if isinstance(paths, str):
171 if isinstance(paths, str):
189 paths = safe_unicode(paths)
172 paths = safe_unicode(paths)
190 url_l = [link_to(repo_name, url('files_home',
173 url_l = [link_to(repo_name, url('files_home',
191 repo_name=repo_name,
174 repo_name=repo_name,
192 revision=rev, f_path=''),
175 revision=rev, f_path=''),
193 class_='ypjax-link')]
176 class_='ypjax-link')]
194 paths_l = paths.split('/')
177 paths_l = paths.split('/')
195 for cnt, p in enumerate(paths_l):
178 for cnt, p in enumerate(paths_l):
196 if p != '':
179 if p != '':
197 url_l.append(link_to(p,
180 url_l.append(link_to(p,
198 url('files_home',
181 url('files_home',
199 repo_name=repo_name,
182 repo_name=repo_name,
200 revision=rev,
183 revision=rev,
201 f_path='/'.join(paths_l[:cnt + 1])
184 f_path='/'.join(paths_l[:cnt + 1])
202 ),
185 ),
203 class_='ypjax-link'
186 class_='ypjax-link'
204 )
187 )
205 )
188 )
206
189
207 return literal('/'.join(url_l))
190 return literal('/'.join(url_l))
208
191
209 files_breadcrumbs = _FilesBreadCrumbs()
192 files_breadcrumbs = _FilesBreadCrumbs()
210
193
211
194
212 class CodeHtmlFormatter(HtmlFormatter):
195 class CodeHtmlFormatter(HtmlFormatter):
213 """
196 """
214 My code Html Formatter for source codes
197 My code Html Formatter for source codes
215 """
198 """
216
199
217 def wrap(self, source, outfile):
200 def wrap(self, source, outfile):
218 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
201 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
219
202
220 def _wrap_code(self, source):
203 def _wrap_code(self, source):
221 for cnt, it in enumerate(source):
204 for cnt, it in enumerate(source):
222 i, t = it
205 i, t = it
223 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
206 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
224 yield i, t
207 yield i, t
225
208
226 def _wrap_tablelinenos(self, inner):
209 def _wrap_tablelinenos(self, inner):
227 dummyoutfile = StringIO.StringIO()
210 dummyoutfile = StringIO.StringIO()
228 lncount = 0
211 lncount = 0
229 for t, line in inner:
212 for t, line in inner:
230 if t:
213 if t:
231 lncount += 1
214 lncount += 1
232 dummyoutfile.write(line)
215 dummyoutfile.write(line)
233
216
234 fl = self.linenostart
217 fl = self.linenostart
235 mw = len(str(lncount + fl - 1))
218 mw = len(str(lncount + fl - 1))
236 sp = self.linenospecial
219 sp = self.linenospecial
237 st = self.linenostep
220 st = self.linenostep
238 la = self.lineanchors
221 la = self.lineanchors
239 aln = self.anchorlinenos
222 aln = self.anchorlinenos
240 nocls = self.noclasses
223 nocls = self.noclasses
241 if sp:
224 if sp:
242 lines = []
225 lines = []
243
226
244 for i in range(fl, fl + lncount):
227 for i in range(fl, fl + lncount):
245 if i % st == 0:
228 if i % st == 0:
246 if i % sp == 0:
229 if i % sp == 0:
247 if aln:
230 if aln:
248 lines.append('<a href="#%s%d" class="special">%*d</a>' %
231 lines.append('<a href="#%s%d" class="special">%*d</a>' %
249 (la, i, mw, i))
232 (la, i, mw, i))
250 else:
233 else:
251 lines.append('<span class="special">%*d</span>' % (mw, i))
234 lines.append('<span class="special">%*d</span>' % (mw, i))
252 else:
235 else:
253 if aln:
236 if aln:
254 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
237 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
255 else:
238 else:
256 lines.append('%*d' % (mw, i))
239 lines.append('%*d' % (mw, i))
257 else:
240 else:
258 lines.append('')
241 lines.append('')
259 ls = '\n'.join(lines)
242 ls = '\n'.join(lines)
260 else:
243 else:
261 lines = []
244 lines = []
262 for i in range(fl, fl + lncount):
245 for i in range(fl, fl + lncount):
263 if i % st == 0:
246 if i % st == 0:
264 if aln:
247 if aln:
265 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
248 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
266 else:
249 else:
267 lines.append('%*d' % (mw, i))
250 lines.append('%*d' % (mw, i))
268 else:
251 else:
269 lines.append('')
252 lines.append('')
270 ls = '\n'.join(lines)
253 ls = '\n'.join(lines)
271
254
272 # in case you wonder about the seemingly redundant <div> here: since the
255 # in case you wonder about the seemingly redundant <div> here: since the
273 # content in the other cell also is wrapped in a div, some browsers in
256 # content in the other cell also is wrapped in a div, some browsers in
274 # some configurations seem to mess up the formatting...
257 # some configurations seem to mess up the formatting...
275 if nocls:
258 if nocls:
276 yield 0, ('<table class="%stable">' % self.cssclass +
259 yield 0, ('<table class="%stable">' % self.cssclass +
277 '<tr><td><div class="linenodiv" '
260 '<tr><td><div class="linenodiv" '
278 'style="background-color: #f0f0f0; padding-right: 10px">'
261 'style="background-color: #f0f0f0; padding-right: 10px">'
279 '<pre style="line-height: 125%">' +
262 '<pre style="line-height: 125%">' +
280 ls + '</pre></div></td><td id="hlcode" class="code">')
263 ls + '</pre></div></td><td id="hlcode" class="code">')
281 else:
264 else:
282 yield 0, ('<table class="%stable">' % self.cssclass +
265 yield 0, ('<table class="%stable">' % self.cssclass +
283 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
266 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
284 ls + '</pre></div></td><td id="hlcode" class="code">')
267 ls + '</pre></div></td><td id="hlcode" class="code">')
285 yield 0, dummyoutfile.getvalue()
268 yield 0, dummyoutfile.getvalue()
286 yield 0, '</td></tr></table>'
269 yield 0, '</td></tr></table>'
287
270
288
271
289 _whitespace_re = re.compile(r'(\t)|( )(?=\n|</div>)')
272 _whitespace_re = re.compile(r'(\t)|( )(?=\n|</div>)')
290
273
291 def _markup_whitespace(m):
274 def _markup_whitespace(m):
292 groups = m.groups()
275 groups = m.groups()
293 if groups[0]:
276 if groups[0]:
294 return '<u>\t</u>'
277 return '<u>\t</u>'
295 if groups[1]:
278 if groups[1]:
296 return ' <i></i>'
279 return ' <i></i>'
297
280
298 def markup_whitespace(s):
281 def markup_whitespace(s):
299 return _whitespace_re.sub(_markup_whitespace, s)
282 return _whitespace_re.sub(_markup_whitespace, s)
300
283
301 def pygmentize(filenode, **kwargs):
284 def pygmentize(filenode, **kwargs):
302 """
285 """
303 pygmentize function using pygments
286 pygmentize function using pygments
304
287
305 :param filenode:
288 :param filenode:
306 """
289 """
307 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
290 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
308 return literal(markup_whitespace(
291 return literal(markup_whitespace(
309 code_highlight(filenode.content, lexer, CodeHtmlFormatter(**kwargs))))
292 code_highlight(filenode.content, lexer, CodeHtmlFormatter(**kwargs))))
310
293
311
294
312 def pygmentize_annotation(repo_name, filenode, **kwargs):
295 def pygmentize_annotation(repo_name, filenode, **kwargs):
313 """
296 """
314 pygmentize function for annotation
297 pygmentize function for annotation
315
298
316 :param filenode:
299 :param filenode:
317 """
300 """
318
301
319 color_dict = {}
302 color_dict = {}
320
303
321 def gen_color(n=10000):
304 def gen_color(n=10000):
322 """generator for getting n of evenly distributed colors using
305 """generator for getting n of evenly distributed colors using
323 hsv color and golden ratio. It always return same order of colors
306 hsv color and golden ratio. It always return same order of colors
324
307
325 :returns: RGB tuple
308 :returns: RGB tuple
326 """
309 """
327
310
328 def hsv_to_rgb(h, s, v):
311 def hsv_to_rgb(h, s, v):
329 if s == 0.0:
312 if s == 0.0:
330 return v, v, v
313 return v, v, v
331 i = int(h * 6.0) # XXX assume int() truncates!
314 i = int(h * 6.0) # XXX assume int() truncates!
332 f = (h * 6.0) - i
315 f = (h * 6.0) - i
333 p = v * (1.0 - s)
316 p = v * (1.0 - s)
334 q = v * (1.0 - s * f)
317 q = v * (1.0 - s * f)
335 t = v * (1.0 - s * (1.0 - f))
318 t = v * (1.0 - s * (1.0 - f))
336 i = i % 6
319 i = i % 6
337 if i == 0:
320 if i == 0:
338 return v, t, p
321 return v, t, p
339 if i == 1:
322 if i == 1:
340 return q, v, p
323 return q, v, p
341 if i == 2:
324 if i == 2:
342 return p, v, t
325 return p, v, t
343 if i == 3:
326 if i == 3:
344 return p, q, v
327 return p, q, v
345 if i == 4:
328 if i == 4:
346 return t, p, v
329 return t, p, v
347 if i == 5:
330 if i == 5:
348 return v, p, q
331 return v, p, q
349
332
350 golden_ratio = 0.618033988749895
333 golden_ratio = 0.618033988749895
351 h = 0.22717784590367374
334 h = 0.22717784590367374
352
335
353 for _unused in xrange(n):
336 for _unused in xrange(n):
354 h += golden_ratio
337 h += golden_ratio
355 h %= 1
338 h %= 1
356 HSV_tuple = [h, 0.95, 0.95]
339 HSV_tuple = [h, 0.95, 0.95]
357 RGB_tuple = hsv_to_rgb(*HSV_tuple)
340 RGB_tuple = hsv_to_rgb(*HSV_tuple)
358 yield map(lambda x: str(int(x * 256)), RGB_tuple)
341 yield map(lambda x: str(int(x * 256)), RGB_tuple)
359
342
360 cgenerator = gen_color()
343 cgenerator = gen_color()
361
344
362 def get_color_string(cs):
345 def get_color_string(cs):
363 if cs in color_dict:
346 if cs in color_dict:
364 col = color_dict[cs]
347 col = color_dict[cs]
365 else:
348 else:
366 col = color_dict[cs] = cgenerator.next()
349 col = color_dict[cs] = cgenerator.next()
367 return "color: rgb(%s)! important;" % (', '.join(col))
350 return "color: rgb(%s)! important;" % (', '.join(col))
368
351
369 def url_func(repo_name):
352 def url_func(repo_name):
370
353
371 def _url_func(changeset):
354 def _url_func(changeset):
372 author = changeset.author
355 author = changeset.author
373 date = changeset.date
356 date = changeset.date
374 message = tooltip(changeset.message)
357 message = tooltip(changeset.message)
375
358
376 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
359 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
377 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
360 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
378 "</b> %s<br/></div>")
361 "</b> %s<br/></div>")
379
362
380 tooltip_html = tooltip_html % (author, date, message)
363 tooltip_html = tooltip_html % (author, date, message)
381 lnk_format = show_id(changeset)
364 lnk_format = show_id(changeset)
382 uri = link_to(
365 uri = link_to(
383 lnk_format,
366 lnk_format,
384 url('changeset_home', repo_name=repo_name,
367 url('changeset_home', repo_name=repo_name,
385 revision=changeset.raw_id),
368 revision=changeset.raw_id),
386 style=get_color_string(changeset.raw_id),
369 style=get_color_string(changeset.raw_id),
387 class_='tooltip',
370 class_='tooltip',
388 title=tooltip_html
371 title=tooltip_html
389 )
372 )
390
373
391 uri += '\n'
374 uri += '\n'
392 return uri
375 return uri
393 return _url_func
376 return _url_func
394
377
395 return literal(markup_whitespace(annotate_highlight(filenode, url_func(repo_name), **kwargs)))
378 return literal(markup_whitespace(annotate_highlight(filenode, url_func(repo_name), **kwargs)))
396
379
397
380
398 def is_following_repo(repo_name, user_id):
381 def is_following_repo(repo_name, user_id):
399 from kallithea.model.scm import ScmModel
382 from kallithea.model.scm import ScmModel
400 return ScmModel().is_following_repo(repo_name, user_id)
383 return ScmModel().is_following_repo(repo_name, user_id)
401
384
402 class _Message(object):
385 class _Message(object):
403 """A message returned by ``Flash.pop_messages()``.
386 """A message returned by ``Flash.pop_messages()``.
404
387
405 Converting the message to a string returns the message text. Instances
388 Converting the message to a string returns the message text. Instances
406 also have the following attributes:
389 also have the following attributes:
407
390
408 * ``message``: the message text.
391 * ``message``: the message text.
409 * ``category``: the category specified when the message was created.
392 * ``category``: the category specified when the message was created.
410 """
393 """
411
394
412 def __init__(self, category, message):
395 def __init__(self, category, message):
413 self.category = category
396 self.category = category
414 self.message = message
397 self.message = message
415
398
416 def __str__(self):
399 def __str__(self):
417 return self.message
400 return self.message
418
401
419 __unicode__ = __str__
402 __unicode__ = __str__
420
403
421 def __html__(self):
404 def __html__(self):
422 return escape(safe_unicode(self.message))
405 return escape(safe_unicode(self.message))
423
406
424 class Flash(_Flash):
407 class Flash(_Flash):
425
408
426 def pop_messages(self):
409 def pop_messages(self):
427 """Return all accumulated messages and delete them from the session.
410 """Return all accumulated messages and delete them from the session.
428
411
429 The return value is a list of ``Message`` objects.
412 The return value is a list of ``Message`` objects.
430 """
413 """
431 from pylons import session
414 from pylons import session
432 messages = session.pop(self.session_key, [])
415 messages = session.pop(self.session_key, [])
433 session.save()
416 session.save()
434 return [_Message(*m) for m in messages]
417 return [_Message(*m) for m in messages]
435
418
436 flash = Flash()
419 flash = Flash()
437
420
438 #==============================================================================
421 #==============================================================================
439 # SCM FILTERS available via h.
422 # SCM FILTERS available via h.
440 #==============================================================================
423 #==============================================================================
441 from kallithea.lib.vcs.utils import author_name, author_email
424 from kallithea.lib.vcs.utils import author_name, author_email
442 from kallithea.lib.utils2 import credentials_filter, age as _age
425 from kallithea.lib.utils2 import credentials_filter, age as _age
443 from kallithea.model.db import User, ChangesetStatus
426 from kallithea.model.db import User, ChangesetStatus
444
427
445 age = lambda x, y=False: _age(x, y)
428 age = lambda x, y=False: _age(x, y)
446 capitalize = lambda x: x.capitalize()
429 capitalize = lambda x: x.capitalize()
447 email = author_email
430 email = author_email
448 short_id = lambda x: x[:12]
431 short_id = lambda x: x[:12]
449 hide_credentials = lambda x: ''.join(credentials_filter(x))
432 hide_credentials = lambda x: ''.join(credentials_filter(x))
450
433
451
434
452 def show_id(cs):
435 def show_id(cs):
453 """
436 """
454 Configurable function that shows ID
437 Configurable function that shows ID
455 by default it's r123:fffeeefffeee
438 by default it's r123:fffeeefffeee
456
439
457 :param cs: changeset instance
440 :param cs: changeset instance
458 """
441 """
459 from kallithea import CONFIG
442 from kallithea import CONFIG
460 def_len = safe_int(CONFIG.get('show_sha_length', 12))
443 def_len = safe_int(CONFIG.get('show_sha_length', 12))
461 show_rev = str2bool(CONFIG.get('show_revision_number', False))
444 show_rev = str2bool(CONFIG.get('show_revision_number', False))
462
445
463 raw_id = cs.raw_id[:def_len]
446 raw_id = cs.raw_id[:def_len]
464 if show_rev:
447 if show_rev:
465 return 'r%s:%s' % (cs.revision, raw_id)
448 return 'r%s:%s' % (cs.revision, raw_id)
466 else:
449 else:
467 return '%s' % (raw_id)
450 return '%s' % (raw_id)
468
451
469
452
470 def fmt_date(date):
453 def fmt_date(date):
471 if date:
454 if date:
472 return date.strftime("%Y-%m-%d %H:%M:%S").decode('utf8')
455 return date.strftime("%Y-%m-%d %H:%M:%S").decode('utf8')
473
456
474 return ""
457 return ""
475
458
476
459
477 def is_git(repository):
460 def is_git(repository):
478 if hasattr(repository, 'alias'):
461 if hasattr(repository, 'alias'):
479 _type = repository.alias
462 _type = repository.alias
480 elif hasattr(repository, 'repo_type'):
463 elif hasattr(repository, 'repo_type'):
481 _type = repository.repo_type
464 _type = repository.repo_type
482 else:
465 else:
483 _type = repository
466 _type = repository
484 return _type == 'git'
467 return _type == 'git'
485
468
486
469
487 def is_hg(repository):
470 def is_hg(repository):
488 if hasattr(repository, 'alias'):
471 if hasattr(repository, 'alias'):
489 _type = repository.alias
472 _type = repository.alias
490 elif hasattr(repository, 'repo_type'):
473 elif hasattr(repository, 'repo_type'):
491 _type = repository.repo_type
474 _type = repository.repo_type
492 else:
475 else:
493 _type = repository
476 _type = repository
494 return _type == 'hg'
477 return _type == 'hg'
495
478
496
479
497 def user_or_none(author):
480 def user_or_none(author):
498 email = author_email(author)
481 email = author_email(author)
499 if email is not None:
482 if email is not None:
500 user = User.get_by_email(email, case_insensitive=True, cache=True)
483 user = User.get_by_email(email, case_insensitive=True, cache=True)
501 if user is not None:
484 if user is not None:
502 return user
485 return user
503
486
504 user = User.get_by_username(author_name(author), case_insensitive=True, cache=True)
487 user = User.get_by_username(author_name(author), case_insensitive=True, cache=True)
505 if user is not None:
488 if user is not None:
506 return user
489 return user
507
490
508 return None
491 return None
509
492
510 def email_or_none(author):
493 def email_or_none(author):
511 if not author:
494 if not author:
512 return None
495 return None
513 user = user_or_none(author)
496 user = user_or_none(author)
514 if user is not None:
497 if user is not None:
515 return user.email # always use main email address - not necessarily the one used to find user
498 return user.email # always use main email address - not necessarily the one used to find user
516
499
517 # extract email from the commit string
500 # extract email from the commit string
518 email = author_email(author)
501 email = author_email(author)
519 if email:
502 if email:
520 return email
503 return email
521
504
522 # No valid email, not a valid user in the system, none!
505 # No valid email, not a valid user in the system, none!
523 return None
506 return None
524
507
525 def person(author, show_attr="username"):
508 def person(author, show_attr="username"):
526 """Find the user identified by 'author', return one of the users attributes,
509 """Find the user identified by 'author', return one of the users attributes,
527 default to the username attribute, None if there is no user"""
510 default to the username attribute, None if there is no user"""
528 # attr to return from fetched user
511 # attr to return from fetched user
529 person_getter = lambda usr: getattr(usr, show_attr)
512 person_getter = lambda usr: getattr(usr, show_attr)
530
513
531 # if author is already an instance use it for extraction
514 # if author is already an instance use it for extraction
532 if isinstance(author, User):
515 if isinstance(author, User):
533 return person_getter(author)
516 return person_getter(author)
534
517
535 user = user_or_none(author)
518 user = user_or_none(author)
536 if user is not None:
519 if user is not None:
537 return person_getter(user)
520 return person_getter(user)
538
521
539 # Still nothing? Just pass back the author name if any, else the email
522 # Still nothing? Just pass back the author name if any, else the email
540 return author_name(author) or email(author)
523 return author_name(author) or email(author)
541
524
542
525
543 def person_by_id(id_, show_attr="username"):
526 def person_by_id(id_, show_attr="username"):
544 # attr to return from fetched user
527 # attr to return from fetched user
545 person_getter = lambda usr: getattr(usr, show_attr)
528 person_getter = lambda usr: getattr(usr, show_attr)
546
529
547 #maybe it's an ID ?
530 #maybe it's an ID ?
548 if str(id_).isdigit() or isinstance(id_, int):
531 if str(id_).isdigit() or isinstance(id_, int):
549 id_ = int(id_)
532 id_ = int(id_)
550 user = User.get(id_)
533 user = User.get(id_)
551 if user is not None:
534 if user is not None:
552 return person_getter(user)
535 return person_getter(user)
553 return id_
536 return id_
554
537
555
538
556 def desc_stylize(value):
539 def desc_stylize(value):
557 """
540 """
558 converts tags from value into html equivalent
541 converts tags from value into html equivalent
559
542
560 :param value:
543 :param value:
561 """
544 """
562 if not value:
545 if not value:
563 return ''
546 return ''
564
547
565 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
548 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
566 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
549 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
567 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
550 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
568 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
551 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
569 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
552 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
570 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
553 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
571 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
554 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
572 '<div class="metatag" tag="lang">\\2</div>', value)
555 '<div class="metatag" tag="lang">\\2</div>', value)
573 value = re.sub(r'\[([a-z]+)\]',
556 value = re.sub(r'\[([a-z]+)\]',
574 '<div class="metatag" tag="\\1">\\1</div>', value)
557 '<div class="metatag" tag="\\1">\\1</div>', value)
575
558
576 return value
559 return value
577
560
578
561
579 def boolicon(value):
562 def boolicon(value):
580 """Returns boolean value of a value, represented as small html image of true/false
563 """Returns boolean value of a value, represented as small html image of true/false
581 icons
564 icons
582
565
583 :param value: value
566 :param value: value
584 """
567 """
585
568
586 if value:
569 if value:
587 return HTML.tag('i', class_="icon-ok")
570 return HTML.tag('i', class_="icon-ok")
588 else:
571 else:
589 return HTML.tag('i', class_="icon-minus-circled")
572 return HTML.tag('i', class_="icon-minus-circled")
590
573
591
574
592 def action_parser(user_log, feed=False, parse_cs=False):
575 def action_parser(user_log, feed=False, parse_cs=False):
593 """
576 """
594 This helper will action_map the specified string action into translated
577 This helper will action_map the specified string action into translated
595 fancy names with icons and links
578 fancy names with icons and links
596
579
597 :param user_log: user log instance
580 :param user_log: user log instance
598 :param feed: use output for feeds (no html and fancy icons)
581 :param feed: use output for feeds (no html and fancy icons)
599 :param parse_cs: parse Changesets into VCS instances
582 :param parse_cs: parse Changesets into VCS instances
600 """
583 """
601
584
602 action = user_log.action
585 action = user_log.action
603 action_params = ' '
586 action_params = ' '
604
587
605 x = action.split(':')
588 x = action.split(':')
606
589
607 if len(x) > 1:
590 if len(x) > 1:
608 action, action_params = x
591 action, action_params = x
609
592
610 def get_cs_links():
593 def get_cs_links():
611 revs_limit = 3 # display this amount always
594 revs_limit = 3 # display this amount always
612 revs_top_limit = 50 # show upto this amount of changesets hidden
595 revs_top_limit = 50 # show upto this amount of changesets hidden
613 revs_ids = action_params.split(',')
596 revs_ids = action_params.split(',')
614 deleted = user_log.repository is None
597 deleted = user_log.repository is None
615 if deleted:
598 if deleted:
616 return ','.join(revs_ids)
599 return ','.join(revs_ids)
617
600
618 repo_name = user_log.repository.repo_name
601 repo_name = user_log.repository.repo_name
619
602
620 def lnk(rev, repo_name):
603 def lnk(rev, repo_name):
621 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
604 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
622 lazy_cs = True
605 lazy_cs = True
623 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
606 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
624 lazy_cs = False
607 lazy_cs = False
625 lbl = '?'
608 lbl = '?'
626 if rev.op == 'delete_branch':
609 if rev.op == 'delete_branch':
627 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
610 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
628 title = ''
611 title = ''
629 elif rev.op == 'tag':
612 elif rev.op == 'tag':
630 lbl = '%s' % _('Created tag: %s') % rev.ref_name
613 lbl = '%s' % _('Created tag: %s') % rev.ref_name
631 title = ''
614 title = ''
632 _url = '#'
615 _url = '#'
633
616
634 else:
617 else:
635 lbl = '%s' % (rev.short_id[:8])
618 lbl = '%s' % (rev.short_id[:8])
636 _url = url('changeset_home', repo_name=repo_name,
619 _url = url('changeset_home', repo_name=repo_name,
637 revision=rev.raw_id)
620 revision=rev.raw_id)
638 title = tooltip(rev.message)
621 title = tooltip(rev.message)
639 else:
622 else:
640 ## changeset cannot be found/striped/removed etc.
623 ## changeset cannot be found/striped/removed etc.
641 lbl = ('%s' % rev)[:12]
624 lbl = ('%s' % rev)[:12]
642 _url = '#'
625 _url = '#'
643 title = _('Changeset not found')
626 title = _('Changeset not found')
644 if parse_cs:
627 if parse_cs:
645 return link_to(lbl, _url, title=title, class_='tooltip')
628 return link_to(lbl, _url, title=title, class_='tooltip')
646 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
629 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
647 class_='lazy-cs' if lazy_cs else '')
630 class_='lazy-cs' if lazy_cs else '')
648
631
649 def _get_op(rev_txt):
632 def _get_op(rev_txt):
650 _op = None
633 _op = None
651 _name = rev_txt
634 _name = rev_txt
652 if len(rev_txt.split('=>')) == 2:
635 if len(rev_txt.split('=>')) == 2:
653 _op, _name = rev_txt.split('=>')
636 _op, _name = rev_txt.split('=>')
654 return _op, _name
637 return _op, _name
655
638
656 revs = []
639 revs = []
657 if len(filter(lambda v: v != '', revs_ids)) > 0:
640 if len(filter(lambda v: v != '', revs_ids)) > 0:
658 repo = None
641 repo = None
659 for rev in revs_ids[:revs_top_limit]:
642 for rev in revs_ids[:revs_top_limit]:
660 _op, _name = _get_op(rev)
643 _op, _name = _get_op(rev)
661
644
662 # we want parsed changesets, or new log store format is bad
645 # we want parsed changesets, or new log store format is bad
663 if parse_cs:
646 if parse_cs:
664 try:
647 try:
665 if repo is None:
648 if repo is None:
666 repo = user_log.repository.scm_instance
649 repo = user_log.repository.scm_instance
667 _rev = repo.get_changeset(rev)
650 _rev = repo.get_changeset(rev)
668 revs.append(_rev)
651 revs.append(_rev)
669 except ChangesetDoesNotExistError:
652 except ChangesetDoesNotExistError:
670 log.error('cannot find revision %s in this repo' % rev)
653 log.error('cannot find revision %s in this repo' % rev)
671 revs.append(rev)
654 revs.append(rev)
672 continue
655 continue
673 else:
656 else:
674 _rev = AttributeDict({
657 _rev = AttributeDict({
675 'short_id': rev[:12],
658 'short_id': rev[:12],
676 'raw_id': rev,
659 'raw_id': rev,
677 'message': '',
660 'message': '',
678 'op': _op,
661 'op': _op,
679 'ref_name': _name
662 'ref_name': _name
680 })
663 })
681 revs.append(_rev)
664 revs.append(_rev)
682 cs_links = [" " + ', '.join(
665 cs_links = [" " + ', '.join(
683 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
666 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
684 )]
667 )]
685 _op1, _name1 = _get_op(revs_ids[0])
668 _op1, _name1 = _get_op(revs_ids[0])
686 _op2, _name2 = _get_op(revs_ids[-1])
669 _op2, _name2 = _get_op(revs_ids[-1])
687
670
688 _rev = '%s...%s' % (_name1, _name2)
671 _rev = '%s...%s' % (_name1, _name2)
689
672
690 compare_view = (
673 compare_view = (
691 ' <div class="compare_view tooltip" title="%s">'
674 ' <div class="compare_view tooltip" title="%s">'
692 '<a href="%s">%s</a> </div>' % (
675 '<a href="%s">%s</a> </div>' % (
693 _('Show all combined changesets %s->%s') % (
676 _('Show all combined changesets %s->%s') % (
694 revs_ids[0][:12], revs_ids[-1][:12]
677 revs_ids[0][:12], revs_ids[-1][:12]
695 ),
678 ),
696 url('changeset_home', repo_name=repo_name,
679 url('changeset_home', repo_name=repo_name,
697 revision=_rev
680 revision=_rev
698 ),
681 ),
699 _('compare view')
682 _('compare view')
700 )
683 )
701 )
684 )
702
685
703 # if we have exactly one more than normally displayed
686 # if we have exactly one more than normally displayed
704 # just display it, takes less space than displaying
687 # just display it, takes less space than displaying
705 # "and 1 more revisions"
688 # "and 1 more revisions"
706 if len(revs_ids) == revs_limit + 1:
689 if len(revs_ids) == revs_limit + 1:
707 cs_links.append(", " + lnk(revs[revs_limit], repo_name))
690 cs_links.append(", " + lnk(revs[revs_limit], repo_name))
708
691
709 # hidden-by-default ones
692 # hidden-by-default ones
710 if len(revs_ids) > revs_limit + 1:
693 if len(revs_ids) > revs_limit + 1:
711 uniq_id = revs_ids[0]
694 uniq_id = revs_ids[0]
712 html_tmpl = (
695 html_tmpl = (
713 '<span> %s <a class="show_more" id="_%s" '
696 '<span> %s <a class="show_more" id="_%s" '
714 'href="#more">%s</a> %s</span>'
697 'href="#more">%s</a> %s</span>'
715 )
698 )
716 if not feed:
699 if not feed:
717 cs_links.append(html_tmpl % (
700 cs_links.append(html_tmpl % (
718 _('and'),
701 _('and'),
719 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
702 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
720 _('revisions')
703 _('revisions')
721 )
704 )
722 )
705 )
723
706
724 if not feed:
707 if not feed:
725 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
708 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
726 else:
709 else:
727 html_tmpl = '<span id="%s"> %s </span>'
710 html_tmpl = '<span id="%s"> %s </span>'
728
711
729 morelinks = ', '.join(
712 morelinks = ', '.join(
730 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
713 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
731 )
714 )
732
715
733 if len(revs_ids) > revs_top_limit:
716 if len(revs_ids) > revs_top_limit:
734 morelinks += ', ...'
717 morelinks += ', ...'
735
718
736 cs_links.append(html_tmpl % (uniq_id, morelinks))
719 cs_links.append(html_tmpl % (uniq_id, morelinks))
737 if len(revs) > 1:
720 if len(revs) > 1:
738 cs_links.append(compare_view)
721 cs_links.append(compare_view)
739 return ''.join(cs_links)
722 return ''.join(cs_links)
740
723
741 def get_fork_name():
724 def get_fork_name():
742 repo_name = action_params
725 repo_name = action_params
743 _url = url('summary_home', repo_name=repo_name)
726 _url = url('summary_home', repo_name=repo_name)
744 return _('fork name %s') % link_to(action_params, _url)
727 return _('fork name %s') % link_to(action_params, _url)
745
728
746 def get_user_name():
729 def get_user_name():
747 user_name = action_params
730 user_name = action_params
748 return user_name
731 return user_name
749
732
750 def get_users_group():
733 def get_users_group():
751 group_name = action_params
734 group_name = action_params
752 return group_name
735 return group_name
753
736
754 def get_pull_request():
737 def get_pull_request():
755 pull_request_id = action_params
738 pull_request_id = action_params
756 deleted = user_log.repository is None
739 deleted = user_log.repository is None
757 if deleted:
740 if deleted:
758 repo_name = user_log.repository_name
741 repo_name = user_log.repository_name
759 else:
742 else:
760 repo_name = user_log.repository.repo_name
743 repo_name = user_log.repository.repo_name
761 return link_to(_('Pull request #%s') % pull_request_id,
744 return link_to(_('Pull request #%s') % pull_request_id,
762 url('pullrequest_show', repo_name=repo_name,
745 url('pullrequest_show', repo_name=repo_name,
763 pull_request_id=pull_request_id))
746 pull_request_id=pull_request_id))
764
747
765 def get_archive_name():
748 def get_archive_name():
766 archive_name = action_params
749 archive_name = action_params
767 return archive_name
750 return archive_name
768
751
769 # action : translated str, callback(extractor), icon
752 # action : translated str, callback(extractor), icon
770 action_map = {
753 action_map = {
771 'user_deleted_repo': (_('[deleted] repository'),
754 'user_deleted_repo': (_('[deleted] repository'),
772 None, 'icon-trashcan'),
755 None, 'icon-trashcan'),
773 'user_created_repo': (_('[created] repository'),
756 'user_created_repo': (_('[created] repository'),
774 None, 'icon-plus'),
757 None, 'icon-plus'),
775 'user_created_fork': (_('[created] repository as fork'),
758 'user_created_fork': (_('[created] repository as fork'),
776 None, 'icon-fork'),
759 None, 'icon-fork'),
777 'user_forked_repo': (_('[forked] repository'),
760 'user_forked_repo': (_('[forked] repository'),
778 get_fork_name, 'icon-fork'),
761 get_fork_name, 'icon-fork'),
779 'user_updated_repo': (_('[updated] repository'),
762 'user_updated_repo': (_('[updated] repository'),
780 None, 'icon-pencil'),
763 None, 'icon-pencil'),
781 'user_downloaded_archive': (_('[downloaded] archive from repository'),
764 'user_downloaded_archive': (_('[downloaded] archive from repository'),
782 get_archive_name, 'icon-download-cloud'),
765 get_archive_name, 'icon-download-cloud'),
783 'admin_deleted_repo': (_('[delete] repository'),
766 'admin_deleted_repo': (_('[delete] repository'),
784 None, 'icon-trashcan'),
767 None, 'icon-trashcan'),
785 'admin_created_repo': (_('[created] repository'),
768 'admin_created_repo': (_('[created] repository'),
786 None, 'icon-plus'),
769 None, 'icon-plus'),
787 'admin_forked_repo': (_('[forked] repository'),
770 'admin_forked_repo': (_('[forked] repository'),
788 None, 'icon-fork'),
771 None, 'icon-fork'),
789 'admin_updated_repo': (_('[updated] repository'),
772 'admin_updated_repo': (_('[updated] repository'),
790 None, 'icon-pencil'),
773 None, 'icon-pencil'),
791 'admin_created_user': (_('[created] user'),
774 'admin_created_user': (_('[created] user'),
792 get_user_name, 'icon-user'),
775 get_user_name, 'icon-user'),
793 'admin_updated_user': (_('[updated] user'),
776 'admin_updated_user': (_('[updated] user'),
794 get_user_name, 'icon-user'),
777 get_user_name, 'icon-user'),
795 'admin_created_users_group': (_('[created] user group'),
778 'admin_created_users_group': (_('[created] user group'),
796 get_users_group, 'icon-pencil'),
779 get_users_group, 'icon-pencil'),
797 'admin_updated_users_group': (_('[updated] user group'),
780 'admin_updated_users_group': (_('[updated] user group'),
798 get_users_group, 'icon-pencil'),
781 get_users_group, 'icon-pencil'),
799 'user_commented_revision': (_('[commented] on revision in repository'),
782 'user_commented_revision': (_('[commented] on revision in repository'),
800 get_cs_links, 'icon-comment'),
783 get_cs_links, 'icon-comment'),
801 'user_commented_pull_request': (_('[commented] on pull request for'),
784 'user_commented_pull_request': (_('[commented] on pull request for'),
802 get_pull_request, 'icon-comment'),
785 get_pull_request, 'icon-comment'),
803 'user_closed_pull_request': (_('[closed] pull request for'),
786 'user_closed_pull_request': (_('[closed] pull request for'),
804 get_pull_request, 'icon-ok'),
787 get_pull_request, 'icon-ok'),
805 'push': (_('[pushed] into'),
788 'push': (_('[pushed] into'),
806 get_cs_links, 'icon-move-up'),
789 get_cs_links, 'icon-move-up'),
807 'push_local': (_('[committed via Kallithea] into repository'),
790 'push_local': (_('[committed via Kallithea] into repository'),
808 get_cs_links, 'icon-pencil'),
791 get_cs_links, 'icon-pencil'),
809 'push_remote': (_('[pulled from remote] into repository'),
792 'push_remote': (_('[pulled from remote] into repository'),
810 get_cs_links, 'icon-move-up'),
793 get_cs_links, 'icon-move-up'),
811 'pull': (_('[pulled] from'),
794 'pull': (_('[pulled] from'),
812 None, 'icon-move-down'),
795 None, 'icon-move-down'),
813 'started_following_repo': (_('[started following] repository'),
796 'started_following_repo': (_('[started following] repository'),
814 None, 'icon-heart'),
797 None, 'icon-heart'),
815 'stopped_following_repo': (_('[stopped following] repository'),
798 'stopped_following_repo': (_('[stopped following] repository'),
816 None, 'icon-heart-empty'),
799 None, 'icon-heart-empty'),
817 }
800 }
818
801
819 action_str = action_map.get(action, action)
802 action_str = action_map.get(action, action)
820 if feed:
803 if feed:
821 action = action_str[0].replace('[', '').replace(']', '')
804 action = action_str[0].replace('[', '').replace(']', '')
822 else:
805 else:
823 action = action_str[0]\
806 action = action_str[0]\
824 .replace('[', '<span class="journal_highlight">')\
807 .replace('[', '<span class="journal_highlight">')\
825 .replace(']', '</span>')
808 .replace(']', '</span>')
826
809
827 action_params_func = lambda: ""
810 action_params_func = lambda: ""
828
811
829 if callable(action_str[1]):
812 if callable(action_str[1]):
830 action_params_func = action_str[1]
813 action_params_func = action_str[1]
831
814
832 def action_parser_icon():
815 def action_parser_icon():
833 action = user_log.action
816 action = user_log.action
834 action_params = None
817 action_params = None
835 x = action.split(':')
818 x = action.split(':')
836
819
837 if len(x) > 1:
820 if len(x) > 1:
838 action, action_params = x
821 action, action_params = x
839
822
840 tmpl = """<i class="%s" alt="%s"></i>"""
823 tmpl = """<i class="%s" alt="%s"></i>"""
841 ico = action_map.get(action, ['', '', ''])[2]
824 ico = action_map.get(action, ['', '', ''])[2]
842 return literal(tmpl % (ico, action))
825 return literal(tmpl % (ico, action))
843
826
844 # returned callbacks we need to call to get
827 # returned callbacks we need to call to get
845 return [lambda: literal(action), action_params_func, action_parser_icon]
828 return [lambda: literal(action), action_params_func, action_parser_icon]
846
829
847
830
848
831
849 #==============================================================================
832 #==============================================================================
850 # PERMS
833 # PERMS
851 #==============================================================================
834 #==============================================================================
852 from kallithea.lib.auth import HasPermissionAny, HasPermissionAll, \
835 from kallithea.lib.auth import HasPermissionAny, HasPermissionAll, \
853 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
836 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
854 HasRepoGroupPermissionAny
837 HasRepoGroupPermissionAny
855
838
856
839
857 #==============================================================================
840 #==============================================================================
858 # GRAVATAR URL
841 # GRAVATAR URL
859 #==============================================================================
842 #==============================================================================
860 def gravatar(email_address, cls='', size=30, ssl_enabled=True):
843 def gravatar(email_address, cls='', size=30, ssl_enabled=True):
861 """return html element of the gravatar
844 """return html element of the gravatar
862
845
863 This method will return an <img> with the resolution double the size (for
846 This method will return an <img> with the resolution double the size (for
864 retina screens) of the image. If the url returned from gravatar_url is
847 retina screens) of the image. If the url returned from gravatar_url is
865 empty then we fallback to using an icon.
848 empty then we fallback to using an icon.
866
849
867 """
850 """
868 src = gravatar_url(email_address, size*2, ssl_enabled)
851 src = gravatar_url(email_address, size*2, ssl_enabled)
869
852
870 # here it makes sense to use style="width: ..." (instead of, say, a
853 # here it makes sense to use style="width: ..." (instead of, say, a
871 # stylesheet) because we using this to generate a high-res (retina) size
854 # stylesheet) because we using this to generate a high-res (retina) size
872 tmpl = """<img alt="gravatar" class="{cls}" style="width: {size}px; height: {size}px" src="{src}"/>"""
855 tmpl = """<img alt="gravatar" class="{cls}" style="width: {size}px; height: {size}px" src="{src}"/>"""
873
856
874 # if src is empty then there was no gravatar, so we use a font icon
857 # if src is empty then there was no gravatar, so we use a font icon
875 if not src:
858 if not src:
876 tmpl = """<i class="icon-user {cls}" style="font-size: {size}px;"></i>"""
859 tmpl = """<i class="icon-user {cls}" style="font-size: {size}px;"></i>"""
877
860
878 tmpl = tmpl.format(cls=cls, size=size, src=src)
861 tmpl = tmpl.format(cls=cls, size=size, src=src)
879 return literal(tmpl)
862 return literal(tmpl)
880
863
881 def gravatar_url(email_address, size=30, ssl_enabled=True):
864 def gravatar_url(email_address, size=30, ssl_enabled=True):
882 # doh, we need to re-import those to mock it later
865 # doh, we need to re-import those to mock it later
883 from pylons import url
866 from pylons import url
884 from pylons import tmpl_context as c
867 from pylons import tmpl_context as c
885
868
886 _def = 'anonymous@kallithea-scm.org' # default gravatar
869 _def = 'anonymous@kallithea-scm.org' # default gravatar
887 _use_gravatar = c.visual.use_gravatar
870 _use_gravatar = c.visual.use_gravatar
888 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
871 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
889
872
890 email_address = email_address or _def
873 email_address = email_address or _def
891
874
892 if not _use_gravatar or not email_address or email_address == _def:
875 if not _use_gravatar or not email_address or email_address == _def:
893 return ""
876 return ""
894
877
895 if _use_gravatar:
878 if _use_gravatar:
896 _md5 = lambda s: hashlib.md5(s).hexdigest()
879 _md5 = lambda s: hashlib.md5(s).hexdigest()
897
880
898 tmpl = _gravatar_url
881 tmpl = _gravatar_url
899 parsed_url = urlparse.urlparse(url.current(qualified=True))
882 parsed_url = urlparse.urlparse(url.current(qualified=True))
900 tmpl = tmpl.replace('{email}', email_address)\
883 tmpl = tmpl.replace('{email}', email_address)\
901 .replace('{md5email}', _md5(safe_str(email_address).lower())) \
884 .replace('{md5email}', _md5(safe_str(email_address).lower())) \
902 .replace('{netloc}', parsed_url.netloc)\
885 .replace('{netloc}', parsed_url.netloc)\
903 .replace('{scheme}', parsed_url.scheme)\
886 .replace('{scheme}', parsed_url.scheme)\
904 .replace('{size}', safe_str(size))
887 .replace('{size}', safe_str(size))
905 return tmpl
888 return tmpl
906
889
907 class Page(_Page):
890 class Page(_Page):
908 """
891 """
909 Custom pager to match rendering style with YUI paginator
892 Custom pager to match rendering style with YUI paginator
910 """
893 """
911
894
912 def _get_pos(self, cur_page, max_page, items):
895 def _get_pos(self, cur_page, max_page, items):
913 edge = (items / 2) + 1
896 edge = (items / 2) + 1
914 if (cur_page <= edge):
897 if (cur_page <= edge):
915 radius = max(items / 2, items - cur_page)
898 radius = max(items / 2, items - cur_page)
916 elif (max_page - cur_page) < edge:
899 elif (max_page - cur_page) < edge:
917 radius = (items - 1) - (max_page - cur_page)
900 radius = (items - 1) - (max_page - cur_page)
918 else:
901 else:
919 radius = items / 2
902 radius = items / 2
920
903
921 left = max(1, (cur_page - (radius)))
904 left = max(1, (cur_page - (radius)))
922 right = min(max_page, cur_page + (radius))
905 right = min(max_page, cur_page + (radius))
923 return left, cur_page, right
906 return left, cur_page, right
924
907
925 def _range(self, regexp_match):
908 def _range(self, regexp_match):
926 """
909 """
927 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
910 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
928
911
929 Arguments:
912 Arguments:
930
913
931 regexp_match
914 regexp_match
932 A "re" (regular expressions) match object containing the
915 A "re" (regular expressions) match object containing the
933 radius of linked pages around the current page in
916 radius of linked pages around the current page in
934 regexp_match.group(1) as a string
917 regexp_match.group(1) as a string
935
918
936 This function is supposed to be called as a callable in
919 This function is supposed to be called as a callable in
937 re.sub.
920 re.sub.
938
921
939 """
922 """
940 radius = int(regexp_match.group(1))
923 radius = int(regexp_match.group(1))
941
924
942 # Compute the first and last page number within the radius
925 # Compute the first and last page number within the radius
943 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
926 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
944 # -> leftmost_page = 5
927 # -> leftmost_page = 5
945 # -> rightmost_page = 9
928 # -> rightmost_page = 9
946 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
929 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
947 self.last_page,
930 self.last_page,
948 (radius * 2) + 1)
931 (radius * 2) + 1)
949 nav_items = []
932 nav_items = []
950
933
951 # Create a link to the first page (unless we are on the first page
934 # Create a link to the first page (unless we are on the first page
952 # or there would be no need to insert '..' spacers)
935 # or there would be no need to insert '..' spacers)
953 if self.page != self.first_page and self.first_page < leftmost_page:
936 if self.page != self.first_page and self.first_page < leftmost_page:
954 nav_items.append(self._pagerlink(self.first_page, self.first_page))
937 nav_items.append(self._pagerlink(self.first_page, self.first_page))
955
938
956 # Insert dots if there are pages between the first page
939 # Insert dots if there are pages between the first page
957 # and the currently displayed page range
940 # and the currently displayed page range
958 if leftmost_page - self.first_page > 1:
941 if leftmost_page - self.first_page > 1:
959 # Wrap in a SPAN tag if nolink_attr is set
942 # Wrap in a SPAN tag if nolink_attr is set
960 text = '..'
943 text = '..'
961 if self.dotdot_attr:
944 if self.dotdot_attr:
962 text = HTML.span(c=text, **self.dotdot_attr)
945 text = HTML.span(c=text, **self.dotdot_attr)
963 nav_items.append(text)
946 nav_items.append(text)
964
947
965 for thispage in xrange(leftmost_page, rightmost_page + 1):
948 for thispage in xrange(leftmost_page, rightmost_page + 1):
966 # Highlight the current page number and do not use a link
949 # Highlight the current page number and do not use a link
967 if thispage == self.page:
950 if thispage == self.page:
968 text = '%s' % (thispage,)
951 text = '%s' % (thispage,)
969 # Wrap in a SPAN tag if nolink_attr is set
952 # Wrap in a SPAN tag if nolink_attr is set
970 if self.curpage_attr:
953 if self.curpage_attr:
971 text = HTML.span(c=text, **self.curpage_attr)
954 text = HTML.span(c=text, **self.curpage_attr)
972 nav_items.append(text)
955 nav_items.append(text)
973 # Otherwise create just a link to that page
956 # Otherwise create just a link to that page
974 else:
957 else:
975 text = '%s' % (thispage,)
958 text = '%s' % (thispage,)
976 nav_items.append(self._pagerlink(thispage, text))
959 nav_items.append(self._pagerlink(thispage, text))
977
960
978 # Insert dots if there are pages between the displayed
961 # Insert dots if there are pages between the displayed
979 # page numbers and the end of the page range
962 # page numbers and the end of the page range
980 if self.last_page - rightmost_page > 1:
963 if self.last_page - rightmost_page > 1:
981 text = '..'
964 text = '..'
982 # Wrap in a SPAN tag if nolink_attr is set
965 # Wrap in a SPAN tag if nolink_attr is set
983 if self.dotdot_attr:
966 if self.dotdot_attr:
984 text = HTML.span(c=text, **self.dotdot_attr)
967 text = HTML.span(c=text, **self.dotdot_attr)
985 nav_items.append(text)
968 nav_items.append(text)
986
969
987 # Create a link to the very last page (unless we are on the last
970 # Create a link to the very last page (unless we are on the last
988 # page or there would be no need to insert '..' spacers)
971 # page or there would be no need to insert '..' spacers)
989 if self.page != self.last_page and rightmost_page < self.last_page:
972 if self.page != self.last_page and rightmost_page < self.last_page:
990 nav_items.append(self._pagerlink(self.last_page, self.last_page))
973 nav_items.append(self._pagerlink(self.last_page, self.last_page))
991
974
992 #_page_link = url.current()
975 #_page_link = url.current()
993 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
976 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
994 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
977 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
995 return self.separator.join(nav_items)
978 return self.separator.join(nav_items)
996
979
997 def pager(self, format='~2~', page_param='page', partial_param='partial',
980 def pager(self, format='~2~', page_param='page', partial_param='partial',
998 show_if_single_page=False, separator=' ', onclick=None,
981 show_if_single_page=False, separator=' ', onclick=None,
999 symbol_first='<<', symbol_last='>>',
982 symbol_first='<<', symbol_last='>>',
1000 symbol_previous='<', symbol_next='>',
983 symbol_previous='<', symbol_next='>',
1001 link_attr={'class': 'pager_link', 'rel': 'prerender'},
984 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1002 curpage_attr={'class': 'pager_curpage'},
985 curpage_attr={'class': 'pager_curpage'},
1003 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
986 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1004
987
1005 self.curpage_attr = curpage_attr
988 self.curpage_attr = curpage_attr
1006 self.separator = separator
989 self.separator = separator
1007 self.pager_kwargs = kwargs
990 self.pager_kwargs = kwargs
1008 self.page_param = page_param
991 self.page_param = page_param
1009 self.partial_param = partial_param
992 self.partial_param = partial_param
1010 self.onclick = onclick
993 self.onclick = onclick
1011 self.link_attr = link_attr
994 self.link_attr = link_attr
1012 self.dotdot_attr = dotdot_attr
995 self.dotdot_attr = dotdot_attr
1013
996
1014 # Don't show navigator if there is no more than one page
997 # Don't show navigator if there is no more than one page
1015 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
998 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1016 return ''
999 return ''
1017
1000
1018 from string import Template
1001 from string import Template
1019 # Replace ~...~ in token format by range of pages
1002 # Replace ~...~ in token format by range of pages
1020 result = re.sub(r'~(\d+)~', self._range, format)
1003 result = re.sub(r'~(\d+)~', self._range, format)
1021
1004
1022 # Interpolate '%' variables
1005 # Interpolate '%' variables
1023 result = Template(result).safe_substitute({
1006 result = Template(result).safe_substitute({
1024 'first_page': self.first_page,
1007 'first_page': self.first_page,
1025 'last_page': self.last_page,
1008 'last_page': self.last_page,
1026 'page': self.page,
1009 'page': self.page,
1027 'page_count': self.page_count,
1010 'page_count': self.page_count,
1028 'items_per_page': self.items_per_page,
1011 'items_per_page': self.items_per_page,
1029 'first_item': self.first_item,
1012 'first_item': self.first_item,
1030 'last_item': self.last_item,
1013 'last_item': self.last_item,
1031 'item_count': self.item_count,
1014 'item_count': self.item_count,
1032 'link_first': self.page > self.first_page and \
1015 'link_first': self.page > self.first_page and \
1033 self._pagerlink(self.first_page, symbol_first) or '',
1016 self._pagerlink(self.first_page, symbol_first) or '',
1034 'link_last': self.page < self.last_page and \
1017 'link_last': self.page < self.last_page and \
1035 self._pagerlink(self.last_page, symbol_last) or '',
1018 self._pagerlink(self.last_page, symbol_last) or '',
1036 'link_previous': self.previous_page and \
1019 'link_previous': self.previous_page and \
1037 self._pagerlink(self.previous_page, symbol_previous) \
1020 self._pagerlink(self.previous_page, symbol_previous) \
1038 or HTML.span(symbol_previous, class_="yui-pg-previous"),
1021 or HTML.span(symbol_previous, class_="yui-pg-previous"),
1039 'link_next': self.next_page and \
1022 'link_next': self.next_page and \
1040 self._pagerlink(self.next_page, symbol_next) \
1023 self._pagerlink(self.next_page, symbol_next) \
1041 or HTML.span(symbol_next, class_="yui-pg-next")
1024 or HTML.span(symbol_next, class_="yui-pg-next")
1042 })
1025 })
1043
1026
1044 return literal(result)
1027 return literal(result)
1045
1028
1046
1029
1047 #==============================================================================
1030 #==============================================================================
1048 # REPO PAGER, PAGER FOR REPOSITORY
1031 # REPO PAGER, PAGER FOR REPOSITORY
1049 #==============================================================================
1032 #==============================================================================
1050 class RepoPage(Page):
1033 class RepoPage(Page):
1051
1034
1052 def __init__(self, collection, page=1, items_per_page=20,
1035 def __init__(self, collection, page=1, items_per_page=20,
1053 item_count=None, url=None, **kwargs):
1036 item_count=None, url=None, **kwargs):
1054
1037
1055 """Create a "RepoPage" instance. special pager for paging
1038 """Create a "RepoPage" instance. special pager for paging
1056 repository
1039 repository
1057 """
1040 """
1058 self._url_generator = url
1041 self._url_generator = url
1059
1042
1060 # Safe the kwargs class-wide so they can be used in the pager() method
1043 # Safe the kwargs class-wide so they can be used in the pager() method
1061 self.kwargs = kwargs
1044 self.kwargs = kwargs
1062
1045
1063 # Save a reference to the collection
1046 # Save a reference to the collection
1064 self.original_collection = collection
1047 self.original_collection = collection
1065
1048
1066 self.collection = collection
1049 self.collection = collection
1067
1050
1068 # The self.page is the number of the current page.
1051 # The self.page is the number of the current page.
1069 # The first page has the number 1!
1052 # The first page has the number 1!
1070 try:
1053 try:
1071 self.page = int(page) # make it int() if we get it as a string
1054 self.page = int(page) # make it int() if we get it as a string
1072 except (ValueError, TypeError):
1055 except (ValueError, TypeError):
1073 self.page = 1
1056 self.page = 1
1074
1057
1075 self.items_per_page = items_per_page
1058 self.items_per_page = items_per_page
1076
1059
1077 # Unless the user tells us how many items the collections has
1060 # Unless the user tells us how many items the collections has
1078 # we calculate that ourselves.
1061 # we calculate that ourselves.
1079 if item_count is not None:
1062 if item_count is not None:
1080 self.item_count = item_count
1063 self.item_count = item_count
1081 else:
1064 else:
1082 self.item_count = len(self.collection)
1065 self.item_count = len(self.collection)
1083
1066
1084 # Compute the number of the first and last available page
1067 # Compute the number of the first and last available page
1085 if self.item_count > 0:
1068 if self.item_count > 0:
1086 self.first_page = 1
1069 self.first_page = 1
1087 self.page_count = int(math.ceil(float(self.item_count) /
1070 self.page_count = int(math.ceil(float(self.item_count) /
1088 self.items_per_page))
1071 self.items_per_page))
1089 self.last_page = self.first_page + self.page_count - 1
1072 self.last_page = self.first_page + self.page_count - 1
1090
1073
1091 # Make sure that the requested page number is the range of
1074 # Make sure that the requested page number is the range of
1092 # valid pages
1075 # valid pages
1093 if self.page > self.last_page:
1076 if self.page > self.last_page:
1094 self.page = self.last_page
1077 self.page = self.last_page
1095 elif self.page < self.first_page:
1078 elif self.page < self.first_page:
1096 self.page = self.first_page
1079 self.page = self.first_page
1097
1080
1098 # Note: the number of items on this page can be less than
1081 # Note: the number of items on this page can be less than
1099 # items_per_page if the last page is not full
1082 # items_per_page if the last page is not full
1100 self.first_item = max(0, (self.item_count) - (self.page *
1083 self.first_item = max(0, (self.item_count) - (self.page *
1101 items_per_page))
1084 items_per_page))
1102 self.last_item = ((self.item_count - 1) - items_per_page *
1085 self.last_item = ((self.item_count - 1) - items_per_page *
1103 (self.page - 1))
1086 (self.page - 1))
1104
1087
1105 self.items = list(self.collection[self.first_item:self.last_item + 1])
1088 self.items = list(self.collection[self.first_item:self.last_item + 1])
1106
1089
1107 # Links to previous and next page
1090 # Links to previous and next page
1108 if self.page > self.first_page:
1091 if self.page > self.first_page:
1109 self.previous_page = self.page - 1
1092 self.previous_page = self.page - 1
1110 else:
1093 else:
1111 self.previous_page = None
1094 self.previous_page = None
1112
1095
1113 if self.page < self.last_page:
1096 if self.page < self.last_page:
1114 self.next_page = self.page + 1
1097 self.next_page = self.page + 1
1115 else:
1098 else:
1116 self.next_page = None
1099 self.next_page = None
1117
1100
1118 # No items available
1101 # No items available
1119 else:
1102 else:
1120 self.first_page = None
1103 self.first_page = None
1121 self.page_count = 0
1104 self.page_count = 0
1122 self.last_page = None
1105 self.last_page = None
1123 self.first_item = None
1106 self.first_item = None
1124 self.last_item = None
1107 self.last_item = None
1125 self.previous_page = None
1108 self.previous_page = None
1126 self.next_page = None
1109 self.next_page = None
1127 self.items = []
1110 self.items = []
1128
1111
1129 # This is a subclass of the 'list' type. Initialise the list now.
1112 # This is a subclass of the 'list' type. Initialise the list now.
1130 list.__init__(self, reversed(self.items))
1113 list.__init__(self, reversed(self.items))
1131
1114
1132
1115
1133 def changed_tooltip(nodes):
1116 def changed_tooltip(nodes):
1134 """
1117 """
1135 Generates a html string for changed nodes in changeset page.
1118 Generates a html string for changed nodes in changeset page.
1136 It limits the output to 30 entries
1119 It limits the output to 30 entries
1137
1120
1138 :param nodes: LazyNodesGenerator
1121 :param nodes: LazyNodesGenerator
1139 """
1122 """
1140 if nodes:
1123 if nodes:
1141 pref = ': <br/> '
1124 pref = ': <br/> '
1142 suf = ''
1125 suf = ''
1143 if len(nodes) > 30:
1126 if len(nodes) > 30:
1144 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1127 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1145 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1128 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1146 for x in nodes[:30]]) + suf)
1129 for x in nodes[:30]]) + suf)
1147 else:
1130 else:
1148 return ': ' + _('No Files')
1131 return ': ' + _('No Files')
1149
1132
1150
1133
1151 def repo_link(groups_and_repos):
1134 def repo_link(groups_and_repos):
1152 """
1135 """
1153 Makes a breadcrumbs link to repo within a group
1136 Makes a breadcrumbs link to repo within a group
1154 joins &raquo; on each group to create a fancy link
1137 joins &raquo; on each group to create a fancy link
1155
1138
1156 ex::
1139 ex::
1157 group >> subgroup >> repo
1140 group >> subgroup >> repo
1158
1141
1159 :param groups_and_repos:
1142 :param groups_and_repos:
1160 :param last_url:
1143 :param last_url:
1161 """
1144 """
1162 groups, just_name, repo_name = groups_and_repos
1145 groups, just_name, repo_name = groups_and_repos
1163 last_url = url('summary_home', repo_name=repo_name)
1146 last_url = url('summary_home', repo_name=repo_name)
1164 last_link = link_to(just_name, last_url)
1147 last_link = link_to(just_name, last_url)
1165
1148
1166 def make_link(group):
1149 def make_link(group):
1167 return link_to(group.name,
1150 return link_to(group.name,
1168 url('repos_group_home', group_name=group.group_name))
1151 url('repos_group_home', group_name=group.group_name))
1169 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1152 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1170
1153
1171
1154
1172 def fancy_file_stats(stats):
1155 def fancy_file_stats(stats):
1173 """
1156 """
1174 Displays a fancy two colored bar for number of added/deleted
1157 Displays a fancy two colored bar for number of added/deleted
1175 lines of code on file
1158 lines of code on file
1176
1159
1177 :param stats: two element list of added/deleted lines of code
1160 :param stats: two element list of added/deleted lines of code
1178 """
1161 """
1179 from kallithea.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1162 from kallithea.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1180 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1163 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1181
1164
1182 def cgen(l_type, a_v, d_v):
1165 def cgen(l_type, a_v, d_v):
1183 mapping = {'tr': 'top-right-rounded-corner-mid',
1166 mapping = {'tr': 'top-right-rounded-corner-mid',
1184 'tl': 'top-left-rounded-corner-mid',
1167 'tl': 'top-left-rounded-corner-mid',
1185 'br': 'bottom-right-rounded-corner-mid',
1168 'br': 'bottom-right-rounded-corner-mid',
1186 'bl': 'bottom-left-rounded-corner-mid'}
1169 'bl': 'bottom-left-rounded-corner-mid'}
1187 map_getter = lambda x: mapping[x]
1170 map_getter = lambda x: mapping[x]
1188
1171
1189 if l_type == 'a' and d_v:
1172 if l_type == 'a' and d_v:
1190 #case when added and deleted are present
1173 #case when added and deleted are present
1191 return ' '.join(map(map_getter, ['tl', 'bl']))
1174 return ' '.join(map(map_getter, ['tl', 'bl']))
1192
1175
1193 if l_type == 'a' and not d_v:
1176 if l_type == 'a' and not d_v:
1194 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1177 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1195
1178
1196 if l_type == 'd' and a_v:
1179 if l_type == 'd' and a_v:
1197 return ' '.join(map(map_getter, ['tr', 'br']))
1180 return ' '.join(map(map_getter, ['tr', 'br']))
1198
1181
1199 if l_type == 'd' and not a_v:
1182 if l_type == 'd' and not a_v:
1200 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1183 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1201
1184
1202 a, d = stats['added'], stats['deleted']
1185 a, d = stats['added'], stats['deleted']
1203 width = 100
1186 width = 100
1204
1187
1205 if stats['binary']:
1188 if stats['binary']:
1206 #binary mode
1189 #binary mode
1207 lbl = ''
1190 lbl = ''
1208 bin_op = 1
1191 bin_op = 1
1209
1192
1210 if BIN_FILENODE in stats['ops']:
1193 if BIN_FILENODE in stats['ops']:
1211 lbl = 'bin+'
1194 lbl = 'bin+'
1212
1195
1213 if NEW_FILENODE in stats['ops']:
1196 if NEW_FILENODE in stats['ops']:
1214 lbl += _('new file')
1197 lbl += _('new file')
1215 bin_op = NEW_FILENODE
1198 bin_op = NEW_FILENODE
1216 elif MOD_FILENODE in stats['ops']:
1199 elif MOD_FILENODE in stats['ops']:
1217 lbl += _('mod')
1200 lbl += _('mod')
1218 bin_op = MOD_FILENODE
1201 bin_op = MOD_FILENODE
1219 elif DEL_FILENODE in stats['ops']:
1202 elif DEL_FILENODE in stats['ops']:
1220 lbl += _('del')
1203 lbl += _('del')
1221 bin_op = DEL_FILENODE
1204 bin_op = DEL_FILENODE
1222 elif RENAMED_FILENODE in stats['ops']:
1205 elif RENAMED_FILENODE in stats['ops']:
1223 lbl += _('rename')
1206 lbl += _('rename')
1224 bin_op = RENAMED_FILENODE
1207 bin_op = RENAMED_FILENODE
1225
1208
1226 #chmod can go with other operations
1209 #chmod can go with other operations
1227 if CHMOD_FILENODE in stats['ops']:
1210 if CHMOD_FILENODE in stats['ops']:
1228 _org_lbl = _('chmod')
1211 _org_lbl = _('chmod')
1229 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1212 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1230
1213
1231 #import ipdb;ipdb.set_trace()
1214 #import ipdb;ipdb.set_trace()
1232 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1215 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1233 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1216 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1234 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1217 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1235
1218
1236 t = stats['added'] + stats['deleted']
1219 t = stats['added'] + stats['deleted']
1237 unit = float(width) / (t or 1)
1220 unit = float(width) / (t or 1)
1238
1221
1239 # needs > 9% of width to be visible or 0 to be hidden
1222 # needs > 9% of width to be visible or 0 to be hidden
1240 a_p = max(9, unit * a) if a > 0 else 0
1223 a_p = max(9, unit * a) if a > 0 else 0
1241 d_p = max(9, unit * d) if d > 0 else 0
1224 d_p = max(9, unit * d) if d > 0 else 0
1242 p_sum = a_p + d_p
1225 p_sum = a_p + d_p
1243
1226
1244 if p_sum > width:
1227 if p_sum > width:
1245 #adjust the percentage to be == 100% since we adjusted to 9
1228 #adjust the percentage to be == 100% since we adjusted to 9
1246 if a_p > d_p:
1229 if a_p > d_p:
1247 a_p = a_p - (p_sum - width)
1230 a_p = a_p - (p_sum - width)
1248 else:
1231 else:
1249 d_p = d_p - (p_sum - width)
1232 d_p = d_p - (p_sum - width)
1250
1233
1251 a_v = a if a > 0 else ''
1234 a_v = a if a > 0 else ''
1252 d_v = d if d > 0 else ''
1235 d_v = d if d > 0 else ''
1253
1236
1254 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1237 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1255 cgen('a', a_v, d_v), a_p, a_v
1238 cgen('a', a_v, d_v), a_p, a_v
1256 )
1239 )
1257 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1240 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1258 cgen('d', a_v, d_v), d_p, d_v
1241 cgen('d', a_v, d_v), d_p, d_v
1259 )
1242 )
1260 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1243 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1261
1244
1262
1245
1263 def urlify_text(text_, safe=True):
1246 def urlify_text(text_, safe=True):
1264 """
1247 """
1265 Extract urls from text and make html links out of them
1248 Extract urls from text and make html links out of them
1266
1249
1267 :param text_:
1250 :param text_:
1268 """
1251 """
1269
1252
1270 def url_func(match_obj):
1253 def url_func(match_obj):
1271 url_full = match_obj.groups()[0]
1254 url_full = match_obj.groups()[0]
1272 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1255 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1273 _newtext = url_re.sub(url_func, text_)
1256 _newtext = url_re.sub(url_func, text_)
1274 if safe:
1257 if safe:
1275 return literal(_newtext)
1258 return literal(_newtext)
1276 return _newtext
1259 return _newtext
1277
1260
1278
1261
1279 def urlify_changesets(text_, repository):
1262 def urlify_changesets(text_, repository):
1280 """
1263 """
1281 Extract revision ids from changeset and make link from them
1264 Extract revision ids from changeset and make link from them
1282
1265
1283 :param text_:
1266 :param text_:
1284 :param repository: repo name to build the URL with
1267 :param repository: repo name to build the URL with
1285 """
1268 """
1286 from pylons import url # doh, we need to re-import url to mock it later
1269 from pylons import url # doh, we need to re-import url to mock it later
1287
1270
1288 def url_func(match_obj):
1271 def url_func(match_obj):
1289 rev = match_obj.group(0)
1272 rev = match_obj.group(0)
1290 return '<a class="revision-link" href="%(url)s">%(rev)s</a>' % {
1273 return '<a class="revision-link" href="%(url)s">%(rev)s</a>' % {
1291 'url': url('changeset_home', repo_name=repository, revision=rev),
1274 'url': url('changeset_home', repo_name=repository, revision=rev),
1292 'rev': rev,
1275 'rev': rev,
1293 }
1276 }
1294
1277
1295 return re.sub(r'(?:^|(?<=[\s(),]))([0-9a-fA-F]{12,40})(?=$|\s|[.,:()])', url_func, text_)
1278 return re.sub(r'(?:^|(?<=[\s(),]))([0-9a-fA-F]{12,40})(?=$|\s|[.,:()])', url_func, text_)
1296
1279
1297 def linkify_others(t, l):
1280 def linkify_others(t, l):
1298 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1281 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1299 links = []
1282 links = []
1300 for e in urls.split(t):
1283 for e in urls.split(t):
1301 if not urls.match(e):
1284 if not urls.match(e):
1302 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1285 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1303 else:
1286 else:
1304 links.append(e)
1287 links.append(e)
1305
1288
1306 return ''.join(links)
1289 return ''.join(links)
1307
1290
1308 def urlify_commit(text_, repository, link_=None):
1291 def urlify_commit(text_, repository, link_=None):
1309 """
1292 """
1310 Parses given text message and makes proper links.
1293 Parses given text message and makes proper links.
1311 issues are linked to given issue-server, and rest is a changeset link
1294 issues are linked to given issue-server, and rest is a changeset link
1312 if link_ is given, in other case it's a plain text
1295 if link_ is given, in other case it's a plain text
1313
1296
1314 :param text_:
1297 :param text_:
1315 :param repository:
1298 :param repository:
1316 :param link_: changeset link
1299 :param link_: changeset link
1317 """
1300 """
1318 def escaper(string):
1301 def escaper(string):
1319 return string.replace('<', '&lt;').replace('>', '&gt;')
1302 return string.replace('<', '&lt;').replace('>', '&gt;')
1320
1303
1321 # urlify changesets - extract revisions and make link out of them
1304 # urlify changesets - extract revisions and make link out of them
1322 newtext = urlify_changesets(escaper(text_), repository)
1305 newtext = urlify_changesets(escaper(text_), repository)
1323
1306
1324 # extract http/https links and make them real urls
1307 # extract http/https links and make them real urls
1325 newtext = urlify_text(newtext, safe=False)
1308 newtext = urlify_text(newtext, safe=False)
1326
1309
1327 newtext = urlify_issues(newtext, repository, link_)
1310 newtext = urlify_issues(newtext, repository, link_)
1328
1311
1329 return literal(newtext)
1312 return literal(newtext)
1330
1313
1331 def urlify_issues(newtext, repository, link_=None):
1314 def urlify_issues(newtext, repository, link_=None):
1332 from kallithea import CONFIG as conf
1315 from kallithea import CONFIG as conf
1333
1316
1334 # allow multiple issue servers to be used
1317 # allow multiple issue servers to be used
1335 valid_indices = [
1318 valid_indices = [
1336 x.group(1)
1319 x.group(1)
1337 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1320 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1338 if x and 'issue_server_link%s' % x.group(1) in conf
1321 if x and 'issue_server_link%s' % x.group(1) in conf
1339 and 'issue_prefix%s' % x.group(1) in conf
1322 and 'issue_prefix%s' % x.group(1) in conf
1340 ]
1323 ]
1341
1324
1342 if valid_indices:
1325 if valid_indices:
1343 log.debug('found issue server suffixes `%s` during valuation of: %s'
1326 log.debug('found issue server suffixes `%s` during valuation of: %s'
1344 % (','.join(valid_indices), newtext))
1327 % (','.join(valid_indices), newtext))
1345
1328
1346 for pattern_index in valid_indices:
1329 for pattern_index in valid_indices:
1347 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1330 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1348 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1331 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1349 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1332 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1350
1333
1351 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1334 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1352 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1335 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1353 ISSUE_PREFIX))
1336 ISSUE_PREFIX))
1354
1337
1355 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1338 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1356
1339
1357 def url_func(match_obj):
1340 def url_func(match_obj):
1358 pref = ''
1341 pref = ''
1359 if match_obj.group().startswith(' '):
1342 if match_obj.group().startswith(' '):
1360 pref = ' '
1343 pref = ' '
1361
1344
1362 issue_id = ''.join(match_obj.groups())
1345 issue_id = ''.join(match_obj.groups())
1363 issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1346 issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1364 if repository:
1347 if repository:
1365 issue_url = issue_url.replace('{repo}', repository)
1348 issue_url = issue_url.replace('{repo}', repository)
1366 repo_name = repository.split(URL_SEP)[-1]
1349 repo_name = repository.split(URL_SEP)[-1]
1367 issue_url = issue_url.replace('{repo_name}', repo_name)
1350 issue_url = issue_url.replace('{repo_name}', repo_name)
1368
1351
1369 return (
1352 return (
1370 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1353 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1371 '%(issue-prefix)s%(id-repr)s'
1354 '%(issue-prefix)s%(id-repr)s'
1372 '</a>'
1355 '</a>'
1373 ) % {
1356 ) % {
1374 'pref': pref,
1357 'pref': pref,
1375 'cls': 'issue-tracker-link',
1358 'cls': 'issue-tracker-link',
1376 'url': issue_url,
1359 'url': issue_url,
1377 'id-repr': issue_id,
1360 'id-repr': issue_id,
1378 'issue-prefix': ISSUE_PREFIX,
1361 'issue-prefix': ISSUE_PREFIX,
1379 'serv': ISSUE_SERVER_LNK,
1362 'serv': ISSUE_SERVER_LNK,
1380 }
1363 }
1381 newtext = URL_PAT.sub(url_func, newtext)
1364 newtext = URL_PAT.sub(url_func, newtext)
1382 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1365 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1383
1366
1384 # if we actually did something above
1367 # if we actually did something above
1385 if link_:
1368 if link_:
1386 # wrap not links into final link => link_
1369 # wrap not links into final link => link_
1387 newtext = linkify_others(newtext, link_)
1370 newtext = linkify_others(newtext, link_)
1388 return newtext
1371 return newtext
1389
1372
1390
1373
1391 def rst(source):
1374 def rst(source):
1392 return literal('<div class="rst-block">%s</div>' %
1375 return literal('<div class="rst-block">%s</div>' %
1393 MarkupRenderer.rst(source))
1376 MarkupRenderer.rst(source))
1394
1377
1395
1378
1396 def rst_w_mentions(source):
1379 def rst_w_mentions(source):
1397 """
1380 """
1398 Wrapped rst renderer with @mention highlighting
1381 Wrapped rst renderer with @mention highlighting
1399
1382
1400 :param source:
1383 :param source:
1401 """
1384 """
1402 return literal('<div class="rst-block">%s</div>' %
1385 return literal('<div class="rst-block">%s</div>' %
1403 MarkupRenderer.rst_with_mentions(source))
1386 MarkupRenderer.rst_with_mentions(source))
1404
1387
1405 def short_ref(ref_type, ref_name):
1388 def short_ref(ref_type, ref_name):
1406 if ref_type == 'rev':
1389 if ref_type == 'rev':
1407 return short_id(ref_name)
1390 return short_id(ref_name)
1408 return ref_name
1391 return ref_name
1409
1392
1410 def link_to_ref(repo_name, ref_type, ref_name, rev=None):
1393 def link_to_ref(repo_name, ref_type, ref_name, rev=None):
1411 """
1394 """
1412 Return full markup for a href to changeset_home for a changeset.
1395 Return full markup for a href to changeset_home for a changeset.
1413 If ref_type is branch it will link to changelog.
1396 If ref_type is branch it will link to changelog.
1414 ref_name is shortened if ref_type is 'rev'.
1397 ref_name is shortened if ref_type is 'rev'.
1415 if rev is specified show it too, explicitly linking to that revision.
1398 if rev is specified show it too, explicitly linking to that revision.
1416 """
1399 """
1417 txt = short_ref(ref_type, ref_name)
1400 txt = short_ref(ref_type, ref_name)
1418 if ref_type == 'branch':
1401 if ref_type == 'branch':
1419 u = url('changelog_home', repo_name=repo_name, branch=ref_name)
1402 u = url('changelog_home', repo_name=repo_name, branch=ref_name)
1420 else:
1403 else:
1421 u = url('changeset_home', repo_name=repo_name, revision=ref_name)
1404 u = url('changeset_home', repo_name=repo_name, revision=ref_name)
1422 l = link_to(repo_name + '#' + txt, u)
1405 l = link_to(repo_name + '#' + txt, u)
1423 if rev and ref_type != 'rev':
1406 if rev and ref_type != 'rev':
1424 l = literal('%s (%s)' % (l, link_to(short_id(rev), url('changeset_home', repo_name=repo_name, revision=rev))))
1407 l = literal('%s (%s)' % (l, link_to(short_id(rev), url('changeset_home', repo_name=repo_name, revision=rev))))
1425 return l
1408 return l
1426
1409
1427 def changeset_status(repo, revision):
1410 def changeset_status(repo, revision):
1428 return ChangesetStatusModel().get_status(repo, revision)
1411 return ChangesetStatusModel().get_status(repo, revision)
1429
1412
1430
1413
1431 def changeset_status_lbl(changeset_status):
1414 def changeset_status_lbl(changeset_status):
1432 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1415 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1433
1416
1434
1417
1435 def get_permission_name(key):
1418 def get_permission_name(key):
1436 return dict(Permission.PERMS).get(key)
1419 return dict(Permission.PERMS).get(key)
1437
1420
1438
1421
1439 def journal_filter_help():
1422 def journal_filter_help():
1440 return _(textwrap.dedent('''
1423 return _(textwrap.dedent('''
1441 Example filter terms:
1424 Example filter terms:
1442 repository:vcs
1425 repository:vcs
1443 username:developer
1426 username:developer
1444 action:*push*
1427 action:*push*
1445 ip:127.0.0.1
1428 ip:127.0.0.1
1446 date:20120101
1429 date:20120101
1447 date:[20120101100000 TO 20120102]
1430 date:[20120101100000 TO 20120102]
1448
1431
1449 Generate wildcards using '*' character:
1432 Generate wildcards using '*' character:
1450 "repository:vcs*" - search everything starting with 'vcs'
1433 "repository:vcs*" - search everything starting with 'vcs'
1451 "repository:*vcs*" - search for repository containing 'vcs'
1434 "repository:*vcs*" - search for repository containing 'vcs'
1452
1435
1453 Optional AND / OR operators in queries
1436 Optional AND / OR operators in queries
1454 "repository:vcs OR repository:test"
1437 "repository:vcs OR repository:test"
1455 "username:test AND repository:test*"
1438 "username:test AND repository:test*"
1456 '''))
1439 '''))
1457
1440
1458
1441
1459 def not_mapped_error(repo_name):
1442 def not_mapped_error(repo_name):
1460 flash(_('%s repository is not mapped to db perhaps'
1443 flash(_('%s repository is not mapped to db perhaps'
1461 ' it was created or renamed from the filesystem'
1444 ' it was created or renamed from the filesystem'
1462 ' please run the application again'
1445 ' please run the application again'
1463 ' in order to rescan repositories') % repo_name, category='error')
1446 ' in order to rescan repositories') % repo_name, category='error')
1464
1447
1465
1448
1466 def ip_range(ip_addr):
1449 def ip_range(ip_addr):
1467 from kallithea.model.db import UserIpMap
1450 from kallithea.model.db import UserIpMap
1468 s, e = UserIpMap._get_ip_range(ip_addr)
1451 s, e = UserIpMap._get_ip_range(ip_addr)
1469 return '%s - %s' % (s, e)
1452 return '%s - %s' % (s, e)
@@ -1,2158 +1,2154 b''
1 /**
1 /**
2 Kallithea JS Files
2 Kallithea JS Files
3 **/
3 **/
4 'use strict';
4 'use strict';
5
5
6 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 if (typeof console == "undefined" || typeof console.log == "undefined"){
7 console = { log: function() {} }
7 console = { log: function() {} }
8 }
8 }
9
9
10 /**
10 /**
11 * INJECT .format function into String
11 * INJECT .format function into String
12 * Usage: "My name is {0} {1}".format("Johny","Bravo")
12 * Usage: "My name is {0} {1}".format("Johny","Bravo")
13 * Return "My name is Johny Bravo"
13 * Return "My name is Johny Bravo"
14 * Inspired by https://gist.github.com/1049426
14 * Inspired by https://gist.github.com/1049426
15 */
15 */
16 String.prototype.format = function() {
16 String.prototype.format = function() {
17 function format() {
17 function format() {
18 var str = this;
18 var str = this;
19 var len = arguments.length+1;
19 var len = arguments.length+1;
20 var safe = undefined;
20 var safe = undefined;
21 var arg = undefined;
21 var arg = undefined;
22
22
23 // For each {0} {1} {n...} replace with the argument in that position. If
23 // For each {0} {1} {n...} replace with the argument in that position. If
24 // the argument is an object or an array it will be stringified to JSON.
24 // the argument is an object or an array it will be stringified to JSON.
25 for (var i=0; i < len; arg = arguments[i++]) {
25 for (var i=0; i < len; arg = arguments[i++]) {
26 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
26 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
27 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
27 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
28 }
28 }
29 return str;
29 return str;
30 }
30 }
31
31
32 // Save a reference of what may already exist under the property native.
32 // Save a reference of what may already exist under the property native.
33 // Allows for doing something like: if("".format.native) { /* use native */ }
33 // Allows for doing something like: if("".format.native) { /* use native */ }
34 format.native = String.prototype.format;
34 format.native = String.prototype.format;
35
35
36 // Replace the prototype property
36 // Replace the prototype property
37 return format;
37 return format;
38
38
39 }();
39 }();
40
40
41 String.prototype.strip = function(char) {
41 String.prototype.strip = function(char) {
42 if(char === undefined){
42 if(char === undefined){
43 char = '\\s';
43 char = '\\s';
44 }
44 }
45 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
45 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
46 }
46 }
47
47
48 String.prototype.lstrip = function(char) {
48 String.prototype.lstrip = function(char) {
49 if(char === undefined){
49 if(char === undefined){
50 char = '\\s';
50 char = '\\s';
51 }
51 }
52 return this.replace(new RegExp('^'+char+'+'),'');
52 return this.replace(new RegExp('^'+char+'+'),'');
53 }
53 }
54
54
55 String.prototype.rstrip = function(char) {
55 String.prototype.rstrip = function(char) {
56 if(char === undefined){
56 if(char === undefined){
57 char = '\\s';
57 char = '\\s';
58 }
58 }
59 return this.replace(new RegExp(''+char+'+$'),'');
59 return this.replace(new RegExp(''+char+'+$'),'');
60 }
60 }
61
61
62 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill
62 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill
63 under MIT license / public domain, see
63 under MIT license / public domain, see
64 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
64 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
65 if(!Array.prototype.indexOf) {
65 if(!Array.prototype.indexOf) {
66 Array.prototype.indexOf = function (searchElement, fromIndex) {
66 Array.prototype.indexOf = function (searchElement, fromIndex) {
67 if ( this === undefined || this === null ) {
67 if ( this === undefined || this === null ) {
68 throw new TypeError( '"this" is null or not defined' );
68 throw new TypeError( '"this" is null or not defined' );
69 }
69 }
70
70
71 var length = this.length >>> 0; // Hack to convert object.length to a UInt32
71 var length = this.length >>> 0; // Hack to convert object.length to a UInt32
72
72
73 fromIndex = +fromIndex || 0;
73 fromIndex = +fromIndex || 0;
74
74
75 if (Math.abs(fromIndex) === Infinity) {
75 if (Math.abs(fromIndex) === Infinity) {
76 fromIndex = 0;
76 fromIndex = 0;
77 }
77 }
78
78
79 if (fromIndex < 0) {
79 if (fromIndex < 0) {
80 fromIndex += length;
80 fromIndex += length;
81 if (fromIndex < 0) {
81 if (fromIndex < 0) {
82 fromIndex = 0;
82 fromIndex = 0;
83 }
83 }
84 }
84 }
85
85
86 for (;fromIndex < length; fromIndex++) {
86 for (;fromIndex < length; fromIndex++) {
87 if (this[fromIndex] === searchElement) {
87 if (this[fromIndex] === searchElement) {
88 return fromIndex;
88 return fromIndex;
89 }
89 }
90 }
90 }
91
91
92 return -1;
92 return -1;
93 };
93 };
94 }
94 }
95
95
96 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Compatibility
96 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Compatibility
97 under MIT license / public domain, see
97 under MIT license / public domain, see
98 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
98 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
99 if (!Array.prototype.filter)
99 if (!Array.prototype.filter)
100 {
100 {
101 Array.prototype.filter = function(fun /*, thisArg */)
101 Array.prototype.filter = function(fun /*, thisArg */)
102 {
102 {
103 if (this === void 0 || this === null)
103 if (this === void 0 || this === null)
104 throw new TypeError();
104 throw new TypeError();
105
105
106 var t = Object(this);
106 var t = Object(this);
107 var len = t.length >>> 0;
107 var len = t.length >>> 0;
108 if (typeof fun !== "function")
108 if (typeof fun !== "function")
109 throw new TypeError();
109 throw new TypeError();
110
110
111 var res = [];
111 var res = [];
112 var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
112 var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
113 for (var i = 0; i < len; i++)
113 for (var i = 0; i < len; i++)
114 {
114 {
115 if (i in t)
115 if (i in t)
116 {
116 {
117 var val = t[i];
117 var val = t[i];
118
118
119 // NOTE: Technically this should Object.defineProperty at
119 // NOTE: Technically this should Object.defineProperty at
120 // the next index, as push can be affected by
120 // the next index, as push can be affected by
121 // properties on Object.prototype and Array.prototype.
121 // properties on Object.prototype and Array.prototype.
122 // But that method's new, and collisions should be
122 // But that method's new, and collisions should be
123 // rare, so use the more-compatible alternative.
123 // rare, so use the more-compatible alternative.
124 if (fun.call(thisArg, val, i, t))
124 if (fun.call(thisArg, val, i, t))
125 res.push(val);
125 res.push(val);
126 }
126 }
127 }
127 }
128
128
129 return res;
129 return res;
130 };
130 };
131 }
131 }
132
132
133 /**
133 /**
134 * A customized version of PyRoutes.JS from https://pypi.python.org/pypi/pyroutes.js/
134 * A customized version of PyRoutes.JS from https://pypi.python.org/pypi/pyroutes.js/
135 * which is copyright Stephane Klein and was made available under the BSD License.
135 * which is copyright Stephane Klein and was made available under the BSD License.
136 *
136 *
137 * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/<error_id>
137 * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/<error_id>
138 */
138 */
139 var pyroutes = (function() {
139 var pyroutes = (function() {
140 // access global map defined in special file pyroutes
140 // access global map defined in special file pyroutes
141 var matchlist = PROUTES_MAP;
141 var matchlist = PROUTES_MAP;
142 var sprintf = (function() {
142 var sprintf = (function() {
143 function get_type(variable) {
143 function get_type(variable) {
144 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
144 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
145 }
145 }
146 function str_repeat(input, multiplier) {
146 function str_repeat(input, multiplier) {
147 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
147 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
148 return output.join('');
148 return output.join('');
149 }
149 }
150
150
151 var str_format = function() {
151 var str_format = function() {
152 if (!str_format.cache.hasOwnProperty(arguments[0])) {
152 if (!str_format.cache.hasOwnProperty(arguments[0])) {
153 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
153 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
154 }
154 }
155 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
155 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
156 };
156 };
157
157
158 str_format.format = function(parse_tree, argv) {
158 str_format.format = function(parse_tree, argv) {
159 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
159 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
160 for (i = 0; i < tree_length; i++) {
160 for (i = 0; i < tree_length; i++) {
161 node_type = get_type(parse_tree[i]);
161 node_type = get_type(parse_tree[i]);
162 if (node_type === 'string') {
162 if (node_type === 'string') {
163 output.push(parse_tree[i]);
163 output.push(parse_tree[i]);
164 }
164 }
165 else if (node_type === 'array') {
165 else if (node_type === 'array') {
166 match = parse_tree[i]; // convenience purposes only
166 match = parse_tree[i]; // convenience purposes only
167 if (match[2]) { // keyword argument
167 if (match[2]) { // keyword argument
168 arg = argv[cursor];
168 arg = argv[cursor];
169 for (k = 0; k < match[2].length; k++) {
169 for (k = 0; k < match[2].length; k++) {
170 if (!arg.hasOwnProperty(match[2][k])) {
170 if (!arg.hasOwnProperty(match[2][k])) {
171 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
171 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
172 }
172 }
173 arg = arg[match[2][k]];
173 arg = arg[match[2][k]];
174 }
174 }
175 }
175 }
176 else if (match[1]) { // positional argument (explicit)
176 else if (match[1]) { // positional argument (explicit)
177 arg = argv[match[1]];
177 arg = argv[match[1]];
178 }
178 }
179 else { // positional argument (implicit)
179 else { // positional argument (implicit)
180 arg = argv[cursor++];
180 arg = argv[cursor++];
181 }
181 }
182
182
183 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
183 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
184 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
184 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
185 }
185 }
186 switch (match[8]) {
186 switch (match[8]) {
187 case 'b': arg = arg.toString(2); break;
187 case 'b': arg = arg.toString(2); break;
188 case 'c': arg = String.fromCharCode(arg); break;
188 case 'c': arg = String.fromCharCode(arg); break;
189 case 'd': arg = parseInt(arg, 10); break;
189 case 'd': arg = parseInt(arg, 10); break;
190 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
190 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
191 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
191 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
192 case 'o': arg = arg.toString(8); break;
192 case 'o': arg = arg.toString(8); break;
193 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
193 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
194 case 'u': arg = Math.abs(arg); break;
194 case 'u': arg = Math.abs(arg); break;
195 case 'x': arg = arg.toString(16); break;
195 case 'x': arg = arg.toString(16); break;
196 case 'X': arg = arg.toString(16).toUpperCase(); break;
196 case 'X': arg = arg.toString(16).toUpperCase(); break;
197 }
197 }
198 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
198 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
199 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
199 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
200 pad_length = match[6] - String(arg).length;
200 pad_length = match[6] - String(arg).length;
201 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
201 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
202 output.push(match[5] ? arg + pad : pad + arg);
202 output.push(match[5] ? arg + pad : pad + arg);
203 }
203 }
204 }
204 }
205 return output.join('');
205 return output.join('');
206 };
206 };
207
207
208 str_format.cache = {};
208 str_format.cache = {};
209
209
210 str_format.parse = function(fmt) {
210 str_format.parse = function(fmt) {
211 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
211 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
212 while (_fmt) {
212 while (_fmt) {
213 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
213 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
214 parse_tree.push(match[0]);
214 parse_tree.push(match[0]);
215 }
215 }
216 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
216 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
217 parse_tree.push('%');
217 parse_tree.push('%');
218 }
218 }
219 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
219 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
220 if (match[2]) {
220 if (match[2]) {
221 arg_names |= 1;
221 arg_names |= 1;
222 var field_list = [], replacement_field = match[2], field_match = [];
222 var field_list = [], replacement_field = match[2], field_match = [];
223 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
223 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
224 field_list.push(field_match[1]);
224 field_list.push(field_match[1]);
225 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
225 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
226 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
226 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
227 field_list.push(field_match[1]);
227 field_list.push(field_match[1]);
228 }
228 }
229 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
229 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
230 field_list.push(field_match[1]);
230 field_list.push(field_match[1]);
231 }
231 }
232 else {
232 else {
233 throw('[sprintf] huh?');
233 throw('[sprintf] huh?');
234 }
234 }
235 }
235 }
236 }
236 }
237 else {
237 else {
238 throw('[sprintf] huh?');
238 throw('[sprintf] huh?');
239 }
239 }
240 match[2] = field_list;
240 match[2] = field_list;
241 }
241 }
242 else {
242 else {
243 arg_names |= 2;
243 arg_names |= 2;
244 }
244 }
245 if (arg_names === 3) {
245 if (arg_names === 3) {
246 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
246 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
247 }
247 }
248 parse_tree.push(match);
248 parse_tree.push(match);
249 }
249 }
250 else {
250 else {
251 throw('[sprintf] huh?');
251 throw('[sprintf] huh?');
252 }
252 }
253 _fmt = _fmt.substring(match[0].length);
253 _fmt = _fmt.substring(match[0].length);
254 }
254 }
255 return parse_tree;
255 return parse_tree;
256 };
256 };
257
257
258 return str_format;
258 return str_format;
259 })();
259 })();
260
260
261 var vsprintf = function(fmt, argv) {
261 var vsprintf = function(fmt, argv) {
262 argv.unshift(fmt);
262 argv.unshift(fmt);
263 return sprintf.apply(null, argv);
263 return sprintf.apply(null, argv);
264 };
264 };
265 return {
265 return {
266 'url': function(route_name, params) {
266 'url': function(route_name, params) {
267 var result = route_name;
267 var result = route_name;
268 if (typeof(params) != 'object'){
268 if (typeof(params) != 'object'){
269 params = {};
269 params = {};
270 }
270 }
271 if (matchlist.hasOwnProperty(route_name)) {
271 if (matchlist.hasOwnProperty(route_name)) {
272 var route = matchlist[route_name];
272 var route = matchlist[route_name];
273 // param substitution
273 // param substitution
274 for(var i=0; i < route[1].length; i++) {
274 for(var i=0; i < route[1].length; i++) {
275 if (!params.hasOwnProperty(route[1][i]))
275 if (!params.hasOwnProperty(route[1][i]))
276 throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation');
276 throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation');
277 }
277 }
278 result = sprintf(route[0], params);
278 result = sprintf(route[0], params);
279
279
280 var ret = [];
280 var ret = [];
281 //extra params => GET
281 //extra params => GET
282 for(var param in params){
282 for(var param in params){
283 if (route[1].indexOf(param) == -1){
283 if (route[1].indexOf(param) == -1){
284 ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
284 ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
285 }
285 }
286 }
286 }
287 var _parts = ret.join("&");
287 var _parts = ret.join("&");
288 if(_parts){
288 if(_parts){
289 result = result +'?'+ _parts
289 result = result +'?'+ _parts
290 }
290 }
291 }
291 }
292
292
293 return result;
293 return result;
294 },
294 },
295 'register': function(route_name, route_tmpl, req_params) {
295 'register': function(route_name, route_tmpl, req_params) {
296 if (typeof(req_params) != 'object') {
296 if (typeof(req_params) != 'object') {
297 req_params = [];
297 req_params = [];
298 }
298 }
299 var keys = [];
299 var keys = [];
300 for (var i=0; i < req_params.length; i++) {
300 for (var i=0; i < req_params.length; i++) {
301 keys.push(req_params[i])
301 keys.push(req_params[i])
302 }
302 }
303 matchlist[route_name] = [
303 matchlist[route_name] = [
304 unescape(route_tmpl),
304 unescape(route_tmpl),
305 keys
305 keys
306 ]
306 ]
307 },
307 },
308 '_routes': function(){
308 '_routes': function(){
309 return matchlist;
309 return matchlist;
310 }
310 }
311 }
311 }
312 })();
312 })();
313
313
314
314
315 /**
315 /**
316 * GLOBAL YUI Shortcuts
316 * GLOBAL YUI Shortcuts
317 */
317 */
318 var YUD = YAHOO.util.Dom;
318 var YUD = YAHOO.util.Dom;
319 var YUE = YAHOO.util.Event;
319 var YUE = YAHOO.util.Event;
320
320
321 /* Invoke all functions in callbacks */
321 /* Invoke all functions in callbacks */
322 var _run_callbacks = function(callbacks){
322 var _run_callbacks = function(callbacks){
323 if (callbacks !== undefined){
323 if (callbacks !== undefined){
324 var _l = callbacks.length;
324 var _l = callbacks.length;
325 for (var i=0;i<_l;i++){
325 for (var i=0;i<_l;i++){
326 var func = callbacks[i];
326 var func = callbacks[i];
327 if(typeof(func)=='function'){
327 if(typeof(func)=='function'){
328 try{
328 try{
329 func();
329 func();
330 }catch (err){};
330 }catch (err){};
331 }
331 }
332 }
332 }
333 }
333 }
334 }
334 }
335
335
336 /**
336 /**
337 * turns objects into GET query string
337 * turns objects into GET query string
338 */
338 */
339 var _toQueryString = function(o) {
339 var _toQueryString = function(o) {
340 if(typeof o !== 'object') {
340 if(typeof o !== 'object') {
341 return false;
341 return false;
342 }
342 }
343 var _p, _qs = [];
343 var _p, _qs = [];
344 for(_p in o) {
344 for(_p in o) {
345 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
345 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
346 }
346 }
347 return _qs.join('&');
347 return _qs.join('&');
348 };
348 };
349
349
350 /**
350 /**
351 * Load HTML into DOM using Ajax
351 * Load HTML into DOM using Ajax
352 *
352 *
353 * @param $target: load html async and place it (or an error message) here
353 * @param $target: load html async and place it (or an error message) here
354 * @param success: success callback function
354 * @param success: success callback function
355 * @param args: query parameters to pass to url
355 * @param args: query parameters to pass to url
356 */
356 */
357 function asynchtml(url, $target, success, args){
357 function asynchtml(url, $target, success, args){
358 if(args===undefined){
358 if(args===undefined){
359 args=null;
359 args=null;
360 }
360 }
361 $target.html(_TM['Loading ...']).css('opacity','0.3');
361 $target.html(_TM['Loading ...']).css('opacity','0.3');
362
362
363 return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'})
363 return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'})
364 .done(function(html) {
364 .done(function(html) {
365 $target.html(html);
365 $target.html(html);
366 $target.css('opacity','1.0');
366 $target.css('opacity','1.0');
367 //execute the given original callback
367 //execute the given original callback
368 if (success !== undefined && success) {
368 if (success !== undefined && success) {
369 success();
369 success();
370 }
370 }
371 })
371 })
372 .fail(function(jqXHR, textStatus, errorThrown) {
372 .fail(function(jqXHR, textStatus, errorThrown) {
373 if (textStatus == "abort")
373 if (textStatus == "abort")
374 return;
374 return;
375 console.log('Ajax failure: ' + textStatus);
375 console.log('Ajax failure: ' + textStatus);
376 $target.html('<span class="error_red">ERROR: {0}</span>'.format(textStatus));
376 $target.html('<span class="error_red">ERROR: {0}</span>'.format(textStatus));
377 $target.css('opacity','1.0');
377 $target.css('opacity','1.0');
378 })
378 })
379 ;
379 ;
380 };
380 };
381
381
382 var ajaxGET = function(url,success) {
382 var ajaxGET = function(url,success) {
383 return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
383 return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
384 .done(success)
384 .done(success)
385 .fail(function(jqXHR, textStatus, errorThrown) {
385 .fail(function(jqXHR, textStatus, errorThrown) {
386 if (textStatus == "abort")
386 if (textStatus == "abort")
387 return;
387 return;
388 alert("Ajax GET error: " + textStatus);
388 alert("Ajax GET error: " + textStatus);
389 })
389 })
390 ;
390 ;
391 };
391 };
392
392
393 var ajaxPOST = function(url, postData, success, failure) {
393 var ajaxPOST = function(url, postData, success, failure) {
394 var postData = _toQueryString(postData);
394 var postData = _toQueryString(postData);
395 if(failure === undefined) {
395 if(failure === undefined) {
396 failure = function(jqXHR, textStatus, errorThrown) {
396 failure = function(jqXHR, textStatus, errorThrown) {
397 if (textStatus != "abort")
397 if (textStatus != "abort")
398 alert("Error posting to server: " + textStatus);
398 alert("Error posting to server: " + textStatus);
399 };
399 };
400 }
400 }
401 return $.ajax({url: url, data: postData, type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false})
401 return $.ajax({url: url, data: postData, type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false})
402 .done(success)
402 .done(success)
403 .fail(failure);
403 .fail(failure);
404 };
404 };
405
405
406
406
407 /**
407 /**
408 * activate .show_more links
408 * activate .show_more links
409 * the .show_more must have an id that is the the id of an element to hide prefixed with _
409 * the .show_more must have an id that is the the id of an element to hide prefixed with _
410 * the parentnode will be displayed
410 * the parentnode will be displayed
411 */
411 */
412 var show_more_event = function(){
412 var show_more_event = function(){
413 $('.show_more').click(function(e){
413 $('.show_more').click(function(e){
414 var el = e.currentTarget;
414 var el = e.currentTarget;
415 $('#' + el.id.substring(1)).hide();
415 $('#' + el.id.substring(1)).hide();
416 $(el.parentNode).show();
416 $(el.parentNode).show();
417 });
417 });
418 };
418 };
419
419
420 /**
420 /**
421 * activate .lazy-cs mouseover for showing changeset tooltip
421 * activate .lazy-cs mouseover for showing changeset tooltip
422 */
422 */
423 var show_changeset_tooltip = function(){
423 var show_changeset_tooltip = function(){
424 $('.lazy-cs').mouseover(function(e){
424 $('.lazy-cs').mouseover(function(e){
425 var $target = $(e.currentTarget);
425 var $target = $(e.currentTarget);
426 var rid = $target.attr('raw_id');
426 var rid = $target.attr('raw_id');
427 var repo_name = $target.attr('repo_name');
427 var repo_name = $target.attr('repo_name');
428 if(rid && !$target.hasClass('tooltip')){
428 if(rid && !$target.hasClass('tooltip')){
429 _show_tooltip(e, _TM['loading ...']);
429 _show_tooltip(e, _TM['loading ...']);
430 var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": rid});
430 var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": rid});
431 ajaxGET(url, function(json){
431 ajaxGET(url, function(json){
432 $target.addClass('tooltip')
432 $target.addClass('tooltip')
433 _show_tooltip(e, json['message']);
433 _show_tooltip(e, json['message']);
434 _activate_tooltip($target);
434 _activate_tooltip($target);
435 });
435 });
436 }
436 }
437 });
437 });
438 };
438 };
439
439
440 var _onSuccessFollow = function(target){
440 var _onSuccessFollow = function(target){
441 var $target = $(target);
441 var $target = $(target);
442 var $f_cnt = $('#current_followers_count');
442 var $f_cnt = $('#current_followers_count');
443 if($target.hasClass('follow')){
443 if($target.hasClass('follow')){
444 $target.attr('class', 'following');
444 $target.attr('class', 'following');
445 $target.attr('title', _TM['Stop following this repository']);
445 $target.attr('title', _TM['Stop following this repository']);
446 if($f_cnt.html()){
446 if($f_cnt.html()){
447 var cnt = Number($f_cnt.html())+1;
447 var cnt = Number($f_cnt.html())+1;
448 $f_cnt.html(cnt);
448 $f_cnt.html(cnt);
449 }
449 }
450 }
450 }
451 else{
451 else{
452 $target.attr('class', 'follow');
452 $target.attr('class', 'follow');
453 $target.attr('title', _TM['Start following this repository']);
453 $target.attr('title', _TM['Start following this repository']);
454 if($f_cnt.html()){
454 if($f_cnt.html()){
455 var cnt = Number($f_cnt.html())-1;
455 var cnt = Number($f_cnt.html())-1;
456 $f_cnt.html(cnt);
456 $f_cnt.html(cnt);
457 }
457 }
458 }
458 }
459 }
459 }
460
460
461 var toggleFollowingRepo = function(target, follows_repo_id, token, user_id){
461 var toggleFollowingRepo = function(target, follows_repo_id){
462 var args = 'follows_repo_id=' + follows_repo_id;
462 var args = 'follows_repo_id=' + follows_repo_id;
463 args += '&amp;auth_token=' + token;
464 if(user_id != undefined){
465 args +="&amp;user_id=" + user_id;
466 }
467 $.post(TOGGLE_FOLLOW_URL, args, function(data){
463 $.post(TOGGLE_FOLLOW_URL, args, function(data){
468 _onSuccessFollow(target);
464 _onSuccessFollow(target);
469 });
465 });
470 return false;
466 return false;
471 };
467 };
472
468
473 var showRepoSize = function(target, repo_name, token){
469 var showRepoSize = function(target, repo_name){
474 var args = 'auth_token=' + token;
470 var args = '';
475
471
476 if(!$("#" + target).hasClass('loaded')){
472 if(!$("#" + target).hasClass('loaded')){
477 $("#" + target).html(_TM['Loading ...']);
473 $("#" + target).html(_TM['Loading ...']);
478 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
474 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
479 $.post(url, args, function(data) {
475 $.post(url, args, function(data) {
480 $("#" + target).html(data);
476 $("#" + target).html(data);
481 $("#" + target).addClass('loaded');
477 $("#" + target).addClass('loaded');
482 });
478 });
483 }
479 }
484 return false;
480 return false;
485 };
481 };
486
482
487 /**
483 /**
488 * tooltips
484 * tooltips
489 */
485 */
490
486
491 var tooltip_activate = function(){
487 var tooltip_activate = function(){
492 $(document).ready(_init_tooltip);
488 $(document).ready(_init_tooltip);
493 };
489 };
494
490
495 var _activate_tooltip = function($tt){
491 var _activate_tooltip = function($tt){
496 $tt.mouseover(_show_tooltip);
492 $tt.mouseover(_show_tooltip);
497 $tt.mousemove(_move_tooltip);
493 $tt.mousemove(_move_tooltip);
498 $tt.mouseout(_close_tooltip);
494 $tt.mouseout(_close_tooltip);
499 };
495 };
500
496
501 var _init_tooltip = function(){
497 var _init_tooltip = function(){
502 var $tipBox = $('#tip-box');
498 var $tipBox = $('#tip-box');
503 if(!$tipBox.length){
499 if(!$tipBox.length){
504 $tipBox = $('<div id="tip-box"></div>')
500 $tipBox = $('<div id="tip-box"></div>')
505 $(document.body).append($tipBox);
501 $(document.body).append($tipBox);
506 }
502 }
507
503
508 $tipBox.hide();
504 $tipBox.hide();
509 $tipBox.css('position', 'absolute');
505 $tipBox.css('position', 'absolute');
510 $tipBox.css('max-width', '600px');
506 $tipBox.css('max-width', '600px');
511
507
512 _activate_tooltip($('.tooltip'));
508 _activate_tooltip($('.tooltip'));
513 };
509 };
514
510
515 var _show_tooltip = function(e, tipText){
511 var _show_tooltip = function(e, tipText){
516 e.stopImmediatePropagation();
512 e.stopImmediatePropagation();
517 var el = e.currentTarget;
513 var el = e.currentTarget;
518 if(tipText){
514 if(tipText){
519 // just use it
515 // just use it
520 } else if(el.tagName.toLowerCase() === 'img'){
516 } else if(el.tagName.toLowerCase() === 'img'){
521 tipText = el.alt ? el.alt : '';
517 tipText = el.alt ? el.alt : '';
522 } else {
518 } else {
523 tipText = el.title ? el.title : '';
519 tipText = el.title ? el.title : '';
524 }
520 }
525
521
526 if(tipText !== ''){
522 if(tipText !== ''){
527 // save org title
523 // save org title
528 $(el).attr('tt_title', tipText);
524 $(el).attr('tt_title', tipText);
529 // reset title to not show org tooltips
525 // reset title to not show org tooltips
530 $(el).attr('title', '');
526 $(el).attr('title', '');
531
527
532 var $tipBox = $('#tip-box');
528 var $tipBox = $('#tip-box');
533 $tipBox.html(tipText);
529 $tipBox.html(tipText);
534 $tipBox.css('display', 'block');
530 $tipBox.css('display', 'block');
535 }
531 }
536 };
532 };
537
533
538 var _move_tooltip = function(e){
534 var _move_tooltip = function(e){
539 e.stopImmediatePropagation();
535 e.stopImmediatePropagation();
540 var $tipBox = $('#tip-box');
536 var $tipBox = $('#tip-box');
541 $tipBox.css('top', (e.pageY + 15) + 'px');
537 $tipBox.css('top', (e.pageY + 15) + 'px');
542 $tipBox.css('left', (e.pageX + 15) + 'px');
538 $tipBox.css('left', (e.pageX + 15) + 'px');
543 };
539 };
544
540
545 var _close_tooltip = function(e){
541 var _close_tooltip = function(e){
546 e.stopImmediatePropagation();
542 e.stopImmediatePropagation();
547 var $tipBox = $('#tip-box');
543 var $tipBox = $('#tip-box');
548 $tipBox.hide();
544 $tipBox.hide();
549 var el = e.currentTarget;
545 var el = e.currentTarget;
550 $(el).attr('title', $(el).attr('tt_title'));
546 $(el).attr('title', $(el).attr('tt_title'));
551 };
547 };
552
548
553 /**
549 /**
554 * Quick filter widget
550 * Quick filter widget
555 *
551 *
556 * @param target: filter input target
552 * @param target: filter input target
557 * @param nodes: list of nodes in html we want to filter.
553 * @param nodes: list of nodes in html we want to filter.
558 * @param display_element function that takes current node from nodes and
554 * @param display_element function that takes current node from nodes and
559 * does hide or show based on the node
555 * does hide or show based on the node
560 */
556 */
561 var q_filter = (function() {
557 var q_filter = (function() {
562 var _namespace = {};
558 var _namespace = {};
563 var namespace = function (target) {
559 var namespace = function (target) {
564 if (!(target in _namespace)) {
560 if (!(target in _namespace)) {
565 _namespace[target] = {};
561 _namespace[target] = {};
566 }
562 }
567 return _namespace[target];
563 return _namespace[target];
568 };
564 };
569 return function (target, $nodes, display_element) {
565 return function (target, $nodes, display_element) {
570 var $nodes = $nodes;
566 var $nodes = $nodes;
571 var $q_filter_field = $('#' + target);
567 var $q_filter_field = $('#' + target);
572 var F = namespace(target);
568 var F = namespace(target);
573
569
574 $q_filter_field.keyup(function (e) {
570 $q_filter_field.keyup(function (e) {
575 clearTimeout(F.filterTimeout);
571 clearTimeout(F.filterTimeout);
576 F.filterTimeout = setTimeout(F.updateFilter, 600);
572 F.filterTimeout = setTimeout(F.updateFilter, 600);
577 });
573 });
578
574
579 F.filterTimeout = null;
575 F.filterTimeout = null;
580
576
581 F.updateFilter = function () {
577 F.updateFilter = function () {
582 // Reset timeout
578 // Reset timeout
583 F.filterTimeout = null;
579 F.filterTimeout = null;
584
580
585 var obsolete = [];
581 var obsolete = [];
586
582
587 var req = $q_filter_field.val().toLowerCase();
583 var req = $q_filter_field.val().toLowerCase();
588
584
589 var showing = 0;
585 var showing = 0;
590 $nodes.each(function () {
586 $nodes.each(function () {
591 var n = this;
587 var n = this;
592 var target_element = display_element(n);
588 var target_element = display_element(n);
593 if (req && n.innerHTML.toLowerCase().indexOf(req) == -1) {
589 if (req && n.innerHTML.toLowerCase().indexOf(req) == -1) {
594 $(target_element).hide();
590 $(target_element).hide();
595 }
591 }
596 else {
592 else {
597 $(target_element).show();
593 $(target_element).show();
598 showing += 1;
594 showing += 1;
599 }
595 }
600 });
596 });
601
597
602 $('#repo_count').html(showing);
598 $('#repo_count').html(showing);
603 /* FIXME: don't hardcode */
599 /* FIXME: don't hardcode */
604 }
600 }
605 }
601 }
606 })();
602 })();
607
603
608 /* return jQuery expression with a tr with body in 3rd column and class cls and id named after the body */
604 /* return jQuery expression with a tr with body in 3rd column and class cls and id named after the body */
609 var _table_tr = function(cls, body){
605 var _table_tr = function(cls, body){
610 // like: <div class="comment" id="comment-8" line="o92"><div class="comment-wrapp">...
606 // like: <div class="comment" id="comment-8" line="o92"><div class="comment-wrapp">...
611 // except new inlines which are different ...
607 // except new inlines which are different ...
612 var comment_id = ($(body).attr('id') || 'comment-new').split('comment-')[1];
608 var comment_id = ($(body).attr('id') || 'comment-new').split('comment-')[1];
613 var tr_id = 'comment-tr-{0}'.format(comment_id);
609 var tr_id = 'comment-tr-{0}'.format(comment_id);
614 return $(('<tr id="{0}" class="{1}">'+
610 return $(('<tr id="{0}" class="{1}">'+
615 '<td class="lineno-inline new-inline"></td>'+
611 '<td class="lineno-inline new-inline"></td>'+
616 '<td class="lineno-inline old-inline"></td>'+
612 '<td class="lineno-inline old-inline"></td>'+
617 '<td>{2}</td>'+
613 '<td>{2}</td>'+
618 '</tr>').format(tr_id, cls, body));
614 '</tr>').format(tr_id, cls, body));
619 };
615 };
620
616
621 /** return jQuery expression with new inline form based on template **/
617 /** return jQuery expression with new inline form based on template **/
622 var _createInlineForm = function(parent_tr, f_path, line) {
618 var _createInlineForm = function(parent_tr, f_path, line) {
623 var $tmpl = $('#comment-inline-form-template').html().format(f_path, line);
619 var $tmpl = $('#comment-inline-form-template').html().format(f_path, line);
624 var $form = _table_tr('comment-form-inline', $tmpl)
620 var $form = _table_tr('comment-form-inline', $tmpl)
625
621
626 // create event for hide button
622 // create event for hide button
627 $form.find('.hide-inline-form').click(function(e) {
623 $form.find('.hide-inline-form').click(function(e) {
628 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
624 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
629 if($(newtr).next().hasClass('inline-comments-button')){
625 if($(newtr).next().hasClass('inline-comments-button')){
630 $(newtr).next().show();
626 $(newtr).next().show();
631 }
627 }
632 $(newtr).remove();
628 $(newtr).remove();
633 $(parent_tr).removeClass('form-open');
629 $(parent_tr).removeClass('form-open');
634 $(parent_tr).removeClass('hl-comment');
630 $(parent_tr).removeClass('hl-comment');
635 });
631 });
636
632
637 return $form
633 return $form
638 };
634 };
639
635
640 /**
636 /**
641 * Inject inline comment for an given TR. This tr should always be a .line .
637 * Inject inline comment for an given TR. This tr should always be a .line .
642 * The form will be inject after any comments.
638 * The form will be inject after any comments.
643 */
639 */
644 var injectInlineForm = function(tr){
640 var injectInlineForm = function(tr){
645 var $tr = $(tr);
641 var $tr = $(tr);
646 if(!$tr.hasClass('line')){
642 if(!$tr.hasClass('line')){
647 return
643 return
648 }
644 }
649 var submit_url = AJAX_COMMENT_URL;
645 var submit_url = AJAX_COMMENT_URL;
650 var $td = $tr.find('.code');
646 var $td = $tr.find('.code');
651 if($tr.hasClass('form-open') || $tr.hasClass('context') || $td.hasClass('no-comment')){
647 if($tr.hasClass('form-open') || $tr.hasClass('context') || $td.hasClass('no-comment')){
652 return
648 return
653 }
649 }
654 $tr.addClass('form-open hl-comment');
650 $tr.addClass('form-open hl-comment');
655 var $node = $tr.parent().parent().parent().find('.full_f_path');
651 var $node = $tr.parent().parent().parent().find('.full_f_path');
656 var f_path = $node.attr('path');
652 var f_path = $node.attr('path');
657 var lineno = _getLineNo(tr);
653 var lineno = _getLineNo(tr);
658 var $form = _createInlineForm(tr, f_path, lineno, submit_url);
654 var $form = _createInlineForm(tr, f_path, lineno, submit_url);
659
655
660 var $parent = $tr;
656 var $parent = $tr;
661 while ($parent.next().hasClass('inline-comments')){
657 while ($parent.next().hasClass('inline-comments')){
662 var $parent = $parent.next();
658 var $parent = $parent.next();
663 }
659 }
664 $form.insertAfter($parent);
660 $form.insertAfter($parent);
665 var $overlay = $form.find('.submitting-overlay');
661 var $overlay = $form.find('.submitting-overlay');
666 var $inlineform = $form.find('.inline-form');
662 var $inlineform = $form.find('.inline-form');
667
663
668 $form.submit(function(e){
664 $form.submit(function(e){
669 e.preventDefault();
665 e.preventDefault();
670
666
671 if(lineno === undefined){
667 if(lineno === undefined){
672 alert('Error submitting, line ' + lineno + ' not found.');
668 alert('Error submitting, line ' + lineno + ' not found.');
673 return;
669 return;
674 }
670 }
675 if(f_path === undefined){
671 if(f_path === undefined){
676 alert('Error submitting, file path ' + f_path + ' not found.');
672 alert('Error submitting, file path ' + f_path + ' not found.');
677 return;
673 return;
678 }
674 }
679
675
680 var text = $('#text_'+lineno).val();
676 var text = $('#text_'+lineno).val();
681 if(text == ""){
677 if(text == ""){
682 return;
678 return;
683 }
679 }
684
680
685 $overlay.show();
681 $overlay.show();
686
682
687 var success = function(json_data){
683 var success = function(json_data){
688 $tr.removeClass('form-open');
684 $tr.removeClass('form-open');
689 $form.remove();
685 $form.remove();
690 _renderInlineComment(json_data);
686 _renderInlineComment(json_data);
691 };
687 };
692 var postData = {
688 var postData = {
693 'text': text,
689 'text': text,
694 'f_path': f_path,
690 'f_path': f_path,
695 'line': lineno
691 'line': lineno
696 };
692 };
697 ajaxPOST(submit_url, postData, success);
693 ajaxPOST(submit_url, postData, success);
698 });
694 });
699
695
700 $('#preview-btn_'+lineno).click(function(e){
696 $('#preview-btn_'+lineno).click(function(e){
701 var text = $('#text_'+lineno).val();
697 var text = $('#text_'+lineno).val();
702 if(!text){
698 if(!text){
703 return
699 return
704 }
700 }
705 $('#preview-box_'+lineno).addClass('unloaded');
701 $('#preview-box_'+lineno).addClass('unloaded');
706 $('#preview-box_'+lineno).html(_TM['Loading ...']);
702 $('#preview-box_'+lineno).html(_TM['Loading ...']);
707 $('#edit-container_'+lineno).hide();
703 $('#edit-container_'+lineno).hide();
708 $('#edit-btn_'+lineno).show();
704 $('#edit-btn_'+lineno).show();
709 $('#preview-container_'+lineno).show();
705 $('#preview-container_'+lineno).show();
710 $('#preview-btn_'+lineno).hide();
706 $('#preview-btn_'+lineno).hide();
711
707
712 var url = pyroutes.url('changeset_comment_preview', {'repo_name': REPO_NAME});
708 var url = pyroutes.url('changeset_comment_preview', {'repo_name': REPO_NAME});
713 var post_data = {'text': text};
709 var post_data = {'text': text};
714 ajaxPOST(url, post_data, function(html){
710 ajaxPOST(url, post_data, function(html){
715 $('#preview-box_'+lineno).html(html);
711 $('#preview-box_'+lineno).html(html);
716 $('#preview-box_'+lineno).removeClass('unloaded');
712 $('#preview-box_'+lineno).removeClass('unloaded');
717 })
713 })
718 })
714 })
719 $('#edit-btn_'+lineno).click(function(e){
715 $('#edit-btn_'+lineno).click(function(e){
720 $('#edit-container_'+lineno).show();
716 $('#edit-container_'+lineno).show();
721 $('#edit-btn_'+lineno).hide();
717 $('#edit-btn_'+lineno).hide();
722 $('#preview-container_'+lineno).hide();
718 $('#preview-container_'+lineno).hide();
723 $('#preview-btn_'+lineno).show();
719 $('#preview-btn_'+lineno).show();
724 })
720 })
725
721
726 setTimeout(function(){
722 setTimeout(function(){
727 // callbacks
723 // callbacks
728 tooltip_activate();
724 tooltip_activate();
729 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
725 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
730 _USERS_AC_DATA, _GROUPS_AC_DATA);
726 _USERS_AC_DATA, _GROUPS_AC_DATA);
731 $('#text_'+lineno).focus();
727 $('#text_'+lineno).focus();
732 },10)
728 },10)
733 };
729 };
734
730
735 var deleteComment = function(comment_id){
731 var deleteComment = function(comment_id){
736 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
732 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
737 var postData = {'_method':'delete'};
733 var postData = {'_method':'delete'};
738 var success = function(o){
734 var success = function(o){
739 var $deleted = $('#comment-tr-'+comment_id);
735 var $deleted = $('#comment-tr-'+comment_id);
740 var $prev = $deleted.prev('tr');
736 var $prev = $deleted.prev('tr');
741 $deleted.remove();
737 $deleted.remove();
742 _placeAddButton($prev);
738 _placeAddButton($prev);
743 }
739 }
744 ajaxPOST(url,postData,success);
740 ajaxPOST(url,postData,success);
745 }
741 }
746
742
747 var _getLineNo = function(tr) {
743 var _getLineNo = function(tr) {
748 var line;
744 var line;
749 var o = $(tr).children()[0].id.split('_');
745 var o = $(tr).children()[0].id.split('_');
750 var n = $(tr).children()[1].id.split('_');
746 var n = $(tr).children()[1].id.split('_');
751
747
752 if (n.length >= 2) {
748 if (n.length >= 2) {
753 line = n[n.length-1];
749 line = n[n.length-1];
754 } else if (o.length >= 2) {
750 } else if (o.length >= 2) {
755 line = o[o.length-1];
751 line = o[o.length-1];
756 }
752 }
757
753
758 return line
754 return line
759 };
755 };
760
756
761 var _placeAddButton = function($line_tr){
757 var _placeAddButton = function($line_tr){
762 var $tr = $line_tr;
758 var $tr = $line_tr;
763 while ($tr.next().hasClass('inline-comments')){
759 while ($tr.next().hasClass('inline-comments')){
764 $tr.find('.add-comment').remove();
760 $tr.find('.add-comment').remove();
765 $tr = $tr.next();
761 $tr = $tr.next();
766 }
762 }
767 $tr.find('.add-comment').remove();
763 $tr.find('.add-comment').remove();
768 var label = TRANSLATION_MAP['Add Another Comment'];
764 var label = TRANSLATION_MAP['Add Another Comment'];
769 var $html_el = $('<div class="add-comment"><span class="btn btn-mini">{0}</span></div>'.format(label));
765 var $html_el = $('<div class="add-comment"><span class="btn btn-mini">{0}</span></div>'.format(label));
770 $html_el.click(function(e) {
766 $html_el.click(function(e) {
771 injectInlineForm($line_tr);
767 injectInlineForm($line_tr);
772 });
768 });
773 $tr.find('.comment').after($html_el);
769 $tr.find('.comment').after($html_el);
774 };
770 };
775
771
776 /**
772 /**
777 * Places the inline comment into the changeset block in proper line position
773 * Places the inline comment into the changeset block in proper line position
778 */
774 */
779 var _placeInline = function(target_id, lineno, html){
775 var _placeInline = function(target_id, lineno, html){
780 var $td = $("#{0}_{1}".format(target_id, lineno));
776 var $td = $("#{0}_{1}".format(target_id, lineno));
781 if (!$td.length){
777 if (!$td.length){
782 return false;
778 return false;
783 }
779 }
784
780
785 // check if there are comments already !
781 // check if there are comments already !
786 var $line_tr = $td.parent(); // the tr
782 var $line_tr = $td.parent(); // the tr
787 var $after_tr = $line_tr;
783 var $after_tr = $line_tr;
788 while ($after_tr.next().hasClass('inline-comments')){
784 while ($after_tr.next().hasClass('inline-comments')){
789 $after_tr = $after_tr.next();
785 $after_tr = $after_tr.next();
790 }
786 }
791 // put in the comment at the bottom
787 // put in the comment at the bottom
792 var $tr = _table_tr('inline-comments', html)
788 var $tr = _table_tr('inline-comments', html)
793 $tr.find('div.comment').addClass('inline-comment');
789 $tr.find('div.comment').addClass('inline-comment');
794 $after_tr.after($tr);
790 $after_tr.after($tr);
795
791
796 // scan nodes, and attach add button to last one
792 // scan nodes, and attach add button to last one
797 _placeAddButton($line_tr);
793 _placeAddButton($line_tr);
798 return true;
794 return true;
799 }
795 }
800
796
801 /**
797 /**
802 * make a single inline comment and place it inside
798 * make a single inline comment and place it inside
803 */
799 */
804 var _renderInlineComment = function(json_data){
800 var _renderInlineComment = function(json_data){
805 var html = json_data['rendered_text'];
801 var html = json_data['rendered_text'];
806 var lineno = json_data['line_no'];
802 var lineno = json_data['line_no'];
807 var target_id = json_data['target_id'];
803 var target_id = json_data['target_id'];
808 return _placeInline(target_id, lineno, html);
804 return _placeInline(target_id, lineno, html);
809 }
805 }
810
806
811 /**
807 /**
812 * Iterates over all the inlines, and places them inside proper blocks of data
808 * Iterates over all the inlines, and places them inside proper blocks of data
813 */
809 */
814 var renderInlineComments = function(file_comments){
810 var renderInlineComments = function(file_comments){
815 for (var f in file_comments){
811 for (var f in file_comments){
816 // holding all comments for a FILE
812 // holding all comments for a FILE
817 var box = file_comments[f];
813 var box = file_comments[f];
818
814
819 var target_id = $(box).attr('target_id');
815 var target_id = $(box).attr('target_id');
820 // actual comments with line numbers
816 // actual comments with line numbers
821 var comments = box.children;
817 var comments = box.children;
822 var obsolete_comments = [];
818 var obsolete_comments = [];
823 for(var i=0; i<comments.length; i++){
819 for(var i=0; i<comments.length; i++){
824 var data = {
820 var data = {
825 'rendered_text': comments[i].outerHTML,
821 'rendered_text': comments[i].outerHTML,
826 'line_no': $(comments[i]).attr('line'),
822 'line_no': $(comments[i]).attr('line'),
827 'target_id': target_id
823 'target_id': target_id
828 }
824 }
829 if (_renderInlineComment(data)) {
825 if (_renderInlineComment(data)) {
830 obsolete_comments.push(comments[i]);
826 obsolete_comments.push(comments[i]);
831 $(comments[i]).hide();
827 $(comments[i]).hide();
832 }else{
828 }else{
833 var txt = document.createTextNode(
829 var txt = document.createTextNode(
834 "Comment to " + YUD.getAttribute(comments[i].parentNode,'path') +
830 "Comment to " + YUD.getAttribute(comments[i].parentNode,'path') +
835 " line " + data.line_no +
831 " line " + data.line_no +
836 " which is outside the diff context:");
832 " which is outside the diff context:");
837 comments[i].insertBefore(txt, comments[i].firstChild);
833 comments[i].insertBefore(txt, comments[i].firstChild);
838 }
834 }
839 }
835 }
840 // now remove all the obsolete comments that have been copied to their
836 // now remove all the obsolete comments that have been copied to their
841 // respective locations.
837 // respective locations.
842 for (var i=0; i < obsolete_comments.length; i++) {
838 for (var i=0; i < obsolete_comments.length; i++) {
843 obsolete_comments[i].parentNode.removeChild(obsolete_comments[i]);
839 obsolete_comments[i].parentNode.removeChild(obsolete_comments[i]);
844 }
840 }
845
841
846 $(box).show();
842 $(box).show();
847 }
843 }
848 }
844 }
849
845
850 /**
846 /**
851 * Double link comments
847 * Double link comments
852 */
848 */
853 var linkInlineComments = function(firstlinks, comments){
849 var linkInlineComments = function(firstlinks, comments){
854 var $comments = $(comments);
850 var $comments = $(comments);
855 if ($comments.length > 0) {
851 if ($comments.length > 0) {
856 $(firstlinks).html('<a href="#{0}">First comment</a>'.format($comments.attr('id')));
852 $(firstlinks).html('<a href="#{0}">First comment</a>'.format($comments.attr('id')));
857 }
853 }
858 if ($comments.length <= 1) {
854 if ($comments.length <= 1) {
859 return;
855 return;
860 }
856 }
861
857
862 $comments.each(function(i, e){
858 $comments.each(function(i, e){
863 var prev = '';
859 var prev = '';
864 if (i > 0){
860 if (i > 0){
865 var prev_anchor = YUD.getAttribute(comments.item(i-1),'id');
861 var prev_anchor = YUD.getAttribute(comments.item(i-1),'id');
866 prev = '<a href="#{0}">Previous comment</a>'.format(prev_anchor);
862 prev = '<a href="#{0}">Previous comment</a>'.format(prev_anchor);
867 }
863 }
868 var next = '';
864 var next = '';
869 if (i+1 < comments.length){
865 if (i+1 < comments.length){
870 var next_anchor = YUD.getAttribute(comments.item(i+1),'id');
866 var next_anchor = YUD.getAttribute(comments.item(i+1),'id');
871 next = '<a href="#{0}">Next comment</a>'.format(next_anchor);
867 next = '<a href="#{0}">Next comment</a>'.format(next_anchor);
872 }
868 }
873 var $div = $(('<div class="prev-next-comment">'+
869 var $div = $(('<div class="prev-next-comment">'+
874 '<div class="prev-comment">{0}</div>'+
870 '<div class="prev-comment">{0}</div>'+
875 '<div class="next-comment">{1}</div>').format(prev, next));
871 '<div class="next-comment">{1}</div>').format(prev, next));
876 $div.prependTo(this);
872 $div.prependTo(this);
877 });
873 });
878 }
874 }
879
875
880 /* activate files.html stuff */
876 /* activate files.html stuff */
881 var fileBrowserListeners = function(current_url, node_list_url, url_base){
877 var fileBrowserListeners = function(current_url, node_list_url, url_base){
882 var current_url_branch = "?branch=__BRANCH__";
878 var current_url_branch = "?branch=__BRANCH__";
883
879
884 $('#stay_at_branch').on('click',function(e){
880 $('#stay_at_branch').on('click',function(e){
885 if(e.currentTarget.checked){
881 if(e.currentTarget.checked){
886 var uri = current_url_branch;
882 var uri = current_url_branch;
887 uri = uri.replace('__BRANCH__',e.currentTarget.value);
883 uri = uri.replace('__BRANCH__',e.currentTarget.value);
888 window.location = uri;
884 window.location = uri;
889 }
885 }
890 else{
886 else{
891 window.location = current_url;
887 window.location = current_url;
892 }
888 }
893 })
889 })
894
890
895 var $node_filter = $('#node_filter');
891 var $node_filter = $('#node_filter');
896
892
897 var filterTimeout = null;
893 var filterTimeout = null;
898 var nodes = null;
894 var nodes = null;
899
895
900 var initFilter = function(){
896 var initFilter = function(){
901 $('#node_filter_box_loading').show();
897 $('#node_filter_box_loading').show();
902 $('#search_activate_id').hide();
898 $('#search_activate_id').hide();
903 $('#add_node_id').hide();
899 $('#add_node_id').hide();
904 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
900 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
905 .done(function(json) {
901 .done(function(json) {
906 nodes = json.nodes;
902 nodes = json.nodes;
907 $('#node_filter_box_loading').hide();
903 $('#node_filter_box_loading').hide();
908 $('#node_filter_box').show();
904 $('#node_filter_box').show();
909 $node_filter.focus();
905 $node_filter.focus();
910 if($node_filter.hasClass('init')){
906 if($node_filter.hasClass('init')){
911 $node_filter.val('');
907 $node_filter.val('');
912 $node_filter.removeClass('init');
908 $node_filter.removeClass('init');
913 }
909 }
914 })
910 })
915 .fail(function() {
911 .fail(function() {
916 console.log('failed to load');
912 console.log('failed to load');
917 })
913 })
918 ;
914 ;
919 }
915 }
920
916
921 var updateFilter = function(e) {
917 var updateFilter = function(e) {
922 return function(){
918 return function(){
923 // Reset timeout
919 // Reset timeout
924 filterTimeout = null;
920 filterTimeout = null;
925 var query = e.currentTarget.value.toLowerCase();
921 var query = e.currentTarget.value.toLowerCase();
926 var match = [];
922 var match = [];
927 var matches = 0;
923 var matches = 0;
928 var matches_max = 20;
924 var matches_max = 20;
929 if (query != ""){
925 if (query != ""){
930 for(var i=0;i<nodes.length;i++){
926 for(var i=0;i<nodes.length;i++){
931 var pos = nodes[i].name.toLowerCase().indexOf(query)
927 var pos = nodes[i].name.toLowerCase().indexOf(query)
932 if(query && pos != -1){
928 if(query && pos != -1){
933 matches++
929 matches++
934 //show only certain amount to not kill browser
930 //show only certain amount to not kill browser
935 if (matches > matches_max){
931 if (matches > matches_max){
936 break;
932 break;
937 }
933 }
938
934
939 var n = nodes[i].name;
935 var n = nodes[i].name;
940 var t = nodes[i].type;
936 var t = nodes[i].type;
941 var n_hl = n.substring(0,pos)
937 var n_hl = n.substring(0,pos)
942 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
938 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
943 +n.substring(pos+query.length)
939 +n.substring(pos+query.length)
944 var new_url = url_base.replace('__FPATH__',n);
940 var new_url = url_base.replace('__FPATH__',n);
945 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
941 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
946 }
942 }
947 if(match.length >= matches_max){
943 if(match.length >= matches_max){
948 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
944 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
949 break;
945 break;
950 }
946 }
951 }
947 }
952 }
948 }
953 if(query != ""){
949 if(query != ""){
954 $('#tbody').hide();
950 $('#tbody').hide();
955 $('#tbody_filtered').show();
951 $('#tbody_filtered').show();
956
952
957 if (match.length==0){
953 if (match.length==0){
958 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
954 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
959 }
955 }
960
956
961 $('#tbody_filtered').html(match.join(""));
957 $('#tbody_filtered').html(match.join(""));
962 }
958 }
963 else{
959 else{
964 $('#tbody').show();
960 $('#tbody').show();
965 $('#tbody_filtered').hide();
961 $('#tbody_filtered').hide();
966 }
962 }
967 }
963 }
968 };
964 };
969
965
970 $('#filter_activate').click(function(){
966 $('#filter_activate').click(function(){
971 initFilter();
967 initFilter();
972 });
968 });
973 $node_filter.click(function(){
969 $node_filter.click(function(){
974 if($node_filter.hasClass('init')){
970 if($node_filter.hasClass('init')){
975 $node_filter.val('');
971 $node_filter.val('');
976 $node_filter.removeClass('init');
972 $node_filter.removeClass('init');
977 }
973 }
978 });
974 });
979 $node_filter.keyup(function(e){
975 $node_filter.keyup(function(e){
980 clearTimeout(filterTimeout);
976 clearTimeout(filterTimeout);
981 filterTimeout = setTimeout(updateFilter(e),600);
977 filterTimeout = setTimeout(updateFilter(e),600);
982 });
978 });
983 };
979 };
984
980
985
981
986 var initCodeMirror = function(textarea_id, resetUrl){
982 var initCodeMirror = function(textarea_id, resetUrl){
987 var myCodeMirror = CodeMirror.fromTextArea($('#' + textarea_id)[0], {
983 var myCodeMirror = CodeMirror.fromTextArea($('#' + textarea_id)[0], {
988 mode: "null",
984 mode: "null",
989 lineNumbers: true,
985 lineNumbers: true,
990 indentUnit: 4,
986 indentUnit: 4,
991 autofocus: true
987 autofocus: true
992 });
988 });
993 CodeMirror.modeURL = "/codemirror/mode/%N/%N.js";
989 CodeMirror.modeURL = "/codemirror/mode/%N/%N.js";
994
990
995 $('#reset').click(function(e){
991 $('#reset').click(function(e){
996 window.location=resetUrl;
992 window.location=resetUrl;
997 });
993 });
998
994
999 $('#file_enable').click(function(){
995 $('#file_enable').click(function(){
1000 $('#editor_container').show();
996 $('#editor_container').show();
1001 $('#upload_file_container').hide();
997 $('#upload_file_container').hide();
1002 $('#filename_container').show();
998 $('#filename_container').show();
1003 $('#set_mode_header').show();
999 $('#set_mode_header').show();
1004 });
1000 });
1005
1001
1006 $('#upload_file_enable').click(function(){
1002 $('#upload_file_enable').click(function(){
1007 $('#editor_container').hide();
1003 $('#editor_container').hide();
1008 $('#upload_file_container').show();
1004 $('#upload_file_container').show();
1009 $('#filename_container').hide();
1005 $('#filename_container').hide();
1010 $('#set_mode_header').hide();
1006 $('#set_mode_header').hide();
1011 });
1007 });
1012
1008
1013 return myCodeMirror
1009 return myCodeMirror
1014 };
1010 };
1015
1011
1016 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
1012 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
1017 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
1013 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
1018 }
1014 }
1019
1015
1020
1016
1021 var _getIdentNode = function(n){
1017 var _getIdentNode = function(n){
1022 //iterate thrugh nodes until matching interesting node
1018 //iterate thrugh nodes until matching interesting node
1023
1019
1024 if (typeof n == 'undefined'){
1020 if (typeof n == 'undefined'){
1025 return -1
1021 return -1
1026 }
1022 }
1027
1023
1028 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
1024 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
1029 return n
1025 return n
1030 }
1026 }
1031 else{
1027 else{
1032 return _getIdentNode(n.parentNode);
1028 return _getIdentNode(n.parentNode);
1033 }
1029 }
1034 };
1030 };
1035
1031
1036 /* generate links for multi line selects that can be shown by files.html page_highlights.
1032 /* generate links for multi line selects that can be shown by files.html page_highlights.
1037 * This is a mouseup handler for hlcode from CodeHtmlFormatter and pygmentize */
1033 * This is a mouseup handler for hlcode from CodeHtmlFormatter and pygmentize */
1038 var getSelectionLink = function(e) {
1034 var getSelectionLink = function(e) {
1039 //get selection from start/to nodes
1035 //get selection from start/to nodes
1040 if (typeof window.getSelection != "undefined") {
1036 if (typeof window.getSelection != "undefined") {
1041 s = window.getSelection();
1037 s = window.getSelection();
1042
1038
1043 var from = _getIdentNode(s.anchorNode);
1039 var from = _getIdentNode(s.anchorNode);
1044 var till = _getIdentNode(s.focusNode);
1040 var till = _getIdentNode(s.focusNode);
1045
1041
1046 var f_int = parseInt(from.id.replace('L',''));
1042 var f_int = parseInt(from.id.replace('L',''));
1047 var t_int = parseInt(till.id.replace('L',''));
1043 var t_int = parseInt(till.id.replace('L',''));
1048
1044
1049 var yoffset = 35;
1045 var yoffset = 35;
1050 var ranges = [parseInt(from.id.replace('L','')), parseInt(till.id.replace('L',''))];
1046 var ranges = [parseInt(from.id.replace('L','')), parseInt(till.id.replace('L',''))];
1051 if (ranges[0] > ranges[1]){
1047 if (ranges[0] > ranges[1]){
1052 //highlight from bottom
1048 //highlight from bottom
1053 yoffset = -yoffset;
1049 yoffset = -yoffset;
1054 ranges = [ranges[1], ranges[0]];
1050 ranges = [ranges[1], ranges[0]];
1055 }
1051 }
1056 var $hl_div = $('div#linktt');
1052 var $hl_div = $('div#linktt');
1057 // if we select more than 2 lines
1053 // if we select more than 2 lines
1058 if (ranges[0] != ranges[1]){
1054 if (ranges[0] != ranges[1]){
1059 if ($hl_div.length) {
1055 if ($hl_div.length) {
1060 $hl_div.html('');
1056 $hl_div.html('');
1061 } else {
1057 } else {
1062 $hl_div = $('<div id="linktt" class="hl-tip-box">');
1058 $hl_div = $('<div id="linktt" class="hl-tip-box">');
1063 $('body').prepend($hl_div);
1059 $('body').prepend($hl_div);
1064 }
1060 }
1065
1061
1066 $hl_div.append($('<a>').html(_TM['Selection link']).attr('href', location.href.substring(0, location.href.indexOf('#')) + '#L' + ranges[0] + '-'+ranges[1]));
1062 $hl_div.append($('<a>').html(_TM['Selection link']).attr('href', location.href.substring(0, location.href.indexOf('#')) + '#L' + ranges[0] + '-'+ranges[1]));
1067 var xy = $(till).offset();
1063 var xy = $(till).offset();
1068 $hl_div.css('top', (xy.top + yoffset) + 'px').css('left', xy.left + 'px');
1064 $hl_div.css('top', (xy.top + yoffset) + 'px').css('left', xy.left + 'px');
1069 $hl_div.show();
1065 $hl_div.show();
1070 }
1066 }
1071 else{
1067 else{
1072 $hl_div.hide();
1068 $hl_div.hide();
1073 }
1069 }
1074 }
1070 }
1075 };
1071 };
1076
1072
1077 var deleteNotification = function(url, notification_id, callbacks){
1073 var deleteNotification = function(url, notification_id, callbacks){
1078 var success = function(o){
1074 var success = function(o){
1079 $("#notification_"+notification_id).remove();
1075 $("#notification_"+notification_id).remove();
1080 _run_callbacks(callbacks);
1076 _run_callbacks(callbacks);
1081 };
1077 };
1082 var failure = function(o){
1078 var failure = function(o){
1083 alert("deleteNotification failure");
1079 alert("deleteNotification failure");
1084 };
1080 };
1085 var postData = {'_method': 'delete'};
1081 var postData = {'_method': 'delete'};
1086 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1082 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1087 ajaxPOST(sUrl, postData, success, failure);
1083 ajaxPOST(sUrl, postData, success, failure);
1088 };
1084 };
1089
1085
1090 var readNotification = function(url, notification_id, callbacks){
1086 var readNotification = function(url, notification_id, callbacks){
1091 var success = function(o){
1087 var success = function(o){
1092 var $obj = $("#notification_"+notification_id);
1088 var $obj = $("#notification_"+notification_id);
1093 $obj.removeClass('unread');
1089 $obj.removeClass('unread');
1094 $obj.find('.read-notification').remove();
1090 $obj.find('.read-notification').remove();
1095 _run_callbacks(callbacks);
1091 _run_callbacks(callbacks);
1096 };
1092 };
1097 var failure = function(o){
1093 var failure = function(o){
1098 alert("readNotification failure");
1094 alert("readNotification failure");
1099 };
1095 };
1100 var postData = {'_method': 'put'};
1096 var postData = {'_method': 'put'};
1101 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1097 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1102 ajaxPOST(sUrl, postData, success, failure);
1098 ajaxPOST(sUrl, postData, success, failure);
1103 };
1099 };
1104
1100
1105 /** MEMBERS AUTOCOMPLETE WIDGET **/
1101 /** MEMBERS AUTOCOMPLETE WIDGET **/
1106
1102
1107 var _MembersAutoComplete = function (divid, cont, users_list, groups_list) {
1103 var _MembersAutoComplete = function (divid, cont, users_list, groups_list) {
1108 var myUsers = users_list;
1104 var myUsers = users_list;
1109 var myGroups = groups_list;
1105 var myGroups = groups_list;
1110
1106
1111 // Define a custom search function for the DataSource of users
1107 // Define a custom search function for the DataSource of users
1112 var matchUsers = function (sQuery) {
1108 var matchUsers = function (sQuery) {
1113 // Case insensitive matching
1109 // Case insensitive matching
1114 var query = sQuery.toLowerCase();
1110 var query = sQuery.toLowerCase();
1115 var i = 0;
1111 var i = 0;
1116 var l = myUsers.length;
1112 var l = myUsers.length;
1117 var matches = [];
1113 var matches = [];
1118
1114
1119 // Match against each name of each contact
1115 // Match against each name of each contact
1120 for (; i < l; i++) {
1116 for (; i < l; i++) {
1121 var contact = myUsers[i];
1117 var contact = myUsers[i];
1122 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1118 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1123 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1119 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1124 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1120 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1125 matches[matches.length] = contact;
1121 matches[matches.length] = contact;
1126 }
1122 }
1127 }
1123 }
1128 return matches;
1124 return matches;
1129 };
1125 };
1130
1126
1131 // Define a custom search function for the DataSource of userGroups
1127 // Define a custom search function for the DataSource of userGroups
1132 var matchGroups = function (sQuery) {
1128 var matchGroups = function (sQuery) {
1133 // Case insensitive matching
1129 // Case insensitive matching
1134 var query = sQuery.toLowerCase();
1130 var query = sQuery.toLowerCase();
1135 var i = 0;
1131 var i = 0;
1136 var l = myGroups.length;
1132 var l = myGroups.length;
1137 var matches = [];
1133 var matches = [];
1138
1134
1139 // Match against each name of each contact
1135 // Match against each name of each contact
1140 for (; i < l; i++) {
1136 for (; i < l; i++) {
1141 var matched_group = myGroups[i];
1137 var matched_group = myGroups[i];
1142 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1138 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1143 matches[matches.length] = matched_group;
1139 matches[matches.length] = matched_group;
1144 }
1140 }
1145 }
1141 }
1146 return matches;
1142 return matches;
1147 };
1143 };
1148
1144
1149 //match all
1145 //match all
1150 var matchAll = function (sQuery) {
1146 var matchAll = function (sQuery) {
1151 var u = matchUsers(sQuery);
1147 var u = matchUsers(sQuery);
1152 var g = matchGroups(sQuery);
1148 var g = matchGroups(sQuery);
1153 return u.concat(g);
1149 return u.concat(g);
1154 };
1150 };
1155
1151
1156 // DataScheme for members
1152 // DataScheme for members
1157 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
1153 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
1158 memberDS.responseSchema = {
1154 memberDS.responseSchema = {
1159 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk", "gravatar_size"]
1155 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk", "gravatar_size"]
1160 };
1156 };
1161
1157
1162 // DataScheme for owner
1158 // DataScheme for owner
1163 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1159 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1164 ownerDS.responseSchema = {
1160 ownerDS.responseSchema = {
1165 fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
1161 fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
1166 };
1162 };
1167
1163
1168 // Instantiate AutoComplete for perms
1164 // Instantiate AutoComplete for perms
1169 var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
1165 var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
1170 membersAC.useShadow = false;
1166 membersAC.useShadow = false;
1171 membersAC.resultTypeList = false;
1167 membersAC.resultTypeList = false;
1172 membersAC.animVert = false;
1168 membersAC.animVert = false;
1173 membersAC.animHoriz = false;
1169 membersAC.animHoriz = false;
1174 membersAC.animSpeed = 0.1;
1170 membersAC.animSpeed = 0.1;
1175
1171
1176 // Instantiate AutoComplete for owner
1172 // Instantiate AutoComplete for owner
1177 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
1173 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
1178 ownerAC.useShadow = false;
1174 ownerAC.useShadow = false;
1179 ownerAC.resultTypeList = false;
1175 ownerAC.resultTypeList = false;
1180 ownerAC.animVert = false;
1176 ownerAC.animVert = false;
1181 ownerAC.animHoriz = false;
1177 ownerAC.animHoriz = false;
1182 ownerAC.animSpeed = 0.1;
1178 ownerAC.animSpeed = 0.1;
1183
1179
1184 // Helper highlight function for the formatter
1180 // Helper highlight function for the formatter
1185 var highlightMatch = function (full, snippet, matchindex) {
1181 var highlightMatch = function (full, snippet, matchindex) {
1186 return full.substring(0, matchindex)
1182 return full.substring(0, matchindex)
1187 + "<span class='match'>"
1183 + "<span class='match'>"
1188 + full.substr(matchindex, snippet.length)
1184 + full.substr(matchindex, snippet.length)
1189 + "</span>" + full.substring(matchindex + snippet.length);
1185 + "</span>" + full.substring(matchindex + snippet.length);
1190 };
1186 };
1191
1187
1192 // Custom formatter to highlight the matching letters
1188 // Custom formatter to highlight the matching letters
1193 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
1189 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
1194 var query = sQuery.toLowerCase();
1190 var query = sQuery.toLowerCase();
1195 var _gravatar = function(res, em, size, group){
1191 var _gravatar = function(res, em, size, group){
1196 var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
1192 var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
1197 if (!em) {
1193 if (!em) {
1198 elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
1194 elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
1199 }
1195 }
1200 if (group !== undefined){
1196 if (group !== undefined){
1201 elem = '<i class="perm-gravatar-ac icon-users"></i>'
1197 elem = '<i class="perm-gravatar-ac icon-users"></i>'
1202 }
1198 }
1203 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
1199 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
1204 return tmpl.format(elem,res)
1200 return tmpl.format(elem,res)
1205 }
1201 }
1206 // group
1202 // group
1207 if (oResultData.grname != undefined) {
1203 if (oResultData.grname != undefined) {
1208 var grname = oResultData.grname;
1204 var grname = oResultData.grname;
1209 var grmembers = oResultData.grmembers;
1205 var grmembers = oResultData.grmembers;
1210 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1206 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1211 var grprefix = "{0}: ".format(_TM['Group']);
1207 var grprefix = "{0}: ".format(_TM['Group']);
1212 var grsuffix = " (" + grmembers + " )";
1208 var grsuffix = " (" + grmembers + " )";
1213 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1209 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1214
1210
1215 if (grnameMatchIndex > -1) {
1211 if (grnameMatchIndex > -1) {
1216 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,null,true);
1212 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,null,true);
1217 }
1213 }
1218 return _gravatar(grprefix + oResultData.grname + grsuffix, null, null, true);
1214 return _gravatar(grprefix + oResultData.grname + grsuffix, null, null, true);
1219 // Users
1215 // Users
1220 } else if (oResultData.nname != undefined) {
1216 } else if (oResultData.nname != undefined) {
1221 var fname = oResultData.fname || "";
1217 var fname = oResultData.fname || "";
1222 var lname = oResultData.lname || "";
1218 var lname = oResultData.lname || "";
1223 var nname = oResultData.nname;
1219 var nname = oResultData.nname;
1224
1220
1225 // Guard against null value
1221 // Guard against null value
1226 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1222 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1227 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1223 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1228 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1224 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1229 displayfname, displaylname, displaynname;
1225 displayfname, displaylname, displaynname;
1230
1226
1231 if (fnameMatchIndex > -1) {
1227 if (fnameMatchIndex > -1) {
1232 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1228 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1233 } else {
1229 } else {
1234 displayfname = fname;
1230 displayfname = fname;
1235 }
1231 }
1236
1232
1237 if (lnameMatchIndex > -1) {
1233 if (lnameMatchIndex > -1) {
1238 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1234 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1239 } else {
1235 } else {
1240 displaylname = lname;
1236 displaylname = lname;
1241 }
1237 }
1242
1238
1243 if (nnameMatchIndex > -1) {
1239 if (nnameMatchIndex > -1) {
1244 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1240 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1245 } else {
1241 } else {
1246 displaynname = nname ? "(" + nname + ")" : "";
1242 displaynname = nname ? "(" + nname + ")" : "";
1247 }
1243 }
1248
1244
1249 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1245 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1250 } else {
1246 } else {
1251 return '';
1247 return '';
1252 }
1248 }
1253 };
1249 };
1254 membersAC.formatResult = custom_formatter;
1250 membersAC.formatResult = custom_formatter;
1255 ownerAC.formatResult = custom_formatter;
1251 ownerAC.formatResult = custom_formatter;
1256
1252
1257 var myHandler = function (sType, aArgs) {
1253 var myHandler = function (sType, aArgs) {
1258 var nextId = divid.split('perm_new_member_name_')[1];
1254 var nextId = divid.split('perm_new_member_name_')[1];
1259 var myAC = aArgs[0]; // reference back to the AC instance
1255 var myAC = aArgs[0]; // reference back to the AC instance
1260 var elLI = aArgs[1]; // reference to the selected LI element
1256 var elLI = aArgs[1]; // reference to the selected LI element
1261 var oData = aArgs[2]; // object literal of selected item's result data
1257 var oData = aArgs[2]; // object literal of selected item's result data
1262 //fill the autocomplete with value
1258 //fill the autocomplete with value
1263 if (oData.nname != undefined) {
1259 if (oData.nname != undefined) {
1264 //users
1260 //users
1265 myAC.getInputEl().value = oData.nname;
1261 myAC.getInputEl().value = oData.nname;
1266 $('#perm_new_member_type_'+nextId).val('user');
1262 $('#perm_new_member_type_'+nextId).val('user');
1267 } else {
1263 } else {
1268 //groups
1264 //groups
1269 myAC.getInputEl().value = oData.grname;
1265 myAC.getInputEl().value = oData.grname;
1270 $('#perm_new_member_type_'+nextId).val('users_group');
1266 $('#perm_new_member_type_'+nextId).val('users_group');
1271 }
1267 }
1272 };
1268 };
1273
1269
1274 membersAC.itemSelectEvent.subscribe(myHandler);
1270 membersAC.itemSelectEvent.subscribe(myHandler);
1275 if(ownerAC.itemSelectEvent){
1271 if(ownerAC.itemSelectEvent){
1276 ownerAC.itemSelectEvent.subscribe(myHandler);
1272 ownerAC.itemSelectEvent.subscribe(myHandler);
1277 }
1273 }
1278
1274
1279 return {
1275 return {
1280 memberDS: memberDS,
1276 memberDS: memberDS,
1281 ownerDS: ownerDS,
1277 ownerDS: ownerDS,
1282 membersAC: membersAC,
1278 membersAC: membersAC,
1283 ownerAC: ownerAC
1279 ownerAC: ownerAC
1284 };
1280 };
1285 }
1281 }
1286
1282
1287 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1283 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1288 var myUsers = users_list;
1284 var myUsers = users_list;
1289 var myGroups = groups_list;
1285 var myGroups = groups_list;
1290
1286
1291 // Define a custom search function for the DataSource of users
1287 // Define a custom search function for the DataSource of users
1292 var matchUsers = function (sQuery) {
1288 var matchUsers = function (sQuery) {
1293 var org_sQuery = sQuery;
1289 var org_sQuery = sQuery;
1294 if(this.mentionQuery == null){
1290 if(this.mentionQuery == null){
1295 return []
1291 return []
1296 }
1292 }
1297 sQuery = this.mentionQuery;
1293 sQuery = this.mentionQuery;
1298 // Case insensitive matching
1294 // Case insensitive matching
1299 var query = sQuery.toLowerCase();
1295 var query = sQuery.toLowerCase();
1300 var i = 0;
1296 var i = 0;
1301 var l = myUsers.length;
1297 var l = myUsers.length;
1302 var matches = [];
1298 var matches = [];
1303
1299
1304 // Match against each name of each contact
1300 // Match against each name of each contact
1305 for (; i < l; i++) {
1301 for (; i < l; i++) {
1306 var contact = myUsers[i];
1302 var contact = myUsers[i];
1307 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1303 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1308 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1304 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1309 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1305 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1310 matches[matches.length] = contact;
1306 matches[matches.length] = contact;
1311 }
1307 }
1312 }
1308 }
1313 return matches
1309 return matches
1314 };
1310 };
1315
1311
1316 //match all
1312 //match all
1317 var matchAll = function (sQuery) {
1313 var matchAll = function (sQuery) {
1318 return matchUsers(sQuery);
1314 return matchUsers(sQuery);
1319 };
1315 };
1320
1316
1321 // DataScheme for owner
1317 // DataScheme for owner
1322 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1318 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1323
1319
1324 ownerDS.responseSchema = {
1320 ownerDS.responseSchema = {
1325 fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
1321 fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
1326 };
1322 };
1327
1323
1328 // Instantiate AutoComplete for mentions
1324 // Instantiate AutoComplete for mentions
1329 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1325 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1330 ownerAC.useShadow = false;
1326 ownerAC.useShadow = false;
1331 ownerAC.resultTypeList = false;
1327 ownerAC.resultTypeList = false;
1332 ownerAC.suppressInputUpdate = true;
1328 ownerAC.suppressInputUpdate = true;
1333 ownerAC.animVert = false;
1329 ownerAC.animVert = false;
1334 ownerAC.animHoriz = false;
1330 ownerAC.animHoriz = false;
1335 ownerAC.animSpeed = 0.1;
1331 ownerAC.animSpeed = 0.1;
1336
1332
1337 // Helper highlight function for the formatter
1333 // Helper highlight function for the formatter
1338 var highlightMatch = function (full, snippet, matchindex) {
1334 var highlightMatch = function (full, snippet, matchindex) {
1339 return full.substring(0, matchindex)
1335 return full.substring(0, matchindex)
1340 + "<span class='match'>"
1336 + "<span class='match'>"
1341 + full.substr(matchindex, snippet.length)
1337 + full.substr(matchindex, snippet.length)
1342 + "</span>" + full.substring(matchindex + snippet.length);
1338 + "</span>" + full.substring(matchindex + snippet.length);
1343 };
1339 };
1344
1340
1345 // Custom formatter to highlight the matching letters
1341 // Custom formatter to highlight the matching letters
1346 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1342 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1347 var org_sQuery = sQuery;
1343 var org_sQuery = sQuery;
1348 if(this.dataSource.mentionQuery != null){
1344 if(this.dataSource.mentionQuery != null){
1349 sQuery = this.dataSource.mentionQuery;
1345 sQuery = this.dataSource.mentionQuery;
1350 }
1346 }
1351
1347
1352 var query = sQuery.toLowerCase();
1348 var query = sQuery.toLowerCase();
1353 var _gravatar = function(res, em, size, group){
1349 var _gravatar = function(res, em, size, group){
1354 var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
1350 var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
1355 if (!em) {
1351 if (!em) {
1356 elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
1352 elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
1357 }
1353 }
1358 if (group !== undefined){
1354 if (group !== undefined){
1359 elem = '<i class="perm-gravatar-ac icon-users"></i>'
1355 elem = '<i class="perm-gravatar-ac icon-users"></i>'
1360 }
1356 }
1361 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
1357 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
1362 return tmpl.format(elem,res)
1358 return tmpl.format(elem,res)
1363 }
1359 }
1364 if (oResultData.nname != undefined) {
1360 if (oResultData.nname != undefined) {
1365 var fname = oResultData.fname || "";
1361 var fname = oResultData.fname || "";
1366 var lname = oResultData.lname || "";
1362 var lname = oResultData.lname || "";
1367 var nname = oResultData.nname;
1363 var nname = oResultData.nname;
1368
1364
1369 // Guard against null value
1365 // Guard against null value
1370 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1366 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1371 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1367 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1372 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1368 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1373 displayfname, displaylname, displaynname;
1369 displayfname, displaylname, displaynname;
1374
1370
1375 if (fnameMatchIndex > -1) {
1371 if (fnameMatchIndex > -1) {
1376 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1372 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1377 } else {
1373 } else {
1378 displayfname = fname;
1374 displayfname = fname;
1379 }
1375 }
1380
1376
1381 if (lnameMatchIndex > -1) {
1377 if (lnameMatchIndex > -1) {
1382 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1378 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1383 } else {
1379 } else {
1384 displaylname = lname;
1380 displaylname = lname;
1385 }
1381 }
1386
1382
1387 if (nnameMatchIndex > -1) {
1383 if (nnameMatchIndex > -1) {
1388 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1384 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1389 } else {
1385 } else {
1390 displaynname = nname ? "(" + nname + ")" : "";
1386 displaynname = nname ? "(" + nname + ")" : "";
1391 }
1387 }
1392
1388
1393 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1389 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1394 } else {
1390 } else {
1395 return '';
1391 return '';
1396 }
1392 }
1397 };
1393 };
1398
1394
1399 if(ownerAC.itemSelectEvent){
1395 if(ownerAC.itemSelectEvent){
1400 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1396 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1401 var myAC = aArgs[0]; // reference back to the AC instance
1397 var myAC = aArgs[0]; // reference back to the AC instance
1402 var elLI = aArgs[1]; // reference to the selected LI element
1398 var elLI = aArgs[1]; // reference to the selected LI element
1403 var oData = aArgs[2]; // object literal of selected item's result data
1399 var oData = aArgs[2]; // object literal of selected item's result data
1404 //fill the autocomplete with value
1400 //fill the autocomplete with value
1405 if (oData.nname != undefined) {
1401 if (oData.nname != undefined) {
1406 //users
1402 //users
1407 //Replace the mention name with replaced
1403 //Replace the mention name with replaced
1408 var re = new RegExp();
1404 var re = new RegExp();
1409 var org = myAC.getInputEl().value;
1405 var org = myAC.getInputEl().value;
1410 var chunks = myAC.dataSource.chunks
1406 var chunks = myAC.dataSource.chunks
1411 // replace middle chunk(the search term) with actuall match
1407 // replace middle chunk(the search term) with actuall match
1412 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1408 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1413 '@'+oData.nname+' ');
1409 '@'+oData.nname+' ');
1414 myAC.getInputEl().value = chunks.join('')
1410 myAC.getInputEl().value = chunks.join('')
1415 myAC.getInputEl().focus(); // Y U NO WORK !?
1411 myAC.getInputEl().focus(); // Y U NO WORK !?
1416 } else {
1412 } else {
1417 //groups
1413 //groups
1418 myAC.getInputEl().value = oData.grname;
1414 myAC.getInputEl().value = oData.grname;
1419 $('#perm_new_member_type').val('users_group');
1415 $('#perm_new_member_type').val('users_group');
1420 }
1416 }
1421 });
1417 });
1422 }
1418 }
1423
1419
1424 // in this keybuffer we will gather current value of search !
1420 // in this keybuffer we will gather current value of search !
1425 // since we need to get this just when someone does `@` then we do the
1421 // since we need to get this just when someone does `@` then we do the
1426 // search
1422 // search
1427 ownerAC.dataSource.chunks = [];
1423 ownerAC.dataSource.chunks = [];
1428 ownerAC.dataSource.mentionQuery = null;
1424 ownerAC.dataSource.mentionQuery = null;
1429
1425
1430 ownerAC.get_mention = function(msg, max_pos) {
1426 ownerAC.get_mention = function(msg, max_pos) {
1431 var org = msg;
1427 var org = msg;
1432 // Must match utils2.py MENTIONS_REGEX.
1428 // Must match utils2.py MENTIONS_REGEX.
1433 // Only matching on string up to cursor, so it must end with $
1429 // Only matching on string up to cursor, so it must end with $
1434 var re = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$')
1430 var re = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$')
1435 var chunks = [];
1431 var chunks = [];
1436
1432
1437 // cut first chunk until current pos
1433 // cut first chunk until current pos
1438 var to_max = msg.substr(0, max_pos);
1434 var to_max = msg.substr(0, max_pos);
1439 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1435 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1440 var msg2 = to_max.substr(at_pos);
1436 var msg2 = to_max.substr(at_pos);
1441
1437
1442 chunks.push(org.substr(0,at_pos))// prefix chunk
1438 chunks.push(org.substr(0,at_pos))// prefix chunk
1443 chunks.push(msg2) // search chunk
1439 chunks.push(msg2) // search chunk
1444 chunks.push(org.substr(max_pos)) // postfix chunk
1440 chunks.push(org.substr(max_pos)) // postfix chunk
1445
1441
1446 // clean up msg2 for filtering and regex match
1442 // clean up msg2 for filtering and regex match
1447 var msg2 = msg2.lstrip(' ').lstrip('\n');
1443 var msg2 = msg2.lstrip(' ').lstrip('\n');
1448
1444
1449 if(re.test(msg2)){
1445 if(re.test(msg2)){
1450 var unam = re.exec(msg2)[1];
1446 var unam = re.exec(msg2)[1];
1451 return [unam, chunks];
1447 return [unam, chunks];
1452 }
1448 }
1453 return [null, null];
1449 return [null, null];
1454 };
1450 };
1455
1451
1456 var $divid = $('#'+divid);
1452 var $divid = $('#'+divid);
1457 $divid.keyup(function(e){
1453 $divid.keyup(function(e){
1458 var currentMessage = $divid.val();
1454 var currentMessage = $divid.val();
1459 var currentCaretPosition = $divid[0].selectionStart;
1455 var currentCaretPosition = $divid[0].selectionStart;
1460
1456
1461 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1457 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1462 var curr_search = null;
1458 var curr_search = null;
1463 if(unam[0]){
1459 if(unam[0]){
1464 curr_search = unam[0];
1460 curr_search = unam[0];
1465 }
1461 }
1466
1462
1467 ownerAC.dataSource.chunks = unam[1];
1463 ownerAC.dataSource.chunks = unam[1];
1468 ownerAC.dataSource.mentionQuery = curr_search;
1464 ownerAC.dataSource.mentionQuery = curr_search;
1469 });
1465 });
1470 }
1466 }
1471
1467
1472 var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
1468 var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
1473 var displayname = "{0} {1} ({2})".format(fname, lname, nname);
1469 var displayname = "{0} {1} ({2})".format(fname, lname, nname);
1474 var gravatarelm = '<img alt="gravatar" style="width: {0}px; height: {0}px" src="{1}"/>'.format(gravatar_size, gravatar_link);
1470 var gravatarelm = '<img alt="gravatar" style="width: {0}px; height: {0}px" src="{1}"/>'.format(gravatar_size, gravatar_link);
1475 if (!gravatar_link)
1471 if (!gravatar_link)
1476 gravatarelm = '<i class="icon-user" style="font-size: {0}px;"></i>'.format(gravatar_size);
1472 gravatarelm = '<i class="icon-user" style="font-size: {0}px;"></i>'.format(gravatar_size);
1477 var element = (
1473 var element = (
1478 ' <li id="reviewer_{2}">\n'+
1474 ' <li id="reviewer_{2}">\n'+
1479 ' <div class="reviewers_member">\n'+
1475 ' <div class="reviewers_member">\n'+
1480 ' <div class="reviewer_status tooltip" title="not_reviewed">\n'+
1476 ' <div class="reviewer_status tooltip" title="not_reviewed">\n'+
1481 ' <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
1477 ' <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
1482 ' </div>\n'+
1478 ' </div>\n'+
1483 ' <div class="reviewer_gravatar gravatar">{0}</div>\n'+
1479 ' <div class="reviewer_gravatar gravatar">{0}</div>\n'+
1484 ' <div style="float:left;">{1}</div>\n'+
1480 ' <div style="float:left;">{1}</div>\n'+
1485 ' <input type="hidden" value="{2}" name="review_members" />\n'+
1481 ' <input type="hidden" value="{2}" name="review_members" />\n'+
1486 ' <div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">\n'+
1482 ' <div class="reviewer_member_remove action_button" onclick="removeReviewMember({2})">\n'+
1487 ' <i class="icon-minus-circled"></i>\n'+
1483 ' <i class="icon-minus-circled"></i>\n'+
1488 ' </div> (add not saved)\n'+
1484 ' </div> (add not saved)\n'+
1489 ' </div>\n'+
1485 ' </div>\n'+
1490 ' </li>\n'
1486 ' </li>\n'
1491 ).format(gravatarelm, displayname, id);
1487 ).format(gravatarelm, displayname, id);
1492 // check if we don't have this ID already in
1488 // check if we don't have this ID already in
1493 var ids = [];
1489 var ids = [];
1494 $('#review_members').find('li').each(function() {
1490 $('#review_members').find('li').each(function() {
1495 ids.push(this.id);
1491 ids.push(this.id);
1496 });
1492 });
1497 if(ids.indexOf('reviewer_'+id) == -1){
1493 if(ids.indexOf('reviewer_'+id) == -1){
1498 //only add if it's not there
1494 //only add if it's not there
1499 $('#review_members').append(element);
1495 $('#review_members').append(element);
1500 }
1496 }
1501 }
1497 }
1502
1498
1503 var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
1499 var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
1504 var $li = $('#reviewer_{0}'.format(reviewer_id));
1500 var $li = $('#reviewer_{0}'.format(reviewer_id));
1505 $li.find('div div').css("text-decoration", "line-through");
1501 $li.find('div div').css("text-decoration", "line-through");
1506 $li.find('input').attr('name', 'review_members_removed');
1502 $li.find('input').attr('name', 'review_members_removed');
1507 $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
1503 $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
1508 }
1504 }
1509
1505
1510 /* activate auto completion of users and groups ... but only used for users as PR reviewers */
1506 /* activate auto completion of users and groups ... but only used for users as PR reviewers */
1511 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1507 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1512 var myUsers = users_list;
1508 var myUsers = users_list;
1513 var myGroups = groups_list;
1509 var myGroups = groups_list;
1514
1510
1515 // Define a custom search function for the DataSource of users
1511 // Define a custom search function for the DataSource of users
1516 var matchUsers = function (sQuery) {
1512 var matchUsers = function (sQuery) {
1517 // Case insensitive matching
1513 // Case insensitive matching
1518 var query = sQuery.toLowerCase();
1514 var query = sQuery.toLowerCase();
1519 var i = 0;
1515 var i = 0;
1520 var l = myUsers.length;
1516 var l = myUsers.length;
1521 var matches = [];
1517 var matches = [];
1522
1518
1523 // Match against each name of each contact
1519 // Match against each name of each contact
1524 for (; i < l; i++) {
1520 for (; i < l; i++) {
1525 var contact = myUsers[i];
1521 var contact = myUsers[i];
1526 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1522 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1527 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1523 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1528 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1524 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1529 matches[matches.length] = contact;
1525 matches[matches.length] = contact;
1530 }
1526 }
1531 }
1527 }
1532 return matches;
1528 return matches;
1533 };
1529 };
1534
1530
1535 // Define a custom search function for the DataSource of userGroups
1531 // Define a custom search function for the DataSource of userGroups
1536 var matchGroups = function (sQuery) {
1532 var matchGroups = function (sQuery) {
1537 // Case insensitive matching
1533 // Case insensitive matching
1538 var query = sQuery.toLowerCase();
1534 var query = sQuery.toLowerCase();
1539 var i = 0;
1535 var i = 0;
1540 var l = myGroups.length;
1536 var l = myGroups.length;
1541 var matches = [];
1537 var matches = [];
1542
1538
1543 // Match against each name of each contact
1539 // Match against each name of each contact
1544 for (; i < l; i++) {
1540 for (; i < l; i++) {
1545 matched_group = myGroups[i];
1541 matched_group = myGroups[i];
1546 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1542 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1547 matches[matches.length] = matched_group;
1543 matches[matches.length] = matched_group;
1548 }
1544 }
1549 }
1545 }
1550 return matches;
1546 return matches;
1551 };
1547 };
1552
1548
1553 //match all
1549 //match all
1554 var matchAll = function (sQuery) {
1550 var matchAll = function (sQuery) {
1555 return matchUsers(sQuery);
1551 return matchUsers(sQuery);
1556 };
1552 };
1557
1553
1558 // DataScheme for owner
1554 // DataScheme for owner
1559 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1555 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1560
1556
1561 ownerDS.responseSchema = {
1557 ownerDS.responseSchema = {
1562 fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
1558 fields: ["id", "fname", "lname", "nname", "gravatar_lnk", "gravatar_size"]
1563 };
1559 };
1564
1560
1565 // Instantiate AutoComplete for mentions
1561 // Instantiate AutoComplete for mentions
1566 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1562 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1567 reviewerAC.useShadow = false;
1563 reviewerAC.useShadow = false;
1568 reviewerAC.resultTypeList = false;
1564 reviewerAC.resultTypeList = false;
1569 reviewerAC.suppressInputUpdate = true;
1565 reviewerAC.suppressInputUpdate = true;
1570 reviewerAC.animVert = false;
1566 reviewerAC.animVert = false;
1571 reviewerAC.animHoriz = false;
1567 reviewerAC.animHoriz = false;
1572 reviewerAC.animSpeed = 0.1;
1568 reviewerAC.animSpeed = 0.1;
1573
1569
1574 // Helper highlight function for the formatter
1570 // Helper highlight function for the formatter
1575 var highlightMatch = function (full, snippet, matchindex) {
1571 var highlightMatch = function (full, snippet, matchindex) {
1576 return full.substring(0, matchindex)
1572 return full.substring(0, matchindex)
1577 + "<span class='match'>"
1573 + "<span class='match'>"
1578 + full.substr(matchindex, snippet.length)
1574 + full.substr(matchindex, snippet.length)
1579 + "</span>" + full.substring(matchindex + snippet.length);
1575 + "</span>" + full.substring(matchindex + snippet.length);
1580 };
1576 };
1581
1577
1582 // Custom formatter to highlight the matching letters
1578 // Custom formatter to highlight the matching letters
1583 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1579 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1584 var org_sQuery = sQuery;
1580 var org_sQuery = sQuery;
1585 if(this.dataSource.mentionQuery != null){
1581 if(this.dataSource.mentionQuery != null){
1586 sQuery = this.dataSource.mentionQuery;
1582 sQuery = this.dataSource.mentionQuery;
1587 }
1583 }
1588
1584
1589 var query = sQuery.toLowerCase();
1585 var query = sQuery.toLowerCase();
1590 var _gravatar = function(res, em, size, group){
1586 var _gravatar = function(res, em, size, group){
1591 var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
1587 var elem = '<img alt="gravatar" class="perm-gravatar-ac" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, em);
1592 if (!em) {
1588 if (!em) {
1593 elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
1589 elem = '<i class="icon-user perm-gravatar-ac" style="font-size: {0}px;"></i>'.format(size);
1594 }
1590 }
1595 if (group !== undefined){
1591 if (group !== undefined){
1596 elem = '<i class="perm-gravatar-ac icon-users"></i>'
1592 elem = '<i class="perm-gravatar-ac icon-users"></i>'
1597 }
1593 }
1598 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
1594 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>'
1599 return tmpl.format(elem,res)
1595 return tmpl.format(elem,res)
1600 }
1596 }
1601 if (oResultData.nname != undefined) {
1597 if (oResultData.nname != undefined) {
1602 var fname = oResultData.fname || "";
1598 var fname = oResultData.fname || "";
1603 var lname = oResultData.lname || "";
1599 var lname = oResultData.lname || "";
1604 var nname = oResultData.nname;
1600 var nname = oResultData.nname;
1605
1601
1606 // Guard against null value
1602 // Guard against null value
1607 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1603 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1608 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1604 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1609 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1605 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1610 displayfname, displaylname, displaynname;
1606 displayfname, displaylname, displaynname;
1611
1607
1612 if (fnameMatchIndex > -1) {
1608 if (fnameMatchIndex > -1) {
1613 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1609 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1614 } else {
1610 } else {
1615 displayfname = fname;
1611 displayfname = fname;
1616 }
1612 }
1617
1613
1618 if (lnameMatchIndex > -1) {
1614 if (lnameMatchIndex > -1) {
1619 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1615 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1620 } else {
1616 } else {
1621 displaylname = lname;
1617 displaylname = lname;
1622 }
1618 }
1623
1619
1624 if (nnameMatchIndex > -1) {
1620 if (nnameMatchIndex > -1) {
1625 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1621 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1626 } else {
1622 } else {
1627 displaynname = nname ? "(" + nname + ")" : "";
1623 displaynname = nname ? "(" + nname + ")" : "";
1628 }
1624 }
1629
1625
1630 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1626 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1631 } else {
1627 } else {
1632 return '';
1628 return '';
1633 }
1629 }
1634 };
1630 };
1635
1631
1636 //members cache to catch duplicates
1632 //members cache to catch duplicates
1637 reviewerAC.dataSource.cache = [];
1633 reviewerAC.dataSource.cache = [];
1638 // hack into select event
1634 // hack into select event
1639 if(reviewerAC.itemSelectEvent){
1635 if(reviewerAC.itemSelectEvent){
1640 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1636 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1641
1637
1642 var myAC = aArgs[0]; // reference back to the AC instance
1638 var myAC = aArgs[0]; // reference back to the AC instance
1643 var elLI = aArgs[1]; // reference to the selected LI element
1639 var elLI = aArgs[1]; // reference to the selected LI element
1644 var oData = aArgs[2]; // object literal of selected item's result data
1640 var oData = aArgs[2]; // object literal of selected item's result data
1645
1641
1646 //fill the autocomplete with value
1642 //fill the autocomplete with value
1647 if (oData.nname != undefined) {
1643 if (oData.nname != undefined) {
1648 addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
1644 addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
1649 oData.gravatar_lnk, oData.gravatar_size);
1645 oData.gravatar_lnk, oData.gravatar_size);
1650 myAC.dataSource.cache.push(oData.id);
1646 myAC.dataSource.cache.push(oData.id);
1651 $('#user').val('');
1647 $('#user').val('');
1652 }
1648 }
1653 });
1649 });
1654 }
1650 }
1655 }
1651 }
1656
1652
1657 /**
1653 /**
1658 * Activate .quick_repo_menu
1654 * Activate .quick_repo_menu
1659 */
1655 */
1660 var quick_repo_menu = function(){
1656 var quick_repo_menu = function(){
1661 $(".quick_repo_menu").mouseenter(function(e) {
1657 $(".quick_repo_menu").mouseenter(function(e) {
1662 var $menu = $(e.currentTarget).children().first().children().first();
1658 var $menu = $(e.currentTarget).children().first().children().first();
1663 if($menu.hasClass('hidden')){
1659 if($menu.hasClass('hidden')){
1664 $menu.removeClass('hidden').addClass('active');
1660 $menu.removeClass('hidden').addClass('active');
1665 $(e.currentTarget).removeClass('hidden').addClass('active');
1661 $(e.currentTarget).removeClass('hidden').addClass('active');
1666 }
1662 }
1667 })
1663 })
1668 $(".quick_repo_menu").mouseleave(function(e) {
1664 $(".quick_repo_menu").mouseleave(function(e) {
1669 var $menu = $(e.currentTarget).children().first().children().first();
1665 var $menu = $(e.currentTarget).children().first().children().first();
1670 if($menu.hasClass('active')){
1666 if($menu.hasClass('active')){
1671 $menu.removeClass('active').addClass('hidden');
1667 $menu.removeClass('active').addClass('hidden');
1672 $(e.currentTarget).removeClass('active').addClass('hidden');
1668 $(e.currentTarget).removeClass('active').addClass('hidden');
1673 }
1669 }
1674 })
1670 })
1675 };
1671 };
1676
1672
1677
1673
1678 /**
1674 /**
1679 * TABLE SORTING
1675 * TABLE SORTING
1680 */
1676 */
1681
1677
1682 var revisionSort = function(a, b, desc, field) {
1678 var revisionSort = function(a, b, desc, field) {
1683 var a_ = parseInt(a.getData('last_rev_raw') || 0);
1679 var a_ = parseInt(a.getData('last_rev_raw') || 0);
1684 var b_ = parseInt(b.getData('last_rev_raw') || 0);
1680 var b_ = parseInt(b.getData('last_rev_raw') || 0);
1685
1681
1686 return YAHOO.util.Sort.compare(a_, b_, desc);
1682 return YAHOO.util.Sort.compare(a_, b_, desc);
1687 };
1683 };
1688
1684
1689 var ageSort = function(a, b, desc, field) {
1685 var ageSort = function(a, b, desc, field) {
1690 // data is like: <span class="tooltip" date="2014-06-04 18:18:55.325474" title="Wed, 04 Jun 2014 18:18:55">1 day and 23 hours ago</span>
1686 // data is like: <span class="tooltip" date="2014-06-04 18:18:55.325474" title="Wed, 04 Jun 2014 18:18:55">1 day and 23 hours ago</span>
1691 var a_ = $(a.getData(field)).attr('date');
1687 var a_ = $(a.getData(field)).attr('date');
1692 var b_ = $(b.getData(field)).attr('date');
1688 var b_ = $(b.getData(field)).attr('date');
1693
1689
1694 return YAHOO.util.Sort.compare(a_, b_, desc);
1690 return YAHOO.util.Sort.compare(a_, b_, desc);
1695 };
1691 };
1696
1692
1697 var lastLoginSort = function(a, b, desc, field) {
1693 var lastLoginSort = function(a, b, desc, field) {
1698 var a_ = parseFloat(a.getData('last_login_raw') || 0);
1694 var a_ = parseFloat(a.getData('last_login_raw') || 0);
1699 var b_ = parseFloat(b.getData('last_login_raw') || 0);
1695 var b_ = parseFloat(b.getData('last_login_raw') || 0);
1700
1696
1701 return YAHOO.util.Sort.compare(a_, b_, desc);
1697 return YAHOO.util.Sort.compare(a_, b_, desc);
1702 };
1698 };
1703
1699
1704 var nameSort = function(a, b, desc, field) {
1700 var nameSort = function(a, b, desc, field) {
1705 var a_ = a.getData('raw_name') || 0;
1701 var a_ = a.getData('raw_name') || 0;
1706 var b_ = b.getData('raw_name') || 0;
1702 var b_ = b.getData('raw_name') || 0;
1707
1703
1708 return YAHOO.util.Sort.compare(a_, b_, desc);
1704 return YAHOO.util.Sort.compare(a_, b_, desc);
1709 };
1705 };
1710
1706
1711 var dateSort = function(a, b, desc, field) {
1707 var dateSort = function(a, b, desc, field) {
1712 var a_ = parseFloat(a.getData('raw_date') || 0);
1708 var a_ = parseFloat(a.getData('raw_date') || 0);
1713 var b_ = parseFloat(b.getData('raw_date') || 0);
1709 var b_ = parseFloat(b.getData('raw_date') || 0);
1714
1710
1715 return YAHOO.util.Sort.compare(a_, b_, desc);
1711 return YAHOO.util.Sort.compare(a_, b_, desc);
1716 };
1712 };
1717
1713
1718 var addPermAction = function(_html, users_list, groups_list){
1714 var addPermAction = function(_html, users_list, groups_list){
1719 var $last_node = $('.last_new_member').last(); // empty tr between last and add
1715 var $last_node = $('.last_new_member').last(); // empty tr between last and add
1720 var next_id = $('.new_members').length;
1716 var next_id = $('.new_members').length;
1721 $last_node.before($('<tr class="new_members">').append(_html.format(next_id)));
1717 $last_node.before($('<tr class="new_members">').append(_html.format(next_id)));
1722 _MembersAutoComplete("perm_new_member_name_"+next_id,
1718 _MembersAutoComplete("perm_new_member_name_"+next_id,
1723 "perm_container_"+next_id, users_list, groups_list);
1719 "perm_container_"+next_id, users_list, groups_list);
1724 }
1720 }
1725
1721
1726 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
1722 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
1727 var success = function (o) {
1723 var success = function (o) {
1728 $('#' + field_id).remove();
1724 $('#' + field_id).remove();
1729 };
1725 };
1730 var failure = function (o) {
1726 var failure = function (o) {
1731 alert(_TM['Failed to revoke permission'] + ": " + o.status);
1727 alert(_TM['Failed to revoke permission'] + ": " + o.status);
1732 };
1728 };
1733 var query_params = {
1729 var query_params = {
1734 '_method': 'delete'
1730 '_method': 'delete'
1735 }
1731 }
1736 // put extra data into POST
1732 // put extra data into POST
1737 if (extra_data !== undefined && (typeof extra_data === 'object')){
1733 if (extra_data !== undefined && (typeof extra_data === 'object')){
1738 for(var k in extra_data){
1734 for(var k in extra_data){
1739 query_params[k] = extra_data[k];
1735 query_params[k] = extra_data[k];
1740 }
1736 }
1741 }
1737 }
1742
1738
1743 if (obj_type=='user'){
1739 if (obj_type=='user'){
1744 query_params['user_id'] = obj_id;
1740 query_params['user_id'] = obj_id;
1745 query_params['obj_type'] = 'user';
1741 query_params['obj_type'] = 'user';
1746 }
1742 }
1747 else if (obj_type=='user_group'){
1743 else if (obj_type=='user_group'){
1748 query_params['user_group_id'] = obj_id;
1744 query_params['user_group_id'] = obj_id;
1749 query_params['obj_type'] = 'user_group';
1745 query_params['obj_type'] = 'user_group';
1750 }
1746 }
1751
1747
1752 ajaxPOST(url, query_params, success, failure);
1748 ajaxPOST(url, query_params, success, failure);
1753 };
1749 };
1754
1750
1755 /* Multi selectors */
1751 /* Multi selectors */
1756
1752
1757 var MultiSelectWidget = function(selected_id, available_id, form_id){
1753 var MultiSelectWidget = function(selected_id, available_id, form_id){
1758 var $availableselect = $('#' + available_id);
1754 var $availableselect = $('#' + available_id);
1759 var $selectedselect = $('#' + selected_id);
1755 var $selectedselect = $('#' + selected_id);
1760
1756
1761 //fill available only with those not in selected
1757 //fill available only with those not in selected
1762 var $selectedoptions = $selectedselect.children('option');
1758 var $selectedoptions = $selectedselect.children('option');
1763 $availableselect.children('option').filter(function(i, e){
1759 $availableselect.children('option').filter(function(i, e){
1764 for(var j = 0, node; node = $selectedoptions[j]; j++){
1760 for(var j = 0, node; node = $selectedoptions[j]; j++){
1765 if(node.value == e.value){
1761 if(node.value == e.value){
1766 return true;
1762 return true;
1767 }
1763 }
1768 }
1764 }
1769 return false;
1765 return false;
1770 }).remove();
1766 }).remove();
1771
1767
1772 $('#add_element').click(function(e){
1768 $('#add_element').click(function(e){
1773 $selectedselect.append($availableselect.children('option:selected'));
1769 $selectedselect.append($availableselect.children('option:selected'));
1774 });
1770 });
1775 $('#remove_element').click(function(e){
1771 $('#remove_element').click(function(e){
1776 $availableselect.append($selectedselect.children('option:selected'));
1772 $availableselect.append($selectedselect.children('option:selected'));
1777 });
1773 });
1778
1774
1779 $('#'+form_id).submit(function(){
1775 $('#'+form_id).submit(function(){
1780 $selectedselect.children('option').each(function(i, e){
1776 $selectedselect.children('option').each(function(i, e){
1781 e.selected = 'selected';
1777 e.selected = 'selected';
1782 });
1778 });
1783 });
1779 });
1784 }
1780 }
1785
1781
1786 // custom paginator
1782 // custom paginator
1787 var YUI_paginator = function(links_per_page, containers){
1783 var YUI_paginator = function(links_per_page, containers){
1788
1784
1789 (function () {
1785 (function () {
1790
1786
1791 var Paginator = YAHOO.widget.Paginator,
1787 var Paginator = YAHOO.widget.Paginator,
1792 l = YAHOO.lang,
1788 l = YAHOO.lang,
1793 setId = YAHOO.util.Dom.generateId;
1789 setId = YAHOO.util.Dom.generateId;
1794
1790
1795 Paginator.ui.MyFirstPageLink = function (p) {
1791 Paginator.ui.MyFirstPageLink = function (p) {
1796 this.paginator = p;
1792 this.paginator = p;
1797
1793
1798 p.subscribe('recordOffsetChange',this.update,this,true);
1794 p.subscribe('recordOffsetChange',this.update,this,true);
1799 p.subscribe('rowsPerPageChange',this.update,this,true);
1795 p.subscribe('rowsPerPageChange',this.update,this,true);
1800 p.subscribe('totalRecordsChange',this.update,this,true);
1796 p.subscribe('totalRecordsChange',this.update,this,true);
1801 p.subscribe('destroy',this.destroy,this,true);
1797 p.subscribe('destroy',this.destroy,this,true);
1802
1798
1803 // TODO: make this work
1799 // TODO: make this work
1804 p.subscribe('firstPageLinkLabelChange',this.update,this,true);
1800 p.subscribe('firstPageLinkLabelChange',this.update,this,true);
1805 p.subscribe('firstPageLinkClassChange',this.update,this,true);
1801 p.subscribe('firstPageLinkClassChange',this.update,this,true);
1806 };
1802 };
1807
1803
1808 Paginator.ui.MyFirstPageLink.init = function (p) {
1804 Paginator.ui.MyFirstPageLink.init = function (p) {
1809 p.setAttributeConfig('firstPageLinkLabel', {
1805 p.setAttributeConfig('firstPageLinkLabel', {
1810 value : 1,
1806 value : 1,
1811 validator : l.isString
1807 validator : l.isString
1812 });
1808 });
1813 p.setAttributeConfig('firstPageLinkClass', {
1809 p.setAttributeConfig('firstPageLinkClass', {
1814 value : 'yui-pg-first',
1810 value : 'yui-pg-first',
1815 validator : l.isString
1811 validator : l.isString
1816 });
1812 });
1817 p.setAttributeConfig('firstPageLinkTitle', {
1813 p.setAttributeConfig('firstPageLinkTitle', {
1818 value : 'First Page',
1814 value : 'First Page',
1819 validator : l.isString
1815 validator : l.isString
1820 });
1816 });
1821 };
1817 };
1822
1818
1823 // Instance members and methods
1819 // Instance members and methods
1824 Paginator.ui.MyFirstPageLink.prototype = {
1820 Paginator.ui.MyFirstPageLink.prototype = {
1825 current : null,
1821 current : null,
1826 leftmost_page: null,
1822 leftmost_page: null,
1827 rightmost_page: null,
1823 rightmost_page: null,
1828 link : null,
1824 link : null,
1829 span : null,
1825 span : null,
1830 dotdot : null,
1826 dotdot : null,
1831 getPos : function(cur_page, max_page, items){
1827 getPos : function(cur_page, max_page, items){
1832 var edge = parseInt(items / 2) + 1;
1828 var edge = parseInt(items / 2) + 1;
1833 if (cur_page <= edge){
1829 if (cur_page <= edge){
1834 var radius = Math.max(parseInt(items / 2), items - cur_page);
1830 var radius = Math.max(parseInt(items / 2), items - cur_page);
1835 }
1831 }
1836 else if ((max_page - cur_page) < edge) {
1832 else if ((max_page - cur_page) < edge) {
1837 var radius = (items - 1) - (max_page - cur_page);
1833 var radius = (items - 1) - (max_page - cur_page);
1838 }
1834 }
1839 else{
1835 else{
1840 var radius = parseInt(items / 2);
1836 var radius = parseInt(items / 2);
1841 }
1837 }
1842
1838
1843 var left = Math.max(1, (cur_page - (radius)))
1839 var left = Math.max(1, (cur_page - (radius)))
1844 var right = Math.min(max_page, cur_page + (radius))
1840 var right = Math.min(max_page, cur_page + (radius))
1845 return [left, cur_page, right]
1841 return [left, cur_page, right]
1846 },
1842 },
1847 render : function (id_base) {
1843 render : function (id_base) {
1848 var p = this.paginator,
1844 var p = this.paginator,
1849 c = p.get('firstPageLinkClass'),
1845 c = p.get('firstPageLinkClass'),
1850 label = p.get('firstPageLinkLabel'),
1846 label = p.get('firstPageLinkLabel'),
1851 title = p.get('firstPageLinkTitle');
1847 title = p.get('firstPageLinkTitle');
1852
1848
1853 this.link = document.createElement('a');
1849 this.link = document.createElement('a');
1854 this.span = document.createElement('span');
1850 this.span = document.createElement('span');
1855 $(this.span).hide();
1851 $(this.span).hide();
1856
1852
1857 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
1853 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
1858 this.leftmost_page = _pos[0];
1854 this.leftmost_page = _pos[0];
1859 this.rightmost_page = _pos[2];
1855 this.rightmost_page = _pos[2];
1860
1856
1861 setId(this.link, id_base + '-first-link');
1857 setId(this.link, id_base + '-first-link');
1862 this.link.href = '#';
1858 this.link.href = '#';
1863 this.link.className = c;
1859 this.link.className = c;
1864 this.link.innerHTML = label;
1860 this.link.innerHTML = label;
1865 this.link.title = title;
1861 this.link.title = title;
1866 YUE.on(this.link,'click',this.onClick,this,true);
1862 YUE.on(this.link,'click',this.onClick,this,true);
1867
1863
1868 setId(this.span, id_base + '-first-span');
1864 setId(this.span, id_base + '-first-span');
1869 this.span.className = c;
1865 this.span.className = c;
1870 this.span.innerHTML = label;
1866 this.span.innerHTML = label;
1871
1867
1872 this.current = p.getCurrentPage() > 1 ? this.link : this.span;
1868 this.current = p.getCurrentPage() > 1 ? this.link : this.span;
1873 return this.current;
1869 return this.current;
1874 },
1870 },
1875 update : function (e) {
1871 update : function (e) {
1876 var p = this.paginator;
1872 var p = this.paginator;
1877 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
1873 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
1878 this.leftmost_page = _pos[0];
1874 this.leftmost_page = _pos[0];
1879 this.rightmost_page = _pos[2];
1875 this.rightmost_page = _pos[2];
1880
1876
1881 if (e && e.prevValue === e.newValue) {
1877 if (e && e.prevValue === e.newValue) {
1882 return;
1878 return;
1883 }
1879 }
1884
1880
1885 var par = this.current ? this.current.parentNode : null;
1881 var par = this.current ? this.current.parentNode : null;
1886 if (this.leftmost_page > 1) {
1882 if (this.leftmost_page > 1) {
1887 if (par && this.current === this.span) {
1883 if (par && this.current === this.span) {
1888 par.replaceChild(this.link,this.current);
1884 par.replaceChild(this.link,this.current);
1889 this.current = this.link;
1885 this.current = this.link;
1890 }
1886 }
1891 } else {
1887 } else {
1892 if (par && this.current === this.link) {
1888 if (par && this.current === this.link) {
1893 par.replaceChild(this.span,this.current);
1889 par.replaceChild(this.span,this.current);
1894 this.current = this.span;
1890 this.current = this.span;
1895 }
1891 }
1896 }
1892 }
1897 },
1893 },
1898 destroy : function () {
1894 destroy : function () {
1899 YUE.purgeElement(this.link);
1895 YUE.purgeElement(this.link);
1900 this.current.parentNode.removeChild(this.current);
1896 this.current.parentNode.removeChild(this.current);
1901 this.link = this.span = null;
1897 this.link = this.span = null;
1902 },
1898 },
1903 onClick : function (e) {
1899 onClick : function (e) {
1904 YUE.stopEvent(e);
1900 YUE.stopEvent(e);
1905 this.paginator.setPage(1);
1901 this.paginator.setPage(1);
1906 }
1902 }
1907 };
1903 };
1908
1904
1909 })();
1905 })();
1910
1906
1911 (function () {
1907 (function () {
1912
1908
1913 var Paginator = YAHOO.widget.Paginator,
1909 var Paginator = YAHOO.widget.Paginator,
1914 l = YAHOO.lang,
1910 l = YAHOO.lang,
1915 setId = YAHOO.util.Dom.generateId;
1911 setId = YAHOO.util.Dom.generateId;
1916
1912
1917 Paginator.ui.MyLastPageLink = function (p) {
1913 Paginator.ui.MyLastPageLink = function (p) {
1918 this.paginator = p;
1914 this.paginator = p;
1919
1915
1920 p.subscribe('recordOffsetChange',this.update,this,true);
1916 p.subscribe('recordOffsetChange',this.update,this,true);
1921 p.subscribe('rowsPerPageChange',this.update,this,true);
1917 p.subscribe('rowsPerPageChange',this.update,this,true);
1922 p.subscribe('totalRecordsChange',this.update,this,true);
1918 p.subscribe('totalRecordsChange',this.update,this,true);
1923 p.subscribe('destroy',this.destroy,this,true);
1919 p.subscribe('destroy',this.destroy,this,true);
1924
1920
1925 // TODO: make this work
1921 // TODO: make this work
1926 p.subscribe('lastPageLinkLabelChange',this.update,this,true);
1922 p.subscribe('lastPageLinkLabelChange',this.update,this,true);
1927 p.subscribe('lastPageLinkClassChange', this.update,this,true);
1923 p.subscribe('lastPageLinkClassChange', this.update,this,true);
1928 };
1924 };
1929
1925
1930 Paginator.ui.MyLastPageLink.init = function (p) {
1926 Paginator.ui.MyLastPageLink.init = function (p) {
1931 p.setAttributeConfig('lastPageLinkLabel', {
1927 p.setAttributeConfig('lastPageLinkLabel', {
1932 value : -1,
1928 value : -1,
1933 validator : l.isString
1929 validator : l.isString
1934 });
1930 });
1935 p.setAttributeConfig('lastPageLinkClass', {
1931 p.setAttributeConfig('lastPageLinkClass', {
1936 value : 'yui-pg-last',
1932 value : 'yui-pg-last',
1937 validator : l.isString
1933 validator : l.isString
1938 });
1934 });
1939 p.setAttributeConfig('lastPageLinkTitle', {
1935 p.setAttributeConfig('lastPageLinkTitle', {
1940 value : 'Last Page',
1936 value : 'Last Page',
1941 validator : l.isString
1937 validator : l.isString
1942 });
1938 });
1943
1939
1944 };
1940 };
1945
1941
1946 Paginator.ui.MyLastPageLink.prototype = {
1942 Paginator.ui.MyLastPageLink.prototype = {
1947
1943
1948 current : null,
1944 current : null,
1949 leftmost_page: null,
1945 leftmost_page: null,
1950 rightmost_page: null,
1946 rightmost_page: null,
1951 link : null,
1947 link : null,
1952 span : null,
1948 span : null,
1953 dotdot : null,
1949 dotdot : null,
1954 na : null,
1950 na : null,
1955 getPos : function(cur_page, max_page, items){
1951 getPos : function(cur_page, max_page, items){
1956 var edge = parseInt(items / 2) + 1;
1952 var edge = parseInt(items / 2) + 1;
1957 if (cur_page <= edge){
1953 if (cur_page <= edge){
1958 var radius = Math.max(parseInt(items / 2), items - cur_page);
1954 var radius = Math.max(parseInt(items / 2), items - cur_page);
1959 }
1955 }
1960 else if ((max_page - cur_page) < edge) {
1956 else if ((max_page - cur_page) < edge) {
1961 var radius = (items - 1) - (max_page - cur_page);
1957 var radius = (items - 1) - (max_page - cur_page);
1962 }
1958 }
1963 else{
1959 else{
1964 var radius = parseInt(items / 2);
1960 var radius = parseInt(items / 2);
1965 }
1961 }
1966
1962
1967 var left = Math.max(1, (cur_page - (radius)))
1963 var left = Math.max(1, (cur_page - (radius)))
1968 var right = Math.min(max_page, cur_page + (radius))
1964 var right = Math.min(max_page, cur_page + (radius))
1969 return [left, cur_page, right]
1965 return [left, cur_page, right]
1970 },
1966 },
1971 render : function (id_base) {
1967 render : function (id_base) {
1972 var p = this.paginator,
1968 var p = this.paginator,
1973 c = p.get('lastPageLinkClass'),
1969 c = p.get('lastPageLinkClass'),
1974 label = p.get('lastPageLinkLabel'),
1970 label = p.get('lastPageLinkLabel'),
1975 last = p.getTotalPages(),
1971 last = p.getTotalPages(),
1976 title = p.get('lastPageLinkTitle');
1972 title = p.get('lastPageLinkTitle');
1977
1973
1978 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
1974 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
1979 this.leftmost_page = _pos[0];
1975 this.leftmost_page = _pos[0];
1980 this.rightmost_page = _pos[2];
1976 this.rightmost_page = _pos[2];
1981
1977
1982 this.link = document.createElement('a');
1978 this.link = document.createElement('a');
1983 this.span = document.createElement('span');
1979 this.span = document.createElement('span');
1984 $(this.span).hide();
1980 $(this.span).hide();
1985
1981
1986 this.na = this.span.cloneNode(false);
1982 this.na = this.span.cloneNode(false);
1987
1983
1988 setId(this.link, id_base + '-last-link');
1984 setId(this.link, id_base + '-last-link');
1989 this.link.href = '#';
1985 this.link.href = '#';
1990 this.link.className = c;
1986 this.link.className = c;
1991 this.link.innerHTML = label;
1987 this.link.innerHTML = label;
1992 this.link.title = title;
1988 this.link.title = title;
1993 YUE.on(this.link,'click',this.onClick,this,true);
1989 YUE.on(this.link,'click',this.onClick,this,true);
1994
1990
1995 setId(this.span, id_base + '-last-span');
1991 setId(this.span, id_base + '-last-span');
1996 this.span.className = c;
1992 this.span.className = c;
1997 this.span.innerHTML = label;
1993 this.span.innerHTML = label;
1998
1994
1999 setId(this.na, id_base + '-last-na');
1995 setId(this.na, id_base + '-last-na');
2000
1996
2001 if (this.rightmost_page < p.getTotalPages()){
1997 if (this.rightmost_page < p.getTotalPages()){
2002 this.current = this.link;
1998 this.current = this.link;
2003 }
1999 }
2004 else{
2000 else{
2005 this.current = this.span;
2001 this.current = this.span;
2006 }
2002 }
2007
2003
2008 this.current.innerHTML = p.getTotalPages();
2004 this.current.innerHTML = p.getTotalPages();
2009 return this.current;
2005 return this.current;
2010 },
2006 },
2011
2007
2012 update : function (e) {
2008 update : function (e) {
2013 var p = this.paginator;
2009 var p = this.paginator;
2014
2010
2015 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
2011 var _pos = this.getPos(p.getCurrentPage(), p.getTotalPages(), 5);
2016 this.leftmost_page = _pos[0];
2012 this.leftmost_page = _pos[0];
2017 this.rightmost_page = _pos[2];
2013 this.rightmost_page = _pos[2];
2018
2014
2019 if (e && e.prevValue === e.newValue) {
2015 if (e && e.prevValue === e.newValue) {
2020 return;
2016 return;
2021 }
2017 }
2022
2018
2023 var par = this.current ? this.current.parentNode : null,
2019 var par = this.current ? this.current.parentNode : null,
2024 after = this.link;
2020 after = this.link;
2025 if (par) {
2021 if (par) {
2026
2022
2027 // only show the last page if the rightmost one is
2023 // only show the last page if the rightmost one is
2028 // lower, so we don't have doubled entries at the end
2024 // lower, so we don't have doubled entries at the end
2029 if (!(this.rightmost_page < p.getTotalPages())){
2025 if (!(this.rightmost_page < p.getTotalPages())){
2030 after = this.span
2026 after = this.span
2031 }
2027 }
2032
2028
2033 if (this.current !== after) {
2029 if (this.current !== after) {
2034 par.replaceChild(after,this.current);
2030 par.replaceChild(after,this.current);
2035 this.current = after;
2031 this.current = after;
2036 }
2032 }
2037 }
2033 }
2038 this.current.innerHTML = this.paginator.getTotalPages();
2034 this.current.innerHTML = this.paginator.getTotalPages();
2039
2035
2040 },
2036 },
2041 destroy : function () {
2037 destroy : function () {
2042 YUE.purgeElement(this.link);
2038 YUE.purgeElement(this.link);
2043 this.current.parentNode.removeChild(this.current);
2039 this.current.parentNode.removeChild(this.current);
2044 this.link = this.span = null;
2040 this.link = this.span = null;
2045 },
2041 },
2046 onClick : function (e) {
2042 onClick : function (e) {
2047 YUE.stopEvent(e);
2043 YUE.stopEvent(e);
2048 this.paginator.setPage(this.paginator.getTotalPages());
2044 this.paginator.setPage(this.paginator.getTotalPages());
2049 }
2045 }
2050 };
2046 };
2051
2047
2052 })();
2048 })();
2053
2049
2054 var pagi = new YAHOO.widget.Paginator({
2050 var pagi = new YAHOO.widget.Paginator({
2055 rowsPerPage: links_per_page,
2051 rowsPerPage: links_per_page,
2056 alwaysVisible: false,
2052 alwaysVisible: false,
2057 template : "{PreviousPageLink} {MyFirstPageLink} {PageLinks} {MyLastPageLink} {NextPageLink}",
2053 template : "{PreviousPageLink} {MyFirstPageLink} {PageLinks} {MyLastPageLink} {NextPageLink}",
2058 pageLinks: 5,
2054 pageLinks: 5,
2059 containerClass: 'pagination-wh',
2055 containerClass: 'pagination-wh',
2060 currentPageClass: 'pager_curpage',
2056 currentPageClass: 'pager_curpage',
2061 pageLinkClass: 'pager_link',
2057 pageLinkClass: 'pager_link',
2062 nextPageLinkLabel: '&gt;',
2058 nextPageLinkLabel: '&gt;',
2063 previousPageLinkLabel: '&lt;',
2059 previousPageLinkLabel: '&lt;',
2064 containers:containers
2060 containers:containers
2065 })
2061 })
2066
2062
2067 return pagi
2063 return pagi
2068 }
2064 }
2069
2065
2070 var YUI_datatable = function(data, fields, columns, countnode, sortkey, rows){
2066 var YUI_datatable = function(data, fields, columns, countnode, sortkey, rows){
2071 var myDataSource = new YAHOO.util.DataSource(data);
2067 var myDataSource = new YAHOO.util.DataSource(data);
2072 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
2068 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
2073 myDataSource.responseSchema = {
2069 myDataSource.responseSchema = {
2074 resultsList: "records",
2070 resultsList: "records",
2075 fields: fields
2071 fields: fields
2076 };
2072 };
2077 myDataSource.doBeforeCallback = function(req, raw, res, cb) {
2073 myDataSource.doBeforeCallback = function(req, raw, res, cb) {
2078 // This is the filter function
2074 // This is the filter function
2079 var data = res.results || [],
2075 var data = res.results || [],
2080 filtered = [],
2076 filtered = [],
2081 i, l;
2077 i, l;
2082
2078
2083 if (req) {
2079 if (req) {
2084 req = req.toLowerCase();
2080 req = req.toLowerCase();
2085 for (i = 0; i<data.length; i++) {
2081 for (i = 0; i<data.length; i++) {
2086 var pos = data[i].raw_name.toLowerCase().indexOf(req)
2082 var pos = data[i].raw_name.toLowerCase().indexOf(req)
2087 if (pos != -1) {
2083 if (pos != -1) {
2088 filtered.push(data[i]);
2084 filtered.push(data[i]);
2089 }
2085 }
2090 }
2086 }
2091 res.results = filtered;
2087 res.results = filtered;
2092 }
2088 }
2093 $(countnode).html(res.results.length);
2089 $(countnode).html(res.results.length);
2094 return res;
2090 return res;
2095 }
2091 }
2096
2092
2097 var myDataTable = new YAHOO.widget.DataTable("datatable_list_wrap", columns, myDataSource, {
2093 var myDataTable = new YAHOO.widget.DataTable("datatable_list_wrap", columns, myDataSource, {
2098 sortedBy: {key:sortkey, dir:"asc"},
2094 sortedBy: {key:sortkey, dir:"asc"},
2099 paginator: YUI_paginator(rows !== undefined && rows ? rows : 25, ['user-paginator']),
2095 paginator: YUI_paginator(rows !== undefined && rows ? rows : 25, ['user-paginator']),
2100 MSG_SORTASC: _TM['MSG_SORTASC'],
2096 MSG_SORTASC: _TM['MSG_SORTASC'],
2101 MSG_SORTDESC: _TM['MSG_SORTDESC'],
2097 MSG_SORTDESC: _TM['MSG_SORTDESC'],
2102 MSG_EMPTY: _TM['MSG_EMPTY'],
2098 MSG_EMPTY: _TM['MSG_EMPTY'],
2103 MSG_ERROR: _TM['MSG_ERROR'],
2099 MSG_ERROR: _TM['MSG_ERROR'],
2104 MSG_LOADING: _TM['MSG_LOADING']
2100 MSG_LOADING: _TM['MSG_LOADING']
2105 });
2101 });
2106 myDataTable.subscribe('postRenderEvent',function(oArgs) {
2102 myDataTable.subscribe('postRenderEvent',function(oArgs) {
2107 tooltip_activate();
2103 tooltip_activate();
2108 quick_repo_menu();
2104 quick_repo_menu();
2109 });
2105 });
2110
2106
2111 var filterTimeout = null;
2107 var filterTimeout = null;
2112 var $q_filter = $('#q_filter');
2108 var $q_filter = $('#q_filter');
2113
2109
2114 var updateFilter = function () {
2110 var updateFilter = function () {
2115 // Reset timeout
2111 // Reset timeout
2116 filterTimeout = null;
2112 filterTimeout = null;
2117
2113
2118 // Reset sort
2114 // Reset sort
2119 var state = myDataTable.getState();
2115 var state = myDataTable.getState();
2120 state.sortedBy = {key:sortkey, dir:YAHOO.widget.DataTable.CLASS_ASC};
2116 state.sortedBy = {key:sortkey, dir:YAHOO.widget.DataTable.CLASS_ASC};
2121
2117
2122 // Get filtered data
2118 // Get filtered data
2123 myDataSource.sendRequest($q_filter.val(), {
2119 myDataSource.sendRequest($q_filter.val(), {
2124 success : myDataTable.onDataReturnInitializeTable,
2120 success : myDataTable.onDataReturnInitializeTable,
2125 failure : myDataTable.onDataReturnInitializeTable,
2121 failure : myDataTable.onDataReturnInitializeTable,
2126 scope : myDataTable,
2122 scope : myDataTable,
2127 argument: state});
2123 argument: state});
2128 };
2124 };
2129
2125
2130 $q_filter.click(function(){
2126 $q_filter.click(function(){
2131 if(!$q_filter.hasClass('loaded')){
2127 if(!$q_filter.hasClass('loaded')){
2132 //TODO: load here full list later to do search within groups
2128 //TODO: load here full list later to do search within groups
2133 $q_filter.addClass('loaded');
2129 $q_filter.addClass('loaded');
2134 }
2130 }
2135 });
2131 });
2136
2132
2137 $q_filter.keyup(function (e) {
2133 $q_filter.keyup(function (e) {
2138 clearTimeout(filterTimeout);
2134 clearTimeout(filterTimeout);
2139 filterTimeout = setTimeout(updateFilter, 600);
2135 filterTimeout = setTimeout(updateFilter, 600);
2140 });
2136 });
2141 }
2137 }
2142
2138
2143 // global hooks after DOM is loaded
2139 // global hooks after DOM is loaded
2144
2140
2145 $(document).ready(function(){
2141 $(document).ready(function(){
2146 $('.diff-collapse-button').click(function(e) {
2142 $('.diff-collapse-button').click(function(e) {
2147 var $button = $(e.currentTarget);
2143 var $button = $(e.currentTarget);
2148 var $target = $('#' + $button.attr('target'));
2144 var $target = $('#' + $button.attr('target'));
2149 if($target.hasClass('hidden')){
2145 if($target.hasClass('hidden')){
2150 $target.removeClass('hidden');
2146 $target.removeClass('hidden');
2151 $button.html("&uarr; {0} &uarr;".format(_TM['Collapse Diff']));
2147 $button.html("&uarr; {0} &uarr;".format(_TM['Collapse Diff']));
2152 }
2148 }
2153 else if(!$target.hasClass('hidden')){
2149 else if(!$target.hasClass('hidden')){
2154 $target.addClass('hidden');
2150 $target.addClass('hidden');
2155 $button.html("&darr; {0} &darr;".format(_TM['Expand Diff']));
2151 $button.html("&darr; {0} &darr;".format(_TM['Expand Diff']));
2156 }
2152 }
2157 });
2153 });
2158 });
2154 });
@@ -1,95 +1,94 b''
1 <h3>${_('Parent')}</h3>
1 <h3>${_('Parent')}</h3>
2 ${h.form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')}
2 ${h.form(url('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='put')}
3 <div class="form">
3 <div class="form">
4 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
4 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
5 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small")}
5 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small")}
6 <div class="field" style="border:none;color:#888">
6 <div class="field" style="border:none;color:#888">
7 <ul>
7 <ul>
8 <li>${_('''Manually set this repository as a fork of another from the list.''')}</li>
8 <li>${_('''Manually set this repository as a fork of another from the list.''')}</li>
9 </ul>
9 </ul>
10 </div>
10 </div>
11 </div>
11 </div>
12 ${h.end_form()}
12 ${h.end_form()}
13
13
14 <script>
14 <script>
15 $(document).ready(function(){
15 $(document).ready(function(){
16 $("#id_fork_of").select2({
16 $("#id_fork_of").select2({
17 'dropdownAutoWidth': true
17 'dropdownAutoWidth': true
18 });
18 });
19 })
19 })
20 </script>
20 </script>
21
21
22 <h3>${_('Public Journal Visibility')}</h3>
22 <h3>${_('Public Journal Visibility')}</h3>
23 ${h.form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')}
23 ${h.form(url('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='put')}
24 <div class="form">
24 <div class="form">
25 ${h.hidden('auth_token',str(h.get_token()))}
26 <div class="field">
25 <div class="field">
27 %if c.in_public_journal:
26 %if c.in_public_journal:
28 <button class="btn btn-small" type="submit">
27 <button class="btn btn-small" type="submit">
29 <i class="icon-minus"></i>
28 <i class="icon-minus"></i>
30 ${_('Remove from public journal')}
29 ${_('Remove from public journal')}
31 </button>
30 </button>
32 %else:
31 %else:
33 <button class="btn btn-small" type="submit">
32 <button class="btn btn-small" type="submit">
34 <i class="icon-plus"></i>
33 <i class="icon-plus"></i>
35 ${_('Add to Public Journal')}
34 ${_('Add to Public Journal')}
36 </button>
35 </button>
37 %endif
36 %endif
38 </div>
37 </div>
39 <div class="field" style="border:none;color:#888">
38 <div class="field" style="border:none;color:#888">
40 <ul>
39 <ul>
41 <li>${_('All actions done in this repository will be visible to everyone in the public journal.')}</li>
40 <li>${_('All actions done in this repository will be visible to everyone in the public journal.')}</li>
42 </ul>
41 </ul>
43 </div>
42 </div>
44 </div>
43 </div>
45 ${h.end_form()}
44 ${h.end_form()}
46
45
47 <h3>${_('Change Locking')}</h3>
46 <h3>${_('Change Locking')}</h3>
48 ${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')}
47 ${h.form(url('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='put')}
49 <div class="form">
48 <div class="form">
50 %if c.repo_info.locked[0]:
49 %if c.repo_info.locked[0]:
51 ${h.hidden('set_unlock', '1')}
50 ${h.hidden('set_unlock', '1')}
52 <button class="btn btn-small" type="submit"
51 <button class="btn btn-small" type="submit"
53 onclick="return confirm('${_('Confirm to unlock repository.')}');">
52 onclick="return confirm('${_('Confirm to unlock repository.')}');">
54 <i class="icon-lock-open-alt"></i>
53 <i class="icon-lock-open-alt"></i>
55 ${_('Unlock Repository')}
54 ${_('Unlock Repository')}
56 </button>
55 </button>
57 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
56 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
58 %else:
57 %else:
59 ${h.hidden('set_lock', '1')}
58 ${h.hidden('set_lock', '1')}
60 <button class="btn btn-small" type="submit"
59 <button class="btn btn-small" type="submit"
61 onclick="return confirm('${_('Confirm to lock repository.')}');">
60 onclick="return confirm('${_('Confirm to lock repository.')}');">
62 <i class="icon-lock"></i>
61 <i class="icon-lock"></i>
63 ${_('Lock Repository')}
62 ${_('Lock Repository')}
64 </button>
63 </button>
65 ${_('Repository is not locked')}
64 ${_('Repository is not locked')}
66 %endif
65 %endif
67 <div class="field" style="border:none;color:#888">
66 <div class="field" style="border:none;color:#888">
68 <ul>
67 <ul>
69 <li>${_('Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository. The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push.')}
68 <li>${_('Force locking on the repository. Works only when anonymous access is disabled. Triggering a pull locks the repository. The user who is pulling locks the repository; only the user who pulled and locked it can unlock it by doing a push.')}
70 </li>
69 </li>
71 </ul>
70 </ul>
72 </div>
71 </div>
73 </div>
72 </div>
74 ${h.end_form()}
73 ${h.end_form()}
75
74
76 <h3>${_('Delete')}</h3>
75 <h3>${_('Delete')}</h3>
77 ${h.form(url('repo', repo_name=c.repo_name),method='delete')}
76 ${h.form(url('repo', repo_name=c.repo_name),method='delete')}
78 <div class="form">
77 <div class="form">
79 <button class="btn btn-small btn-danger" type="submit"
78 <button class="btn btn-small btn-danger" type="submit"
80 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
79 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
81 <i class="icon-minus-circled"></i>
80 <i class="icon-minus-circled"></i>
82 ${_('Delete this Repository')}
81 ${_('Delete this Repository')}
83 </button>
82 </button>
84 %if c.repo_info.forks.count():
83 %if c.repo_info.forks.count():
85 ${ungettext('This repository has %s fork', 'This repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
84 ${ungettext('This repository has %s fork', 'This repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
86 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
85 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
87 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
86 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
88 %endif
87 %endif
89 <div class="field" style="border:none;color:#888">
88 <div class="field" style="border:none;color:#888">
90 <ul>
89 <ul>
91 <li>${_('The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it.')}</li>
90 <li>${_('The deleted repository will be moved away and hidden until the administrator expires it. The administrator can both permanently delete it or restore it.')}</li>
92 </ul>
91 </ul>
93 </div>
92 </div>
94 </div>
93 </div>
95 ${h.end_form()}
94 ${h.end_form()}
@@ -1,596 +1,596 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- CONTENT -->
4 <!-- CONTENT -->
5 <div id="content">
5 <div id="content">
6 ${self.flash_msg()}
6 ${self.flash_msg()}
7 <div id="main">
7 <div id="main">
8 ${next.main()}
8 ${next.main()}
9 </div>
9 </div>
10 </div>
10 </div>
11 <!-- END CONTENT -->
11 <!-- END CONTENT -->
12
12
13 <!-- FOOTER -->
13 <!-- FOOTER -->
14 <div id="footer">
14 <div id="footer">
15 <div id="footer-inner" class="title">
15 <div id="footer-inner" class="title">
16 <div>
16 <div>
17 <p class="footer-link">
17 <p class="footer-link">
18 ${_('Server instance: %s') % c.instance_id if c.instance_id else ''}
18 ${_('Server instance: %s') % c.instance_id if c.instance_id else ''}
19 </p>
19 </p>
20 <p class="footer-link-right">
20 <p class="footer-link-right">
21 This site is powered by
21 This site is powered by
22 %if c.visual.show_version:
22 %if c.visual.show_version:
23 <a href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a> ${c.kallithea_version},
23 <a href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a> ${c.kallithea_version},
24 %else:
24 %else:
25 <a href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
25 <a href="${h.url('kallithea_project_url')}" target="_blank">Kallithea</a>,
26 %endif
26 %endif
27 which is
27 which is
28 <a href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2015 by various authors &amp; licensed under GPLv3</a>.
28 <a href="${h.canonical_url('about')}#copyright">&copy; 2010&ndash;2015 by various authors &amp; licensed under GPLv3</a>.
29 %if c.issues_url:
29 %if c.issues_url:
30 &ndash; <a href="${c.issues_url}" target="_blank">${_('Support')}</a>
30 &ndash; <a href="${c.issues_url}" target="_blank">${_('Support')}</a>
31 %endif
31 %endif
32 </p>
32 </p>
33 </div>
33 </div>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <!-- END FOOTER -->
37 <!-- END FOOTER -->
38
38
39 ### MAKO DEFS ###
39 ### MAKO DEFS ###
40
40
41 <%block name="branding_title">
41 <%block name="branding_title">
42 %if c.site_name:
42 %if c.site_name:
43 &middot; ${c.site_name}
43 &middot; ${c.site_name}
44 %endif
44 %endif
45 </%block>
45 </%block>
46
46
47 <%def name="flash_msg()">
47 <%def name="flash_msg()">
48 <%include file="/base/flash_msg.html"/>
48 <%include file="/base/flash_msg.html"/>
49 </%def>
49 </%def>
50
50
51 <%def name="breadcrumbs()">
51 <%def name="breadcrumbs()">
52 <div class="breadcrumbs">
52 <div class="breadcrumbs">
53 ${self.breadcrumbs_links()}
53 ${self.breadcrumbs_links()}
54 </div>
54 </div>
55 </%def>
55 </%def>
56
56
57 <%def name="admin_menu()">
57 <%def name="admin_menu()">
58 <ul class="admin_menu">
58 <ul class="admin_menu">
59 <li><a href="${h.url('admin_home')}"><i class="icon-book"></i> ${_('Admin Journal')}</a></li>
59 <li><a href="${h.url('admin_home')}"><i class="icon-book"></i> ${_('Admin Journal')}</a></li>
60 <li><a href="${h.url('repos')}"><i class="icon-database"></i> ${_('Repositories')}</a></li>
60 <li><a href="${h.url('repos')}"><i class="icon-database"></i> ${_('Repositories')}</a></li>
61 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
61 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
62 <li><a href="${h.url('users')}"><i class="icon-user"></i> ${_('Users')}</a></li>
62 <li><a href="${h.url('users')}"><i class="icon-user"></i> ${_('Users')}</a></li>
63 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
63 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
64 <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i> ${_('Permissions')}</a></li>
64 <li><a href="${h.url('admin_permissions')}"><i class="icon-block"></i> ${_('Permissions')}</a></li>
65 <li><a href="${h.url('auth_home')}"><i class="icon-key"></i> ${_('Authentication')}</a></li>
65 <li><a href="${h.url('auth_home')}"><i class="icon-key"></i> ${_('Authentication')}</a></li>
66 <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i> ${_('Defaults')}</a></li>
66 <li><a href="${h.url('defaults')}"><i class="icon-wrench"></i> ${_('Defaults')}</a></li>
67 <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
67 <li class="last"><a href="${h.url('admin_settings')}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
68 </ul>
68 </ul>
69
69
70 </%def>
70 </%def>
71
71
72
72
73 ## admin menu used for people that have some admin resources
73 ## admin menu used for people that have some admin resources
74 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
74 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
75 <ul>
75 <ul>
76 %if repositories:
76 %if repositories:
77 <li><a href="${h.url('repos')}"><i class="icon-database"></i> ${_('Repositories')}</a></li>
77 <li><a href="${h.url('repos')}"><i class="icon-database"></i> ${_('Repositories')}</a></li>
78 %endif
78 %endif
79 %if repository_groups:
79 %if repository_groups:
80 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
80 <li><a href="${h.url('repos_groups')}"><i class="icon-folder"></i> ${_('Repository Groups')}</a></li>
81 %endif
81 %endif
82 %if user_groups:
82 %if user_groups:
83 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
83 <li><a href="${h.url('users_groups')}"><i class="icon-users"></i> ${_('User Groups')}</a></li>
84 %endif
84 %endif
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88 <%def name="repotag(repo)">
88 <%def name="repotag(repo)">
89 %if h.is_hg(repo):
89 %if h.is_hg(repo):
90 <span class="repotag" title="${_('Mercurial repository')}">hg</span>
90 <span class="repotag" title="${_('Mercurial repository')}">hg</span>
91 %endif
91 %endif
92 %if h.is_git(repo):
92 %if h.is_git(repo):
93 <span class="repotag" title="${_('Git repository')}">git</span>
93 <span class="repotag" title="${_('Git repository')}">git</span>
94 %endif
94 %endif
95 </%def>
95 </%def>
96
96
97 <%def name="repo_context_bar(current=None, rev=None)">
97 <%def name="repo_context_bar(current=None, rev=None)">
98 <% rev = None if rev == 'tip' else rev %>
98 <% rev = None if rev == 'tip' else rev %>
99 <%
99 <%
100 def follow_class():
100 def follow_class():
101 if c.repository_following:
101 if c.repository_following:
102 return h.literal('following')
102 return h.literal('following')
103 else:
103 else:
104 return h.literal('follow')
104 return h.literal('follow')
105 %>
105 %>
106 <%
106 <%
107 def is_current(selected):
107 def is_current(selected):
108 if selected == current:
108 if selected == current:
109 return h.literal('class="current"')
109 return h.literal('class="current"')
110 %>
110 %>
111
111
112 <!--- CONTEXT BAR -->
112 <!--- CONTEXT BAR -->
113 <div id="context-bar" class="box">
113 <div id="context-bar" class="box">
114 <h2>
114 <h2>
115 ${repotag(c.db_repo)}
115 ${repotag(c.db_repo)}
116
116
117 ## public/private
117 ## public/private
118 %if c.db_repo.private:
118 %if c.db_repo.private:
119 <i class="icon-keyhole-circled"></i>
119 <i class="icon-keyhole-circled"></i>
120 %else:
120 %else:
121 <i class="icon-globe"></i>
121 <i class="icon-globe"></i>
122 %endif
122 %endif
123 ${h.repo_link(c.db_repo.groups_and_repo)}
123 ${h.repo_link(c.db_repo.groups_and_repo)}
124
124
125 %if current == 'createfork':
125 %if current == 'createfork':
126 - ${_('Create Fork')}
126 - ${_('Create Fork')}
127 %endif
127 %endif
128 </h2>
128 </h2>
129 <!--
129 <!--
130 <div id="breadcrumbs">
130 <div id="breadcrumbs">
131 ${h.link_to(_(u'Repositories'),h.url('home'))}
131 ${h.link_to(_(u'Repositories'),h.url('home'))}
132 &raquo;
132 &raquo;
133 ${h.repo_link(c.db_repo.groups_and_repo)}
133 ${h.repo_link(c.db_repo.groups_and_repo)}
134 </div>
134 </div>
135 -->
135 -->
136 <ul id="context-pages" class="horizontal-list">
136 <ul id="context-pages" class="horizontal-list">
137 <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-doc-text"></i> ${_('Summary')}</a></li>
137 <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}"><i class="icon-doc-text"></i> ${_('Summary')}</a></li>
138 %if rev:
138 %if rev:
139 <li ${is_current('changelog')}><a href="${h.url('changelog_file_home', repo_name=c.repo_name, revision=rev, f_path='')}"><i class="icon-clock"></i> ${_('Changelog')}</a></li>
139 <li ${is_current('changelog')}><a href="${h.url('changelog_file_home', repo_name=c.repo_name, revision=rev, f_path='')}"><i class="icon-clock"></i> ${_('Changelog')}</a></li>
140 %else:
140 %else:
141 <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-clock"></i> ${_('Changelog')}</a></li>
141 <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}"><i class="icon-clock"></i> ${_('Changelog')}</a></li>
142 %endif
142 %endif
143 <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name, revision=rev or 'tip')}"><i class="icon-doc-inv"></i> ${_('Files')}</a></li>
143 <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name, revision=rev or 'tip')}"><i class="icon-doc-inv"></i> ${_('Files')}</a></li>
144 <li ${is_current('switch-to')}>
144 <li ${is_current('switch-to')}>
145 <a href="#" id="branch_tag_switcher_2" class="dropdown"><i class="icon-exchange"></i> ${_('Switch To')}</a>
145 <a href="#" id="branch_tag_switcher_2" class="dropdown"><i class="icon-exchange"></i> ${_('Switch To')}</a>
146 <ul id="switch_to_list_2" class="switch_to submenu">
146 <ul id="switch_to_list_2" class="switch_to submenu">
147 <li><a href="#">${_('Loading...')}</a></li>
147 <li><a href="#">${_('Loading...')}</a></li>
148 </ul>
148 </ul>
149 </li>
149 </li>
150 <li ${is_current('options')}>
150 <li ${is_current('options')}>
151 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
151 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
152 <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown"><i class="icon-wrench"></i> ${_('Options')}</a>
152 <a href="${h.url('edit_repo',repo_name=c.repo_name)}" class="dropdown"><i class="icon-wrench"></i> ${_('Options')}</a>
153 %else:
153 %else:
154 <a href="#" class="dropdown"><i class="icon-wrench"></i> ${_('Options')}</a>
154 <a href="#" class="dropdown"><i class="icon-wrench"></i> ${_('Options')}</a>
155 %endif
155 %endif
156 <ul>
156 <ul>
157 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
157 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
158 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
158 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}"><i class="icon-gear"></i> ${_('Settings')}</a></li>
159 %endif
159 %endif
160 %if c.db_repo.fork:
160 %if c.db_repo.fork:
161 <li><a href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1], other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}">
161 <li><a href="${h.url('compare_url',repo_name=c.db_repo.fork.repo_name,org_ref_type=c.db_repo.landing_rev[0],org_ref_name=c.db_repo.landing_rev[1], other_repo=c.repo_name,other_ref_type='branch' if request.GET.get('branch') else c.db_repo.landing_rev[0],other_ref_name=request.GET.get('branch') or c.db_repo.landing_rev[1], merge=1)}">
162 <i class="icon-git-compare"></i> ${_('Compare Fork')}</a></li>
162 <i class="icon-git-compare"></i> ${_('Compare Fork')}</a></li>
163 %endif
163 %endif
164 <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-git-compare"></i> ${_('Compare')}</a></li>
164 <li><a href="${h.url('compare_home',repo_name=c.repo_name)}"><i class="icon-git-compare"></i> ${_('Compare')}</a></li>
165
165
166 <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i> ${_('Search')}</a></li>
166 <li><a href="${h.url('search_repo',repo_name=c.repo_name)}"><i class="icon-search"></i> ${_('Search')}</a></li>
167
167
168 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.db_repo.enable_locking:
168 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.db_repo.enable_locking:
169 %if c.db_repo.locked[0]:
169 %if c.db_repo.locked[0]:
170 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock"></i> ${_('Unlock')}</a></li>
170 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock"></i> ${_('Unlock')}</a></li>
171 %else:
171 %else:
172 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock-open-alt"></i> ${_('Lock')}</li>
172 <li><a href="${h.url('toggle_locking', repo_name=c.repo_name)}"><i class="icon-lock-open-alt"></i> ${_('Lock')}</li>
173 %endif
173 %endif
174 %endif
174 %endif
175 ## TODO: this check feels wrong, it would be better to have a check for permissions
175 ## TODO: this check feels wrong, it would be better to have a check for permissions
176 ## also it feels like a job for the controller
176 ## also it feels like a job for the controller
177 %if c.authuser.username != 'default':
177 %if c.authuser.username != 'default':
178 <li>
178 <li>
179 <a class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.db_repo.repo_id},'${str(h.get_token())}');">
179 <a class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.db_repo.repo_id});">
180 <span class="show-follow"><i class="icon-heart-empty"></i> ${_('Follow')}</span>
180 <span class="show-follow"><i class="icon-heart-empty"></i> ${_('Follow')}</span>
181 <span class="show-following"><i class="icon-heart"></i> ${_('Unfollow')}</span>
181 <span class="show-following"><i class="icon-heart"></i> ${_('Unfollow')}</span>
182 </a>
182 </a>
183 </li>
183 </li>
184 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i> ${_('Fork')}</a></li>
184 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i> ${_('Fork')}</a></li>
185 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i> ${_('Create Pull Request')}</a></li>
185 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}"><i class="icon-git-pull-request"></i> ${_('Create Pull Request')}</a></li>
186 %endif
186 %endif
187 </ul>
187 </ul>
188 </li>
188 </li>
189 <li ${is_current('showpullrequest')}>
189 <li ${is_current('showpullrequest')}>
190 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-git-pull-request"></i> ${_('Pull Requests')}
190 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}"> <i class="icon-git-pull-request"></i> ${_('Pull Requests')}
191 %if c.repository_pull_requests:
191 %if c.repository_pull_requests:
192 <span>${c.repository_pull_requests}</span>
192 <span>${c.repository_pull_requests}</span>
193 %endif
193 %endif
194 </a>
194 </a>
195 </li>
195 </li>
196 </ul>
196 </ul>
197 </div>
197 </div>
198 <script type="text/javascript">
198 <script type="text/javascript">
199 YUE.on('branch_tag_switcher_2','mouseover',function(){
199 YUE.on('branch_tag_switcher_2','mouseover',function(){
200 var $branch_tag_switcher_2 = $('#branch_tag_switcher_2');
200 var $branch_tag_switcher_2 = $('#branch_tag_switcher_2');
201 var loaded = $branch_tag_switcher_2.hasClass('loaded');
201 var loaded = $branch_tag_switcher_2.hasClass('loaded');
202 if(!loaded){
202 if(!loaded){
203 $branch_tag_switcher_2.addClass('loaded');
203 $branch_tag_switcher_2.addClass('loaded');
204 asynchtml("${h.url('branch_tag_switcher',repo_name=c.repo_name)}", $('#switch_to_list_2'));
204 asynchtml("${h.url('branch_tag_switcher',repo_name=c.repo_name)}", $('#switch_to_list_2'));
205 }
205 }
206 return false;
206 return false;
207 });
207 });
208 </script>
208 </script>
209 <!--- END CONTEXT BAR -->
209 <!--- END CONTEXT BAR -->
210 </%def>
210 </%def>
211
211
212 <%def name="menu(current=None)">
212 <%def name="menu(current=None)">
213 <%
213 <%
214 def is_current(selected):
214 def is_current(selected):
215 if selected == current:
215 if selected == current:
216 return h.literal('class="current"')
216 return h.literal('class="current"')
217 %>
217 %>
218
218
219 <ul id="quick" class="horizontal-list">
219 <ul id="quick" class="horizontal-list">
220 <!-- repo switcher -->
220 <!-- repo switcher -->
221 <li ${is_current('repositories')}>
221 <li ${is_current('repositories')}>
222 <input id="repo_switcher" name="repo_switcher" type="hidden">
222 <input id="repo_switcher" name="repo_switcher" type="hidden">
223 </li>
223 </li>
224
224
225 ##ROOT MENU
225 ##ROOT MENU
226 %if c.authuser.username != 'default':
226 %if c.authuser.username != 'default':
227 <li ${is_current('journal')}>
227 <li ${is_current('journal')}>
228 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
228 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
229 <i class="icon-book"></i> ${_('Journal')}
229 <i class="icon-book"></i> ${_('Journal')}
230 </a>
230 </a>
231 </li>
231 </li>
232 %else:
232 %else:
233 <li ${is_current('journal')}>
233 <li ${is_current('journal')}>
234 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
234 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
235 <i class="icon-book"></i> ${_('Public journal')}
235 <i class="icon-book"></i> ${_('Public journal')}
236 </a>
236 </a>
237 </li>
237 </li>
238 %endif
238 %endif
239 <li ${is_current('gists')}>
239 <li ${is_current('gists')}>
240 <a class="menu_link childs" title="${_('Show public gists')}" href="${h.url('gists')}">
240 <a class="menu_link childs" title="${_('Show public gists')}" href="${h.url('gists')}">
241 <i class="icon-clippy"></i> ${_('Gists')}
241 <i class="icon-clippy"></i> ${_('Gists')}
242 </a>
242 </a>
243 <ul class="admin_menu">
243 <ul class="admin_menu">
244 <li><a href="${h.url('new_gist', public=1)}"><i class="icon-paste"></i> ${_('Create New Gist')}</a></li>
244 <li><a href="${h.url('new_gist', public=1)}"><i class="icon-paste"></i> ${_('Create New Gist')}</a></li>
245 <li><a href="${h.url('gists')}"><i class="icon-globe"></i> ${_('All Public Gists')}</a></li>
245 <li><a href="${h.url('gists')}"><i class="icon-globe"></i> ${_('All Public Gists')}</a></li>
246 %if c.authuser.username != 'default':
246 %if c.authuser.username != 'default':
247 <li><a href="${h.url('gists', public=1)}"><i class="icon-user"></i> ${_('My Public Gists')}</a></li>
247 <li><a href="${h.url('gists', public=1)}"><i class="icon-user"></i> ${_('My Public Gists')}</a></li>
248 <li><a href="${h.url('gists', private=1)}"><i class="icon-keyhole-circled"></i> ${_('My Private Gists')}</a></li>
248 <li><a href="${h.url('gists', private=1)}"><i class="icon-keyhole-circled"></i> ${_('My Private Gists')}</a></li>
249 %endif
249 %endif
250 </ul>
250 </ul>
251 </li>
251 </li>
252 <li ${is_current('search')}>
252 <li ${is_current('search')}>
253 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
253 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
254 <i class="icon-search"></i> ${_('Search')}
254 <i class="icon-search"></i> ${_('Search')}
255 </a>
255 </a>
256 </li>
256 </li>
257 % if h.HasPermissionAll('hg.admin')('access admin main page'):
257 % if h.HasPermissionAll('hg.admin')('access admin main page'):
258 <li ${is_current('admin')}>
258 <li ${is_current('admin')}>
259 <a class="menu_link childs" title="${_('Admin')}" href="${h.url('admin_home')}">
259 <a class="menu_link childs" title="${_('Admin')}" href="${h.url('admin_home')}">
260 <i class="icon-gear"></i> ${_('Admin')}
260 <i class="icon-gear"></i> ${_('Admin')}
261 </a>
261 </a>
262 ${admin_menu()}
262 ${admin_menu()}
263 </li>
263 </li>
264 % elif c.authuser.repositories_admin or c.authuser.repository_groups_admin or c.authuser.user_groups_admin:
264 % elif c.authuser.repositories_admin or c.authuser.repository_groups_admin or c.authuser.user_groups_admin:
265 <li ${is_current('admin')}>
265 <li ${is_current('admin')}>
266 <a class="menu_link childs" title="${_('Admin')}">
266 <a class="menu_link childs" title="${_('Admin')}">
267 <i class="icon-gear"></i> ${_('Admin')}
267 <i class="icon-gear"></i> ${_('Admin')}
268 </a>
268 </a>
269 ${admin_menu_simple(c.authuser.repositories_admin,
269 ${admin_menu_simple(c.authuser.repositories_admin,
270 c.authuser.repository_groups_admin,
270 c.authuser.repository_groups_admin,
271 c.authuser.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
271 c.authuser.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
272 </li>
272 </li>
273 % endif
273 % endif
274
274
275 <li ${is_current('my_pullrequests')}>
275 <li ${is_current('my_pullrequests')}>
276 <a class="menu_link" title="${_('My Pull Requests')}" href="${h.url('my_pullrequests')}">
276 <a class="menu_link" title="${_('My Pull Requests')}" href="${h.url('my_pullrequests')}">
277 <i class="icon-git-pull-request"></i> ${_('My Pull Requests')}
277 <i class="icon-git-pull-request"></i> ${_('My Pull Requests')}
278 %if c.my_pr_count != 0:
278 %if c.my_pr_count != 0:
279 <span class="menu_link_notifications">${c.my_pr_count}</span>
279 <span class="menu_link_notifications">${c.my_pr_count}</span>
280 %endif
280 %endif
281 </a>
281 </a>
282 </li>
282 </li>
283
283
284 ## USER MENU
284 ## USER MENU
285 <li>
285 <li>
286 <a class="menu_link childs" id="quick_login_link">
286 <a class="menu_link childs" id="quick_login_link">
287 <span class="icon">
287 <span class="icon">
288 ${h.gravatar(c.authuser.email, size=20)}
288 ${h.gravatar(c.authuser.email, size=20)}
289 </span>
289 </span>
290 %if c.authuser.username != 'default':
290 %if c.authuser.username != 'default':
291 <span class="menu_link_user">${c.authuser.username}</span>
291 <span class="menu_link_user">${c.authuser.username}</span>
292 %if c.unread_notifications != 0:
292 %if c.unread_notifications != 0:
293 <span class="menu_link_notifications">${c.unread_notifications}</span>
293 <span class="menu_link_notifications">${c.unread_notifications}</span>
294 %endif
294 %endif
295 %else:
295 %else:
296 <span>${_('Not Logged In')}</span>
296 <span>${_('Not Logged In')}</span>
297 %endif
297 %endif
298 </a>
298 </a>
299
299
300 <div class="user-menu">
300 <div class="user-menu">
301 <div id="quick_login">
301 <div id="quick_login">
302 %if c.authuser.username == 'default' or c.authuser.user_id is None:
302 %if c.authuser.username == 'default' or c.authuser.user_id is None:
303 <h4>${_('Login to Your Account')}</h4>
303 <h4>${_('Login to Your Account')}</h4>
304 ${h.form(h.url('login_home',came_from=h.url.current()))}
304 ${h.form(h.url('login_home',came_from=h.url.current()))}
305 <div class="form">
305 <div class="form">
306 <div class="fields">
306 <div class="fields">
307 <div class="field">
307 <div class="field">
308 <div class="label">
308 <div class="label">
309 <label for="username">${_('Username')}:</label>
309 <label for="username">${_('Username')}:</label>
310 </div>
310 </div>
311 <div class="input">
311 <div class="input">
312 ${h.text('username',class_='focus')}
312 ${h.text('username',class_='focus')}
313 </div>
313 </div>
314
314
315 </div>
315 </div>
316 <div class="field">
316 <div class="field">
317 <div class="label">
317 <div class="label">
318 <label for="password">${_('Password')}:</label>
318 <label for="password">${_('Password')}:</label>
319 </div>
319 </div>
320 <div class="input">
320 <div class="input">
321 ${h.password('password',class_='focus')}
321 ${h.password('password',class_='focus')}
322 </div>
322 </div>
323
323
324 </div>
324 </div>
325 <div class="buttons">
325 <div class="buttons">
326 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
326 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
327 <div class="register">
327 <div class="register">
328 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
328 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
329 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
329 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
330 %endif
330 %endif
331 </div>
331 </div>
332 <div class="submit">
332 <div class="submit">
333 ${h.submit('sign_in',_('Log In'),class_="btn btn-mini")}
333 ${h.submit('sign_in',_('Log In'),class_="btn btn-mini")}
334 </div>
334 </div>
335 </div>
335 </div>
336 </div>
336 </div>
337 </div>
337 </div>
338 ${h.end_form()}
338 ${h.end_form()}
339 %else:
339 %else:
340 <div class="links_left">
340 <div class="links_left">
341 <div class="big_gravatar">
341 <div class="big_gravatar">
342 ${h.gravatar(c.authuser.email, size=48)}
342 ${h.gravatar(c.authuser.email, size=48)}
343 </div>
343 </div>
344 <div class="full_name">${c.authuser.full_name_or_username}</div>
344 <div class="full_name">${c.authuser.full_name_or_username}</div>
345 <div class="email">${c.authuser.email}</div>
345 <div class="email">${c.authuser.email}</div>
346 </div>
346 </div>
347 <div class="links_right">
347 <div class="links_right">
348 <ol class="links">
348 <ol class="links">
349 <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
349 <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
350 <li>${h.link_to(_(u'My Account'),h.url('my_account'))}</li>
350 <li>${h.link_to(_(u'My Account'),h.url('my_account'))}</li>
351 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
351 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
352 </ol>
352 </ol>
353 </div>
353 </div>
354 %endif
354 %endif
355 </div>
355 </div>
356 </div>
356 </div>
357 </li>
357 </li>
358
358
359 <script type="text/javascript">
359 <script type="text/javascript">
360 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
360 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
361 var cache = {}
361 var cache = {}
362 /*format the look of items in the list*/
362 /*format the look of items in the list*/
363 var format = function(state){
363 var format = function(state){
364 if (!state.id){
364 if (!state.id){
365 return state.text; // optgroup
365 return state.text; // optgroup
366 }
366 }
367 var obj_dict = state.obj;
367 var obj_dict = state.obj;
368 var tmpl = '';
368 var tmpl = '';
369
369
370 if(obj_dict && state.type == 'repo'){
370 if(obj_dict && state.type == 'repo'){
371 tmpl += '<span class="repo-icons">';
371 tmpl += '<span class="repo-icons">';
372 if(obj_dict['repo_type'] === 'hg'){
372 if(obj_dict['repo_type'] === 'hg'){
373 tmpl += '<span class="repotag">hg</span> ';
373 tmpl += '<span class="repotag">hg</span> ';
374 }
374 }
375 else if(obj_dict['repo_type'] === 'git'){
375 else if(obj_dict['repo_type'] === 'git'){
376 tmpl += '<span class="repotag">git</span> ';
376 tmpl += '<span class="repotag">git</span> ';
377 }
377 }
378 if(obj_dict['private']){
378 if(obj_dict['private']){
379 tmpl += '<i class="icon-keyhole-circled"></i> ';
379 tmpl += '<i class="icon-keyhole-circled"></i> ';
380 }
380 }
381 else if(visual_show_public_icon){
381 else if(visual_show_public_icon){
382 tmpl += '<i class="icon-globe"></i> ';
382 tmpl += '<i class="icon-globe"></i> ';
383 }
383 }
384 tmpl += '</span>';
384 tmpl += '</span>';
385 }
385 }
386 if(obj_dict && state.type == 'group'){
386 if(obj_dict && state.type == 'group'){
387 tmpl += '<i class="icon-folder"></i> ';
387 tmpl += '<i class="icon-folder"></i> ';
388 }
388 }
389 tmpl += state.text;
389 tmpl += state.text;
390 return tmpl;
390 return tmpl;
391 }
391 }
392
392
393 $("#repo_switcher").select2({
393 $("#repo_switcher").select2({
394 placeholder: '<i class="icon-database"></i> ${_('Repositories')}',
394 placeholder: '<i class="icon-database"></i> ${_('Repositories')}',
395 dropdownAutoWidth: true,
395 dropdownAutoWidth: true,
396 formatResult: format,
396 formatResult: format,
397 formatSelection: format,
397 formatSelection: format,
398 formatNoMatches: function(term){
398 formatNoMatches: function(term){
399 return "${_('No matches found')}";
399 return "${_('No matches found')}";
400 },
400 },
401 containerCssClass: "repo-switcher",
401 containerCssClass: "repo-switcher",
402 dropdownCssClass: "repo-switcher-dropdown",
402 dropdownCssClass: "repo-switcher-dropdown",
403 escapeMarkup: function(m){
403 escapeMarkup: function(m){
404 // don't escape our custom placeholder
404 // don't escape our custom placeholder
405 if(m.substr(0,29) == '<i class="icon-database"></i>'){
405 if(m.substr(0,29) == '<i class="icon-database"></i>'){
406 return m;
406 return m;
407 }
407 }
408
408
409 return Select2.util.escapeMarkup(m);
409 return Select2.util.escapeMarkup(m);
410 },
410 },
411 query: function(query){
411 query: function(query){
412 var key = 'cache';
412 var key = 'cache';
413 var cached = cache[key] ;
413 var cached = cache[key] ;
414 if(cached) {
414 if(cached) {
415 var data = {results: []};
415 var data = {results: []};
416 //filter results
416 //filter results
417 $.each(cached.results, function(){
417 $.each(cached.results, function(){
418 var section = this.text;
418 var section = this.text;
419 var children = [];
419 var children = [];
420 $.each(this.children, function(){
420 $.each(this.children, function(){
421 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
421 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
422 children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj})
422 children.push({'id': this.id, 'text': this.text, 'type': this.type, 'obj': this.obj})
423 }
423 }
424 })
424 })
425 if(children.length !== 0){
425 if(children.length !== 0){
426 data.results.push({'text': section, 'children': children})
426 data.results.push({'text': section, 'children': children})
427 }
427 }
428
428
429 });
429 });
430 query.callback(data);
430 query.callback(data);
431 }else{
431 }else{
432 $.ajax({
432 $.ajax({
433 url: "${h.url('repo_switcher_data')}",
433 url: "${h.url('repo_switcher_data')}",
434 data: {},
434 data: {},
435 dataType: 'json',
435 dataType: 'json',
436 type: 'GET',
436 type: 'GET',
437 success: function(data) {
437 success: function(data) {
438 cache[key] = data;
438 cache[key] = data;
439 query.callback({results: data.results});
439 query.callback({results: data.results});
440 }
440 }
441 })
441 })
442 }
442 }
443 }
443 }
444 });
444 });
445
445
446 $("#repo_switcher").on('select2-selecting', function(e){
446 $("#repo_switcher").on('select2-selecting', function(e){
447 e.preventDefault();
447 e.preventDefault();
448 window.location = pyroutes.url('summary_home', {'repo_name': e.val})
448 window.location = pyroutes.url('summary_home', {'repo_name': e.val})
449 })
449 })
450
450
451 ## Global mouse bindings ##
451 ## Global mouse bindings ##
452
452
453 // general help "?"
453 // general help "?"
454 Mousetrap.bind(['?'], function(e) {
454 Mousetrap.bind(['?'], function(e) {
455 $('#help_kb').modal({})
455 $('#help_kb').modal({})
456 });
456 });
457
457
458 // / open the quick filter
458 // / open the quick filter
459 Mousetrap.bind(['/'], function(e) {
459 Mousetrap.bind(['/'], function(e) {
460 $("#repo_switcher").select2("open");
460 $("#repo_switcher").select2("open");
461
461
462 // return false to prevent default browser behavior
462 // return false to prevent default browser behavior
463 // and stop event from bubbling
463 // and stop event from bubbling
464 return false;
464 return false;
465 });
465 });
466
466
467 // ctrl/command+b, show the the main bar
467 // ctrl/command+b, show the the main bar
468 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
468 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
469 if($('#header-inner').hasClass('hover') && $('#content').hasClass('hover')){
469 if($('#header-inner').hasClass('hover') && $('#content').hasClass('hover')){
470 $('#header-inner').removeClass('hover');
470 $('#header-inner').removeClass('hover');
471 $('#content').removeClass('hover');
471 $('#content').removeClass('hover');
472 }
472 }
473 else{
473 else{
474 $('#header-inner').addClass('hover');
474 $('#header-inner').addClass('hover');
475 $('#content').addClass('hover');
475 $('#content').addClass('hover');
476 }
476 }
477 return false;
477 return false;
478 });
478 });
479
479
480 // general nav g + action
480 // general nav g + action
481 Mousetrap.bind(['g h'], function(e) {
481 Mousetrap.bind(['g h'], function(e) {
482 window.location = pyroutes.url('home');
482 window.location = pyroutes.url('home');
483 });
483 });
484 Mousetrap.bind(['g g'], function(e) {
484 Mousetrap.bind(['g g'], function(e) {
485 window.location = pyroutes.url('gists', {'private':1});
485 window.location = pyroutes.url('gists', {'private':1});
486 });
486 });
487 Mousetrap.bind(['g G'], function(e) {
487 Mousetrap.bind(['g G'], function(e) {
488 window.location = pyroutes.url('gists', {'public':1});
488 window.location = pyroutes.url('gists', {'public':1});
489 });
489 });
490 Mousetrap.bind(['n g'], function(e) {
490 Mousetrap.bind(['n g'], function(e) {
491 window.location = pyroutes.url('new_gist');
491 window.location = pyroutes.url('new_gist');
492 });
492 });
493 Mousetrap.bind(['n r'], function(e) {
493 Mousetrap.bind(['n r'], function(e) {
494 window.location = pyroutes.url('new_repo');
494 window.location = pyroutes.url('new_repo');
495 });
495 });
496
496
497 % if hasattr(c, 'repo_name') and hasattr(c, 'db_repo'):
497 % if hasattr(c, 'repo_name') and hasattr(c, 'db_repo'):
498 // nav in repo context
498 // nav in repo context
499 Mousetrap.bind(['g s'], function(e) {
499 Mousetrap.bind(['g s'], function(e) {
500 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
500 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
501 });
501 });
502 Mousetrap.bind(['g c'], function(e) {
502 Mousetrap.bind(['g c'], function(e) {
503 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
503 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
504 });
504 });
505 Mousetrap.bind(['g F'], function(e) {
505 Mousetrap.bind(['g F'], function(e) {
506 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
506 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
507 });
507 });
508 Mousetrap.bind(['g f'], function(e) {
508 Mousetrap.bind(['g f'], function(e) {
509 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.db_repo.landing_rev[1]}', 'f_path': ''});
509 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.db_repo.landing_rev[1]}', 'f_path': ''});
510 });
510 });
511 Mousetrap.bind(['g o'], function(e) {
511 Mousetrap.bind(['g o'], function(e) {
512 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
512 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
513 });
513 });
514 Mousetrap.bind(['g O'], function(e) {
514 Mousetrap.bind(['g O'], function(e) {
515 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
515 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
516 });
516 });
517 % endif
517 % endif
518
518
519 </script>
519 </script>
520 </%def>
520 </%def>
521
521
522 %if 0:
522 %if 0:
523 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
523 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
524 <div class="modal-dialog">
524 <div class="modal-dialog">
525 <div class="modal-content">
525 <div class="modal-content">
526 <div class="modal-header">
526 <div class="modal-header">
527 <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="icon-cancel-circled"></i></button>
527 <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="icon-cancel-circled"></i></button>
528 <h4 class="modal-title">${_('Keyboard shortcuts')}</h4>
528 <h4 class="modal-title">${_('Keyboard shortcuts')}</h4>
529 </div>
529 </div>
530 <div class="modal-body">
530 <div class="modal-body">
531 <div class="row">
531 <div class="row">
532 <div class="col-md-5">
532 <div class="col-md-5">
533 <table class="keyboard-mappings">
533 <table class="keyboard-mappings">
534 <tbody>
534 <tbody>
535 <tr>
535 <tr>
536 <th></th>
536 <th></th>
537 <th>${_('Site-wide shortcuts')}</th>
537 <th>${_('Site-wide shortcuts')}</th>
538 </tr>
538 </tr>
539 <%
539 <%
540 elems = [
540 elems = [
541 ('/', 'Open quick search box'),
541 ('/', 'Open quick search box'),
542 ('ctrl/cmd+b', 'Show main settings bar'),
542 ('ctrl/cmd+b', 'Show main settings bar'),
543 ('g h', 'Goto home page'),
543 ('g h', 'Goto home page'),
544 ('g g', 'Goto my private gists page'),
544 ('g g', 'Goto my private gists page'),
545 ('g G', 'Goto my public gists page'),
545 ('g G', 'Goto my public gists page'),
546 ('n r', 'New repository page'),
546 ('n r', 'New repository page'),
547 ('n g', 'New gist page')
547 ('n g', 'New gist page')
548 ]
548 ]
549 %>
549 %>
550 %for key, desc in elems:
550 %for key, desc in elems:
551 <tr>
551 <tr>
552 <td class="keys">
552 <td class="keys">
553 <span class="key">${key}</span>
553 <span class="key">${key}</span>
554 </td>
554 </td>
555 <td>${desc}</td>
555 <td>${desc}</td>
556 </tr>
556 </tr>
557 %endfor
557 %endfor
558 </tbody>
558 </tbody>
559 </table>
559 </table>
560 </div>
560 </div>
561 <div class="col-md-offset-5">
561 <div class="col-md-offset-5">
562 <table class="keyboard-mappings">
562 <table class="keyboard-mappings">
563 <tbody>
563 <tbody>
564 <tr>
564 <tr>
565 <th></th>
565 <th></th>
566 <th>${_('Repositories')}</th>
566 <th>${_('Repositories')}</th>
567 </tr>
567 </tr>
568 <%
568 <%
569 elems = [
569 elems = [
570 ('g s', 'Goto summary page'),
570 ('g s', 'Goto summary page'),
571 ('g c', 'Goto changelog page'),
571 ('g c', 'Goto changelog page'),
572 ('g f', 'Goto files page'),
572 ('g f', 'Goto files page'),
573 ('g F', 'Goto files page with file search activated'),
573 ('g F', 'Goto files page with file search activated'),
574 ('g o', 'Goto repository settings'),
574 ('g o', 'Goto repository settings'),
575 ('g O', 'Goto repository permissions settings')
575 ('g O', 'Goto repository permissions settings')
576 ]
576 ]
577 %>
577 %>
578 %for key, desc in elems:
578 %for key, desc in elems:
579 <tr>
579 <tr>
580 <td class="keys">
580 <td class="keys">
581 <span class="key">${key}</span>
581 <span class="key">${key}</span>
582 </td>
582 </td>
583 <td>${desc}</td>
583 <td>${desc}</td>
584 </tr>
584 </tr>
585 %endfor
585 %endfor
586 </tbody>
586 </tbody>
587 </table>
587 </table>
588 </div>
588 </div>
589 </div>
589 </div>
590 </div>
590 </div>
591 <div class="modal-footer">
591 <div class="modal-footer">
592 </div>
592 </div>
593 </div><!-- /.modal-content -->
593 </div><!-- /.modal-content -->
594 </div><!-- /.modal-dialog -->
594 </div><!-- /.modal-dialog -->
595 </div><!-- /.modal -->
595 </div><!-- /.modal -->
596 %endif
596 %endif
@@ -1,217 +1,217 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
4
4
5 <%namespace name="base" file="/base/base.html"/>
5 <%namespace name="base" file="/base/base.html"/>
6
6
7 <%def name="quick_menu(repo_name)">
7 <%def name="quick_menu(repo_name)">
8 <ul class="menu_items hidden">
8 <ul class="menu_items hidden">
9 ##<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
9 ##<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu">
10
10
11 <li style="border-top:1px solid #577632; margin-left: 21px; padding-left: -99px;"></li>
11 <li style="border-top:1px solid #577632; margin-left: 21px; padding-left: -99px;"></li>
12 <li>
12 <li>
13 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
13 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
14 <span class="icon">
14 <span class="icon">
15 <i class="icon-doc-text-inv"></i>
15 <i class="icon-doc-text-inv"></i>
16 </span>
16 </span>
17 <span>${_('Summary')}</span>
17 <span>${_('Summary')}</span>
18 </a>
18 </a>
19 </li>
19 </li>
20 <li>
20 <li>
21 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
21 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
22 <span class="icon">
22 <span class="icon">
23 <i class="icon-clock"></i>
23 <i class="icon-clock"></i>
24 </span>
24 </span>
25 <span>${_('Changelog')}</span>
25 <span>${_('Changelog')}</span>
26 </a>
26 </a>
27 </li>
27 </li>
28 <li>
28 <li>
29 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
29 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
30 <span class="icon">
30 <span class="icon">
31 <i class="icon-docs"></i>
31 <i class="icon-docs"></i>
32 </span>
32 </span>
33 <span>${_('Files')}</span>
33 <span>${_('Files')}</span>
34 </a>
34 </a>
35 </li>
35 </li>
36 <li>
36 <li>
37 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
37 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
38 <span class="icon">
38 <span class="icon">
39 <i class="icon-fork"></i>
39 <i class="icon-fork"></i>
40 </span>
40 </span>
41 <span>${_('Fork')}</span>
41 <span>${_('Fork')}</span>
42 </a>
42 </a>
43 </li>
43 </li>
44 <li>
44 <li>
45 <a title="${_('Settings')}" href="${h.url('edit_repo',repo_name=repo_name)}">
45 <a title="${_('Settings')}" href="${h.url('edit_repo',repo_name=repo_name)}">
46 <span class="icon">
46 <span class="icon">
47 <i class="icon-gear"></i>
47 <i class="icon-gear"></i>
48 </span>
48 </span>
49 <span>${_('Settings')}</span>
49 <span>${_('Settings')}</span>
50 </a>
50 </a>
51 </li>
51 </li>
52 </ul>
52 </ul>
53 </%def>
53 </%def>
54
54
55 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
55 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
56 <%
56 <%
57 def get_name(name,short_name=short_name):
57 def get_name(name,short_name=short_name):
58 if short_name:
58 if short_name:
59 return name.split('/')[-1]
59 return name.split('/')[-1]
60 else:
60 else:
61 return name
61 return name
62 %>
62 %>
63 <div class="dt_repo ${'dt_repo_pending' if rstate == 'repo_state_pending' else ''}">
63 <div class="dt_repo ${'dt_repo_pending' if rstate == 'repo_state_pending' else ''}">
64 ##NAME
64 ##NAME
65 <a href="${h.url('edit_repo' if admin else 'summary_home', repo_name=name)}">
65 <a href="${h.url('edit_repo' if admin else 'summary_home', repo_name=name)}">
66
66
67 ##TYPE OF REPO
67 ##TYPE OF REPO
68 ${base.repotag(rtype)}
68 ${base.repotag(rtype)}
69
69
70 ##PRIVATE/PUBLIC
70 ##PRIVATE/PUBLIC
71 %if private and c.visual.show_private_icon:
71 %if private and c.visual.show_private_icon:
72 <i class="icon-keyhole-circled" title="${_('Private repository')}"></i>
72 <i class="icon-keyhole-circled" title="${_('Private repository')}"></i>
73 %elif not private and c.visual.show_public_icon:
73 %elif not private and c.visual.show_public_icon:
74 <i class="icon-globe" title="${_('Public repository')}"></i>
74 <i class="icon-globe" title="${_('Public repository')}"></i>
75 %else:
75 %else:
76 <span style="margin: 0px 8px 0px 8px"></span>
76 <span style="margin: 0px 8px 0px 8px"></span>
77 %endif
77 %endif
78 <span class="dt_repo_name">${get_name(name)}</span>
78 <span class="dt_repo_name">${get_name(name)}</span>
79 </a>
79 </a>
80 %if fork_of:
80 %if fork_of:
81 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-fork"></i></a>
81 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-fork"></i></a>
82 %endif
82 %endif
83 %if rstate == 'repo_state_pending':
83 %if rstate == 'repo_state_pending':
84 <i class="icon-wrench" title="${_('Repository creation in progress...')}"></i>
84 <i class="icon-wrench" title="${_('Repository creation in progress...')}"></i>
85 %endif
85 %endif
86 </div>
86 </div>
87 </%def>
87 </%def>
88
88
89 <%def name="last_change(last_change)">
89 <%def name="last_change(last_change)">
90 <span class="tooltip" date="${last_change}" title="${h.tooltip(h.fmt_date(last_change))}">${h.age(last_change)}</span>
90 <span class="tooltip" date="${last_change}" title="${h.tooltip(h.fmt_date(last_change))}">${h.age(last_change)}</span>
91 </%def>
91 </%def>
92
92
93 <%def name="revision(name,rev,tip,author,last_msg)">
93 <%def name="revision(name,rev,tip,author,last_msg)">
94 <div>
94 <div>
95 %if rev >= 0:
95 %if rev >= 0:
96 <a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip revision-link" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a>
96 <a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip revision-link" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a>
97 %else:
97 %else:
98 ${_('No changesets yet')}
98 ${_('No changesets yet')}
99 %endif
99 %endif
100 </div>
100 </div>
101 </%def>
101 </%def>
102
102
103 <%def name="rss(name)">
103 <%def name="rss(name)">
104 %if c.authuser.username != 'default':
104 %if c.authuser.username != 'default':
105 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
105 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
106 %else:
106 %else:
107 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
107 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
108 %endif
108 %endif
109 </%def>
109 </%def>
110
110
111 <%def name="atom(name)">
111 <%def name="atom(name)">
112 %if c.authuser.username != 'default':
112 %if c.authuser.username != 'default':
113 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
113 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i></a>
114 %else:
114 %else:
115 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
115 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-squared"></i></a>
116 %endif
116 %endif
117 </%def>
117 </%def>
118
118
119 <%def name="repo_actions(repo_name, super_user=True)">
119 <%def name="repo_actions(repo_name, super_user=True)">
120 <div>
120 <div>
121 <div style="float:left; margin-right:5px;" class="grid_edit">
121 <div style="float:left; margin-right:5px;" class="grid_edit">
122 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('edit')}">
122 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('edit')}">
123 <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('edit'),class_="action_button")}
123 <i class="icon-pencil"></i> ${h.submit('edit_%s' % repo_name,_('edit'),class_="action_button")}
124 </a>
124 </a>
125 </div>
125 </div>
126 <div style="float:left" class="grid_delete">
126 <div style="float:left" class="grid_delete">
127 ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
127 ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
128 <i class="icon-minus-circled" style="color:#FF4444"></i>
128 <i class="icon-minus-circled" style="color:#FF4444"></i>
129 ${h.submit('remove_%s' % repo_name,_('delete'),class_="action_button",
129 ${h.submit('remove_%s' % repo_name,_('delete'),class_="action_button",
130 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
130 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
131 ${h.end_form()}
131 ${h.end_form()}
132 </div>
132 </div>
133 </div>
133 </div>
134 </%def>
134 </%def>
135
135
136 <%def name="repo_state(repo_state)">
136 <%def name="repo_state(repo_state)">
137 <div>
137 <div>
138 %if repo_state == 'repo_state_pending':
138 %if repo_state == 'repo_state_pending':
139 <div class="btn btn-mini btn-info disabled">${_('Creating')}</div>
139 <div class="btn btn-mini btn-info disabled">${_('Creating')}</div>
140 %elif repo_state == 'repo_state_created':
140 %elif repo_state == 'repo_state_created':
141 <div class="btn btn-mini btn-success disabled">${_('Created')}</div>
141 <div class="btn btn-mini btn-success disabled">${_('Created')}</div>
142 %else:
142 %else:
143 <div class="btn btn-mini btn-danger disabled" title="${repo_state}">invalid</div>
143 <div class="btn btn-mini btn-danger disabled" title="${repo_state}">invalid</div>
144 %endif
144 %endif
145 </div>
145 </div>
146 </%def>
146 </%def>
147
147
148 <%def name="user_actions(user_id, username)">
148 <%def name="user_actions(user_id, username)">
149 <div style="float:left" class="grid_edit">
149 <div style="float:left" class="grid_edit">
150 <a href="${h.url('edit_user',id=user_id)}" title="${_('edit')}">
150 <a href="${h.url('edit_user',id=user_id)}" title="${_('edit')}">
151 <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('edit'),class_="action_button")}
151 <i class="icon-pencil"></i> ${h.submit('edit_%s' % username,_('edit'),class_="action_button")}
152 </a>
152 </a>
153 </div>
153 </div>
154 <div style="float:left" class="grid_delete">
154 <div style="float:left" class="grid_delete">
155 ${h.form(h.url('delete_user', id=user_id),method='delete')}
155 ${h.form(h.url('delete_user', id=user_id),method='delete')}
156 <i class="icon-minus-circled" style="color:#FF4444"></i>
156 <i class="icon-minus-circled" style="color:#FF4444"></i>
157 ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id, class_="action_button",
157 ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id, class_="action_button",
158 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
158 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
159 ${h.end_form()}
159 ${h.end_form()}
160 </div>
160 </div>
161 </%def>
161 </%def>
162
162
163 <%def name="user_group_actions(user_group_id, user_group_name)">
163 <%def name="user_group_actions(user_group_id, user_group_name)">
164 <div style="float:left" class="grid_edit">
164 <div style="float:left" class="grid_edit">
165 <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}">
165 <a href="${h.url('edit_users_group', id=user_group_id)}" title="${_('Edit')}">
166 <i class="icon-pencil"></i>
166 <i class="icon-pencil"></i>
167 ${h.submit('edit_%s' % user_group_name,_('edit'),class_="action_button", id_="submit_user_group_edit")}
167 ${h.submit('edit_%s' % user_group_name,_('edit'),class_="action_button", id_="submit_user_group_edit")}
168 </a>
168 </a>
169 </div>
169 </div>
170 <div style="float:left" class="grid_delete">
170 <div style="float:left" class="grid_delete">
171 ${h.form(h.url('users_group', id=user_group_id),method='delete')}
171 ${h.form(h.url('users_group', id=user_group_id),method='delete')}
172 <i class="icon-minus-circled" style="color:#FF4444"></i>
172 <i class="icon-minus-circled" style="color:#FF4444"></i>
173 ${h.submit('remove_',_('delete'),id="remove_group_%s" % user_group_id, class_="action_button",
173 ${h.submit('remove_',_('delete'),id="remove_group_%s" % user_group_id, class_="action_button",
174 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
174 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
175 ${h.end_form()}
175 ${h.end_form()}
176 </div>
176 </div>
177 </%def>
177 </%def>
178
178
179 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
180 <div style="float:left" class="grid_edit">
180 <div style="float:left" class="grid_edit">
181 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">
181 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">
182 <i class="icon-pencil"></i>
182 <i class="icon-pencil"></i>
183 ${h.submit('edit_%s' % repo_group_name, _('edit'),class_="action_button")}
183 ${h.submit('edit_%s' % repo_group_name, _('edit'),class_="action_button")}
184 </a>
184 </a>
185 </div>
185 </div>
186 <div style="float:left" class="grid_delete">
186 <div style="float:left" class="grid_delete">
187 ${h.form(h.url('repos_group', group_name=repo_group_name),method='delete')}
187 ${h.form(h.url('repos_group', group_name=repo_group_name),method='delete')}
188 <i class="icon-minus-circled" style="color:#FF4444"></i>
188 <i class="icon-minus-circled" style="color:#FF4444"></i>
189 ${h.submit('remove_%s' % repo_group_name,_('delete'),class_="action_button",
189 ${h.submit('remove_%s' % repo_group_name,_('delete'),class_="action_button",
190 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
190 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
191 ${h.end_form()}
191 ${h.end_form()}
192 </div>
192 </div>
193 </%def>
193 </%def>
194
194
195 <%def name="user_name(user_id, username)">
195 <%def name="user_name(user_id, username)">
196 ${h.link_to(username,h.url('edit_user', id=user_id))}
196 ${h.link_to(username,h.url('edit_user', id=user_id))}
197 </%def>
197 </%def>
198
198
199 <%def name="repo_group_name(repo_group_name, children_groups)">
199 <%def name="repo_group_name(repo_group_name, children_groups)">
200 <div style="white-space: nowrap">
200 <div style="white-space: nowrap">
201 <a href="${h.url('repos_group_home',group_name=repo_group_name)}">
201 <a href="${h.url('repos_group_home',group_name=repo_group_name)}">
202 <i class="icon-folder" title="${_('Repository group')}"></i> ${h.literal(' &raquo; '.join(children_groups))}</a>
202 <i class="icon-folder" title="${_('Repository group')}"></i> ${h.literal(' &raquo; '.join(children_groups))}</a>
203 </div>
203 </div>
204 </%def>
204 </%def>
205
205
206 <%def name="user_group_name(user_group_id, user_group_name)">
206 <%def name="user_group_name(user_group_id, user_group_name)">
207 <div style="white-space: nowrap">
207 <div style="white-space: nowrap">
208 <a href="${h.url('edit_users_group', id=user_group_id)}">
208 <a href="${h.url('edit_users_group', id=user_group_id)}">
209 <i class="icon-users" title="${_('User group')}"></i> ${user_group_name}</a>
209 <i class="icon-users" title="${_('User group')}"></i> ${user_group_name}</a>
210 </div>
210 </div>
211 </%def>
211 </%def>
212
212
213 <%def name="toggle_follow(repo_id)">
213 <%def name="toggle_follow(repo_id)">
214 <span id="follow_toggle_${repo_id}" class="following" title="${_('Stop following this repository')}"
214 <span id="follow_toggle_${repo_id}" class="following" title="${_('Stop following this repository')}"
215 onclick="javascript:toggleFollowingRepo(this, ${repo_id},'${str(h.get_token())}')">
215 onclick="javascript:toggleFollowingRepo(this, ${repo_id})">
216 </span>
216 </span>
217 </%def>
217 </%def>
@@ -1,400 +1,400 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%block name="title">
3 <%block name="title">
4 ${_('%s Summary') % c.repo_name}
4 ${_('%s Summary') % c.repo_name}
5 </%block>
5 </%block>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('Summary')}
8 ${_('Summary')}
9
9
10 ## locking icon
10 ## locking icon
11 %if c.db_repo.enable_locking:
11 %if c.db_repo.enable_locking:
12 %if c.db_repo.locked[0]:
12 %if c.db_repo.locked[0]:
13 <span class="locking_locked tooltip icon-block" title="${_('Repository locked by %s') % h.person_by_id(c.db_repo.locked[0])}"></span>
13 <span class="locking_locked tooltip icon-block" title="${_('Repository locked by %s') % h.person_by_id(c.db_repo.locked[0])}"></span>
14 %else:
14 %else:
15 <span class="locking_unlocked tooltip icon-ok" title="${_('Repository unlocked')}"></span>
15 <span class="locking_unlocked tooltip icon-ok" title="${_('Repository unlocked')}"></span>
16 %endif
16 %endif
17 %endif
17 %endif
18
18
19 ##FORK
19 ##FORK
20 %if c.db_repo.fork:
20 %if c.db_repo.fork:
21 <span>
21 <span>
22 - <i class="icon-fork"></i> ${_('Fork of')} "<a href="${h.url('summary_home',repo_name=c.db_repo.fork.repo_name)}">${c.db_repo.fork.repo_name}</a>"
22 - <i class="icon-fork"></i> ${_('Fork of')} "<a href="${h.url('summary_home',repo_name=c.db_repo.fork.repo_name)}">${c.db_repo.fork.repo_name}</a>"
23 </span>
23 </span>
24 %endif
24 %endif
25
25
26 ##REMOTE
26 ##REMOTE
27 %if c.db_repo.clone_uri:
27 %if c.db_repo.clone_uri:
28 <span>
28 <span>
29 - <i class="icon-fork"></i> ${_('Clone from')} "<a href="${h.url(str(h.hide_credentials(c.db_repo.clone_uri)))}">${h.hide_credentials(c.db_repo.clone_uri)}</a>"
29 - <i class="icon-fork"></i> ${_('Clone from')} "<a href="${h.url(str(h.hide_credentials(c.db_repo.clone_uri)))}">${h.hide_credentials(c.db_repo.clone_uri)}</a>"
30 <span>
30 <span>
31 %endif
31 %endif
32 </%def>
32 </%def>
33
33
34 <%block name="header_menu">
34 <%block name="header_menu">
35 ${self.menu('repositories')}
35 ${self.menu('repositories')}
36 </%block>
36 </%block>
37
37
38 <%block name="head_extra">
38 <%block name="head_extra">
39 <link href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
39 <link href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s ATOM feed') % c.repo_name}" type="application/atom+xml" />
40 <link href="${h.url('rss_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
40 <link href="${h.url('rss_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}" rel="alternate" title="${_('%s RSS feed') % c.repo_name}" type="application/rss+xml" />
41
41
42 <script>
42 <script>
43 redirect_hash_branch = function(){
43 redirect_hash_branch = function(){
44 var branch = window.location.hash.replace(/^#(.*)/, '$1');
44 var branch = window.location.hash.replace(/^#(.*)/, '$1');
45 if (branch){
45 if (branch){
46 window.location = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}"
46 window.location = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}"
47 .replace('__BRANCH__',branch);
47 .replace('__BRANCH__',branch);
48 }
48 }
49 }
49 }
50 redirect_hash_branch();
50 redirect_hash_branch();
51 window.onhashchange = function() {
51 window.onhashchange = function() {
52 redirect_hash_branch();
52 redirect_hash_branch();
53 };
53 };
54 </script>
54 </script>
55 </%block>
55 </%block>
56
56
57 <%def name="main()">
57 <%def name="main()">
58 ${self.repo_context_bar('summary')}
58 ${self.repo_context_bar('summary')}
59 <%
59 <%
60 summary = lambda n:{False:'summary-short'}.get(n)
60 summary = lambda n:{False:'summary-short'}.get(n)
61 %>
61 %>
62 <div class="box">
62 <div class="box">
63 <!-- box / title -->
63 <!-- box / title -->
64 <div class="title">
64 <div class="title">
65 ${self.breadcrumbs()}
65 ${self.breadcrumbs()}
66 </div>
66 </div>
67 <!-- end box / title -->
67 <!-- end box / title -->
68 <div class="form">
68 <div class="form">
69 <div id="summary" class="fields">
69 <div id="summary" class="fields">
70 <div class="field">
70 <div class="field">
71 <div class="label-summary">
71 <div class="label-summary">
72 <label>${_('Clone URL')}:</label>
72 <label>${_('Clone URL')}:</label>
73 </div>
73 </div>
74 <div class="input ${summary(c.show_stats)}">
74 <div class="input ${summary(c.show_stats)}">
75 ${self.repotag(c.db_repo)}
75 ${self.repotag(c.db_repo)}
76 <input style="width:80%" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
76 <input style="width:80%" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
77 <input style="display:none;width:80%" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
77 <input style="display:none;width:80%" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
78 <div style="display:none" id="clone_by_name" class="btn btn-small">${_('Show by Name')}</div>
78 <div style="display:none" id="clone_by_name" class="btn btn-small">${_('Show by Name')}</div>
79 <div id="clone_by_id" class="btn btn-small">${_('Show by ID')}</div>
79 <div id="clone_by_id" class="btn btn-small">${_('Show by ID')}</div>
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label-summary">
84 <div class="label-summary">
85 <label>${_('Description')}:</label>
85 <label>${_('Description')}:</label>
86 </div>
86 </div>
87 %if c.visual.stylify_metatags:
87 %if c.visual.stylify_metatags:
88 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.db_repo.description))}</div>
88 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.db_repo.description))}</div>
89 %else:
89 %else:
90 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.db_repo.description)}</div>
90 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.db_repo.description)}</div>
91 %endif
91 %endif
92 </div>
92 </div>
93
93
94 <div class="field">
94 <div class="field">
95 <div class="label-summary">
95 <div class="label-summary">
96 <label>${_('Trending files')}:</label>
96 <label>${_('Trending files')}:</label>
97 </div>
97 </div>
98 <div class="input ${summary(c.show_stats)}">
98 <div class="input ${summary(c.show_stats)}">
99 %if c.show_stats:
99 %if c.show_stats:
100 <div id="lang_stats"></div>
100 <div id="lang_stats"></div>
101 %else:
101 %else:
102 ${_('Statistics are disabled for this repository')}
102 ${_('Statistics are disabled for this repository')}
103 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
103 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
104 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'),class_="btn btn-mini")}
104 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'),class_="btn btn-mini")}
105 %endif
105 %endif
106 %endif
106 %endif
107 </div>
107 </div>
108 </div>
108 </div>
109
109
110 <div class="field">
110 <div class="field">
111 <div class="label-summary">
111 <div class="label-summary">
112 <label>${_('Download')}:</label>
112 <label>${_('Download')}:</label>
113 </div>
113 </div>
114 <div class="input ${summary(c.show_stats)}">
114 <div class="input ${summary(c.show_stats)}">
115 %if len(c.db_repo_scm_instance.revisions) == 0:
115 %if len(c.db_repo_scm_instance.revisions) == 0:
116 ${_('There are no downloads yet')}
116 ${_('There are no downloads yet')}
117 %elif not c.enable_downloads:
117 %elif not c.enable_downloads:
118 ${_('Downloads are disabled for this repository')}
118 ${_('Downloads are disabled for this repository')}
119 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
119 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
120 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'),class_="btn btn-mini")}
120 ${h.link_to(_('Enable'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'),class_="btn btn-mini")}
121 %endif
121 %endif
122 %else:
122 %else:
123 <span id="${'zip_link'}">
123 <span id="${'zip_link'}">
124 <a class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.db_repo.repo_name,fname='tip.zip')}"><i class="icon-file-zip"></i> ${_('Download as zip')}</a>
124 <a class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.db_repo.repo_name,fname='tip.zip')}"><i class="icon-file-zip"></i> ${_('Download as zip')}</a>
125 </span>
125 </span>
126 ${h.hidden('download_options')}
126 ${h.hidden('download_options')}
127 <span style="vertical-align: bottom">
127 <span style="vertical-align: bottom">
128 <input id="archive_subrepos" type="checkbox" name="subrepos" />
128 <input id="archive_subrepos" type="checkbox" name="subrepos" />
129 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
129 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
130 </span>
130 </span>
131 %endif
131 %endif
132 </div>
132 </div>
133 </div>
133 </div>
134 </div>
134 </div>
135 <div id="summary-menu-stats">
135 <div id="summary-menu-stats">
136 <ul>
136 <ul>
137 <li>
137 <li>
138 <a title="${_('Owner')} ${c.db_repo.user.email}">
138 <a title="${_('Owner')} ${c.db_repo.user.email}">
139 <i class="icon-user"></i> ${c.db_repo.user.username}
139 <i class="icon-user"></i> ${c.db_repo.user.username}
140 <div class="gravatar" style="float: right; margin: 0px 0px 0px 0px" title="${c.db_repo.user.name} ${c.db_repo.user.lastname}">
140 <div class="gravatar" style="float: right; margin: 0px 0px 0px 0px" title="${c.db_repo.user.name} ${c.db_repo.user.lastname}">
141 ${h.gravatar(c.db_repo.user.email, size=18)}
141 ${h.gravatar(c.db_repo.user.email, size=18)}
142 </div>
142 </div>
143 </a>
143 </a>
144 </li>
144 </li>
145 <li>
145 <li>
146 <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
146 <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
147 <i class="icon-heart"></i> ${_('Followers')}
147 <i class="icon-heart"></i> ${_('Followers')}
148 <span class="stats-bullet" id="current_followers_count">${c.repository_followers}</span>
148 <span class="stats-bullet" id="current_followers_count">${c.repository_followers}</span>
149 </a>
149 </a>
150 </li>
150 </li>
151 <li>
151 <li>
152 <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
152 <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
153 <i class="icon-fork"></i> ${_('Forks')}
153 <i class="icon-fork"></i> ${_('Forks')}
154 <span class="stats-bullet">${c.repository_forks}</span>
154 <span class="stats-bullet">${c.repository_forks}</span>
155 </a>
155 </a>
156 </li>
156 </li>
157
157
158 %if c.authuser.username != 'default':
158 %if c.authuser.username != 'default':
159 <li class="repo_size">
159 <li class="repo_size">
160 <a href="#" onclick="javascript:showRepoSize('repo_size_2','${c.db_repo.repo_name}','${str(h.get_token())}')"><i class="icon-ruler"></i> ${_('Repository Size')}</a>
160 <a href="#" onclick="javascript:showRepoSize('repo_size_2','${c.db_repo.repo_name}')"><i class="icon-ruler"></i> ${_('Repository Size')}</a>
161 <span class="stats-bullet" id="repo_size_2"></span>
161 <span class="stats-bullet" id="repo_size_2"></span>
162 </li>
162 </li>
163 %endif
163 %endif
164
164
165 <li>
165 <li>
166 %if c.authuser.username != 'default':
166 %if c.authuser.username != 'default':
167 <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i> ${_('Feed')}</a>
167 <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name,api_key=c.authuser.api_key)}"><i class="icon-rss-squared"></i> ${_('Feed')}</a>
168 %else:
168 %else:
169 <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name)}"><i class="icon-rss-squared"></i> ${_('Feed')}</a>
169 <a href="${h.url('atom_feed_home',repo_name=c.db_repo.repo_name)}"><i class="icon-rss-squared"></i> ${_('Feed')}</a>
170 %endif
170 %endif
171 </li>
171 </li>
172
172
173 %if c.show_stats:
173 %if c.show_stats:
174 <li>
174 <li>
175 <a title="${_('Statistics')}" href="${h.url('repo_stats_home',repo_name=c.repo_name)}">
175 <a title="${_('Statistics')}" href="${h.url('repo_stats_home',repo_name=c.repo_name)}">
176 <i class="icon-graph"></i> ${_('Statistics')}
176 <i class="icon-graph"></i> ${_('Statistics')}
177 </a>
177 </a>
178 </li>
178 </li>
179 %endif
179 %endif
180 </ul>
180 </ul>
181 </div>
181 </div>
182 </div>
182 </div>
183 </div>
183 </div>
184
184
185
185
186 <div class="box">
186 <div class="box">
187 <div class="title">
187 <div class="title">
188 <div class="breadcrumbs">
188 <div class="breadcrumbs">
189 %if c.repo_changesets:
189 %if c.repo_changesets:
190 ${h.link_to(_('Latest Changes'),h.url('changelog_home',repo_name=c.repo_name))}
190 ${h.link_to(_('Latest Changes'),h.url('changelog_home',repo_name=c.repo_name))}
191 %else:
191 %else:
192 ${_('Quick Start')}
192 ${_('Quick Start')}
193 %endif
193 %endif
194 </div>
194 </div>
195 </div>
195 </div>
196 <div class="table">
196 <div class="table">
197 <div id="shortlog_data">
197 <div id="shortlog_data">
198 <%include file='../changelog/changelog_summary_data.html'/>
198 <%include file='../changelog/changelog_summary_data.html'/>
199 </div>
199 </div>
200 </div>
200 </div>
201 </div>
201 </div>
202
202
203 %if c.readme_data:
203 %if c.readme_data:
204 <div id="readme" class="anchor">
204 <div id="readme" class="anchor">
205 <div class="box" style="background-color: #FAFAFA">
205 <div class="box" style="background-color: #FAFAFA">
206 <div class="title" title="${_('Readme file from revision %s:%s') % (c.db_repo.landing_rev[0], c.db_repo.landing_rev[1])}">
206 <div class="title" title="${_('Readme file from revision %s:%s') % (c.db_repo.landing_rev[0], c.db_repo.landing_rev[1])}">
207 <div class="breadcrumbs">
207 <div class="breadcrumbs">
208 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
208 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
209 </div>
209 </div>
210 </div>
210 </div>
211 <div class="readme">
211 <div class="readme">
212 <div class="readme_box">
212 <div class="readme_box">
213 ${c.readme_data|n}
213 ${c.readme_data|n}
214 </div>
214 </div>
215 </div>
215 </div>
216 </div>
216 </div>
217 </div>
217 </div>
218 %endif
218 %endif
219
219
220 <script type="text/javascript">
220 <script type="text/javascript">
221 $(document).ready(function(){
221 $(document).ready(function(){
222 var $clone_url = $('#clone_url');
222 var $clone_url = $('#clone_url');
223 var $clone_url_id = $('#clone_url_id');
223 var $clone_url_id = $('#clone_url_id');
224 var $clone_by_name = $('#clone_by_name');
224 var $clone_by_name = $('#clone_by_name');
225 var $clone_by_id = $('#clone_by_id');
225 var $clone_by_id = $('#clone_by_id');
226 $clone_url.click(function(e){
226 $clone_url.click(function(e){
227 if($clone_url.hasClass('selected')){
227 if($clone_url.hasClass('selected')){
228 return ;
228 return ;
229 }else{
229 }else{
230 $clone_url.addClass('selected');
230 $clone_url.addClass('selected');
231 $clone_url.select();
231 $clone_url.select();
232 }
232 }
233 });
233 });
234
234
235 $clone_by_name.click(function(e){
235 $clone_by_name.click(function(e){
236 // show url by name and hide name button
236 // show url by name and hide name button
237 $clone_url.show();
237 $clone_url.show();
238 $clone_by_name.hide();
238 $clone_by_name.hide();
239
239
240 // hide url by id and show name button
240 // hide url by id and show name button
241 $clone_by_id.show();
241 $clone_by_id.show();
242 $clone_url_id.hide();
242 $clone_url_id.hide();
243 });
243 });
244
244
245 $clone_by_id.click(function(e){
245 $clone_by_id.click(function(e){
246 // show url by id and hide id button
246 // show url by id and hide id button
247 $clone_by_id.hide();
247 $clone_by_id.hide();
248 $clone_url_id.show();
248 $clone_url_id.show();
249
249
250 // hide url by name and show id button
250 // hide url by name and show id button
251 $clone_by_name.show();
251 $clone_by_name.show();
252 $clone_url.hide();
252 $clone_url.hide();
253 });
253 });
254
254
255 var cache = {}
255 var cache = {}
256 $("#download_options").select2({
256 $("#download_options").select2({
257 placeholder: _TM['Select changeset'],
257 placeholder: _TM['Select changeset'],
258 dropdownAutoWidth: true,
258 dropdownAutoWidth: true,
259 query: function(query){
259 query: function(query){
260 var key = 'cache';
260 var key = 'cache';
261 var cached = cache[key] ;
261 var cached = cache[key] ;
262 if(cached) {
262 if(cached) {
263 var data = {results: []};
263 var data = {results: []};
264 //filter results
264 //filter results
265 $.each(cached.results, function(){
265 $.each(cached.results, function(){
266 var section = this.text;
266 var section = this.text;
267 var children = [];
267 var children = [];
268 $.each(this.children, function(){
268 $.each(this.children, function(){
269 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
269 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
270 children.push({'id': this.id, 'text': this.text})
270 children.push({'id': this.id, 'text': this.text})
271 }
271 }
272 })
272 })
273 data.results.push({'text': section, 'children': children})
273 data.results.push({'text': section, 'children': children})
274 });
274 });
275 query.callback(data);
275 query.callback(data);
276 }else{
276 }else{
277 $.ajax({
277 $.ajax({
278 url: pyroutes.url('repo_refs_data', {'repo_name': '${c.repo_name}'}),
278 url: pyroutes.url('repo_refs_data', {'repo_name': '${c.repo_name}'}),
279 data: {},
279 data: {},
280 dataType: 'json',
280 dataType: 'json',
281 type: 'GET',
281 type: 'GET',
282 success: function(data) {
282 success: function(data) {
283 cache[key] = data;
283 cache[key] = data;
284 query.callback({results: data.results});
284 query.callback({results: data.results});
285 }
285 }
286 })
286 })
287 }
287 }
288 }
288 }
289 });
289 });
290 // on change of download options
290 // on change of download options
291 $('#download_options').change(function(e){
291 $('#download_options').change(function(e){
292 var new_cs = e.added
292 var new_cs = e.added
293
293
294 for(k in tmpl_links){
294 for(k in tmpl_links){
295 var s = $('#'+k+'_link');
295 var s = $('#'+k+'_link');
296 if(s){
296 if(s){
297 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
297 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
298 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
298 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
299 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
299 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
300 title_tmpl = '<i class="icon-file-zip"></i> '+ title_tmpl;
300 title_tmpl = '<i class="icon-file-zip"></i> '+ title_tmpl;
301 var url = tmpl_links[k].replace('__CS__',new_cs.id);
301 var url = tmpl_links[k].replace('__CS__',new_cs.id);
302 var subrepos = $('#archive_subrepos').is(':checked');
302 var subrepos = $('#archive_subrepos').is(':checked');
303 url = url.replace('__SUB__',subrepos);
303 url = url.replace('__SUB__',subrepos);
304 url = url.replace('__NAME__',title_tmpl);
304 url = url.replace('__NAME__',title_tmpl);
305
305
306 s.html(url)
306 s.html(url)
307 }
307 }
308 }
308 }
309 });
309 });
310
310
311 var tmpl_links = {};
311 var tmpl_links = {};
312 %for cnt,archive in enumerate(c.db_repo_scm_instance._get_archives()):
312 %for cnt,archive in enumerate(c.db_repo_scm_instance._get_archives()):
313 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-small')}';
313 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.db_repo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='btn btn-small')}';
314 %endfor
314 %endfor
315 })
315 })
316 </script>
316 </script>
317
317
318 %if c.show_stats:
318 %if c.show_stats:
319 <script type="text/javascript">
319 <script type="text/javascript">
320 $(document).ready(function(){
320 $(document).ready(function(){
321 var data = ${c.trending_languages|n};
321 var data = ${c.trending_languages|n};
322 var total = 0;
322 var total = 0;
323 var no_data = true;
323 var no_data = true;
324 var tbl = document.createElement('table');
324 var tbl = document.createElement('table');
325 tbl.setAttribute('class','trending_language_tbl');
325 tbl.setAttribute('class','trending_language_tbl');
326 var cnt = 0;
326 var cnt = 0;
327 for (var i=0;i<data.length;i++){
327 for (var i=0;i<data.length;i++){
328 total+= data[i][1].count;
328 total+= data[i][1].count;
329 }
329 }
330 for (var i=0;i<data.length;i++){
330 for (var i=0;i<data.length;i++){
331 cnt += 1;
331 cnt += 1;
332 no_data = false;
332 no_data = false;
333
333
334 var hide = cnt>2;
334 var hide = cnt>2;
335 var tr = document.createElement('tr');
335 var tr = document.createElement('tr');
336 if (hide){
336 if (hide){
337 tr.setAttribute('style','display:none');
337 tr.setAttribute('style','display:none');
338 tr.setAttribute('class','stats_hidden');
338 tr.setAttribute('class','stats_hidden');
339 }
339 }
340 var k = data[i][0];
340 var k = data[i][0];
341 var obj = data[i][1];
341 var obj = data[i][1];
342 var percentage = Math.round((obj.count/total*100),2);
342 var percentage = Math.round((obj.count/total*100),2);
343
343
344 var td1 = document.createElement('td');
344 var td1 = document.createElement('td');
345 td1.width = 150;
345 td1.width = 150;
346 var trending_language_label = document.createElement('div');
346 var trending_language_label = document.createElement('div');
347 trending_language_label.innerHTML = obj.desc+" ("+k+")";
347 trending_language_label.innerHTML = obj.desc+" ("+k+")";
348 td1.appendChild(trending_language_label);
348 td1.appendChild(trending_language_label);
349
349
350 var td2 = document.createElement('td');
350 var td2 = document.createElement('td');
351 td2.setAttribute('style','padding-right:14px !important');
351 td2.setAttribute('style','padding-right:14px !important');
352 var trending_language = document.createElement('div');
352 var trending_language = document.createElement('div');
353 var nr_files = obj.count+" ${_('files')}";
353 var nr_files = obj.count+" ${_('files')}";
354
354
355 trending_language.title = k+" "+nr_files;
355 trending_language.title = k+" "+nr_files;
356
356
357 if (percentage>22){
357 if (percentage>22){
358 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
358 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
359 }
359 }
360 else{
360 else{
361 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
361 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
362 }
362 }
363
363
364 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
364 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
365 trending_language.style.width=percentage+"%";
365 trending_language.style.width=percentage+"%";
366 td2.appendChild(trending_language);
366 td2.appendChild(trending_language);
367
367
368 tr.appendChild(td1);
368 tr.appendChild(td1);
369 tr.appendChild(td2);
369 tr.appendChild(td2);
370 tbl.appendChild(tr);
370 tbl.appendChild(tr);
371 if(cnt == 3){
371 if(cnt == 3){
372 var show_more = document.createElement('tr');
372 var show_more = document.createElement('tr');
373 var td = document.createElement('td');
373 var td = document.createElement('td');
374 lnk = document.createElement('a');
374 lnk = document.createElement('a');
375
375
376 lnk.href='#';
376 lnk.href='#';
377 lnk.innerHTML = "${_('Show more')}";
377 lnk.innerHTML = "${_('Show more')}";
378 lnk.id='code_stats_show_more';
378 lnk.id='code_stats_show_more';
379 td.appendChild(lnk);
379 td.appendChild(lnk);
380
380
381 show_more.appendChild(td);
381 show_more.appendChild(td);
382 show_more.appendChild(document.createElement('td'));
382 show_more.appendChild(document.createElement('td'));
383 tbl.appendChild(show_more);
383 tbl.appendChild(show_more);
384 }
384 }
385
385
386 }
386 }
387 if (data.length == 0) {
387 if (data.length == 0) {
388 tbl.innerHTML = "<tr><td>${_('No data ready yet')}</td></tr>";
388 tbl.innerHTML = "<tr><td>${_('No data ready yet')}</td></tr>";
389 }
389 }
390
390
391 $('#lang_stats').append(tbl);
391 $('#lang_stats').append(tbl);
392 $('#code_stats_show_more').click(function(){
392 $('#code_stats_show_more').click(function(){
393 $('.stats_hidden').show();
393 $('.stats_hidden').show();
394 $('#code_stats_show_more').hide();
394 $('#code_stats_show_more').hide();
395 });
395 });
396 });
396 });
397 </script>
397 </script>
398 %endif
398 %endif
399
399
400 </%def>
400 </%def>
@@ -1,39 +1,38 b''
1 from kallithea.tests import *
1 from kallithea.tests import *
2 import datetime
2 import datetime
3
3
4
4
5 class TestJournalController(TestController):
5 class TestJournalController(TestController):
6
6
7 def test_index(self):
7 def test_index(self):
8 self.log_user()
8 self.log_user()
9 response = self.app.get(url(controller='journal', action='index'))
9 response = self.app.get(url(controller='journal', action='index'))
10
10
11 response.mustcontain("""<div class="journal_day">%s</div>""" % datetime.date.today())
11 response.mustcontain("""<div class="journal_day">%s</div>""" % datetime.date.today())
12
12
13 def test_stop_following_repository(self):
13 def test_stop_following_repository(self):
14 session = self.log_user()
14 session = self.log_user()
15 # usr = Session().query(User).filter(User.username == 'test_admin').one()
15 # usr = Session().query(User).filter(User.username == 'test_admin').one()
16 # repo = Session().query(Repository).filter(Repository.repo_name == HG_REPO).one()
16 # repo = Session().query(Repository).filter(Repository.repo_name == HG_REPO).one()
17 #
17 #
18 # followings = Session().query(UserFollowing)\
18 # followings = Session().query(UserFollowing)\
19 # .filter(UserFollowing.user == usr)\
19 # .filter(UserFollowing.user == usr)\
20 # .filter(UserFollowing.follows_repository == repo).all()
20 # .filter(UserFollowing.follows_repository == repo).all()
21 #
21 #
22 # assert len(followings) == 1, 'Not following any repository'
22 # assert len(followings) == 1, 'Not following any repository'
23 #
23 #
24 # response = self.app.post(url(controller='journal',
24 # response = self.app.post(url(controller='journal',
25 # action='toggle_following'),
25 # action='toggle_following'),
26 # {'auth_token':get_token(session),
26 # {'follows_repo_id':repo.repo_id})
27 # 'follows_repo_id':repo.repo_id})
28
27
29 def test_start_following_repository(self):
28 def test_start_following_repository(self):
30 self.log_user()
29 self.log_user()
31 response = self.app.get(url(controller='journal', action='index'),)
30 response = self.app.get(url(controller='journal', action='index'),)
32
31
33 def test_public_journal_atom(self):
32 def test_public_journal_atom(self):
34 self.log_user()
33 self.log_user()
35 response = self.app.get(url(controller='journal', action='public_journal_atom'),)
34 response = self.app.get(url(controller='journal', action='public_journal_atom'),)
36
35
37 def test_public_journal_rss(self):
36 def test_public_journal_rss(self):
38 self.log_user()
37 self.log_user()
39 response = self.app.get(url(controller='journal', action='public_journal_rss'),)
38 response = self.app.get(url(controller='journal', action='public_journal_rss'),)
General Comments 0
You need to be logged in to leave comments. Login now