##// END OF EJS Templates
templates: few small UI fixes
super-admin -
r5046:0c2a09c8 default
parent child Browse files
Show More
@@ -1,1262 +1,1261 b''
1 1
2 2 <%!
3 3 from rhodecode.lib import html_filters
4 4 %>
5 5
6 6 <%inherit file="root.mako"/>
7 7
8 8 <%include file="/ejs_templates/templates.html"/>
9 9
10 10 <div class="outerwrapper">
11 11 <!-- HEADER -->
12 12 <div class="header">
13 13 <div id="header-inner" class="wrapper">
14 14 <div id="logo">
15 15 <div class="logo-wrapper">
16 16 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
17 17 </div>
18 18 % if c.rhodecode_name:
19 19 <div class="branding">
20 20 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
21 21 </div>
22 22 % endif
23 23 </div>
24 24 <!-- MENU BAR NAV -->
25 25 ${self.menu_bar_nav()}
26 26 <!-- END MENU BAR NAV -->
27 27 </div>
28 28 </div>
29 29 ${self.menu_bar_subnav()}
30 30 <!-- END HEADER -->
31 31
32 32 <!-- CONTENT -->
33 33 <div id="content" class="wrapper">
34 34
35 35 <rhodecode-toast id="notifications"></rhodecode-toast>
36 36
37 37 <div class="main">
38 38 ${next.main()}
39 39 </div>
40 40
41 41 </div>
42 42 <!-- END CONTENT -->
43 43
44 44 </div>
45 45
46 46 <!-- FOOTER -->
47 47 <div id="footer">
48 48 <div id="footer-inner" class="title wrapper">
49 49 <div>
50 50 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
51 51
52 52 <p class="footer-link-right">
53 53 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
54 54 RhodeCode
55 55 % if c.visual.show_version:
56 56 ${c.rhodecode_version}
57 57 % endif
58 58 ${c.rhodecode_edition}
59 59 </a> |
60 60
61 61 % if c.visual.rhodecode_support_url:
62 62 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
63 63 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
64 64 % endif
65 65
66 66 </p>
67 67
68 68 <p class="server-instance" style="display:${sid}">
69 69 ## display hidden instance ID if specially defined
70 70 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
71 71 % if c.rhodecode_instanceid:
72 72 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
73 73 % endif
74 74 </p>
75 75 </div>
76 76 </div>
77 77 </div>
78 78
79 79 <!-- END FOOTER -->
80 80
81 81 ### MAKO DEFS ###
82 82
83 83 <%def name="menu_bar_subnav()">
84 84 </%def>
85 85
86 86 <%def name="breadcrumbs(class_='breadcrumbs')">
87 87 <div class="${class_}">
88 88 ${self.breadcrumbs_links()}
89 89 </div>
90 90 </%def>
91 91
92 92 <%def name="admin_menu(active=None)">
93 93
94 94 <div id="context-bar">
95 95 <div class="wrapper">
96 96 <div class="title">
97 97 <div class="title-content">
98 98 <div class="title-main">
99 99 % if c.is_super_admin:
100 100 ${_('Super-admin Panel')}
101 101 % else:
102 102 ${_('Delegated Admin Panel')}
103 103 % endif
104 104 </div>
105 105 </div>
106 106 </div>
107 107
108 108 <ul id="context-pages" class="navigation horizontal-list">
109 109
110 110 ## super-admin case
111 111 % if c.is_super_admin:
112 112 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
113 113 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
114 114 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
115 115 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
116 116 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
117 117 <li class="${h.is_active('artifacts', active)}"><a href="${h.route_path('admin_artifacts')}">${_('Artifacts')}</a></li>
118 118 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
119 119 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
120 120 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
121 121 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
122 122 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
123 123
124 124 ## delegated admin
125 125 % elif c.is_delegated_admin:
126 126 <%
127 127 repositories=c.auth_user.repositories_admin or c.can_create_repo
128 128 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
129 129 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
130 130 %>
131 131
132 132 %if repositories:
133 133 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
134 134 %endif
135 135 %if repository_groups:
136 136 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
137 137 %endif
138 138 %if user_groups:
139 139 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
140 140 %endif
141 141 % endif
142 142 </ul>
143 143
144 144 </div>
145 145 <div class="clear"></div>
146 146 </div>
147 147 </%def>
148 148
149 149 <%def name="dt_info_panel(elements)">
150 150 <dl class="dl-horizontal">
151 151 %for dt, dd, title, show_items in elements:
152 152 <dt>${dt}:</dt>
153 153 <dd title="${h.tooltip(title)}">
154 154 %if callable(dd):
155 155 ## allow lazy evaluation of elements
156 156 ${dd()}
157 157 %else:
158 158 ${dd}
159 159 %endif
160 160 %if show_items:
161 161 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
162 162 %endif
163 163 </dd>
164 164
165 165 %if show_items:
166 166 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
167 167 %for item in show_items:
168 168 <dt></dt>
169 169 <dd>${item}</dd>
170 170 %endfor
171 171 </div>
172 172 %endif
173 173
174 174 %endfor
175 175 </dl>
176 176 </%def>
177 177
178 178 <%def name="tr_info_entry(element)">
179 179 <% key, val, title, show_items = element %>
180 180
181 181 <tr>
182 182 <td style="vertical-align: top">${key}</td>
183 183 <td title="${h.tooltip(title)}">
184 184 %if callable(val):
185 185 ## allow lazy evaluation of elements
186 186 ${val()}
187 187 %else:
188 188 ${val}
189 189 %endif
190 190 %if show_items:
191 191 <div class="collapsable-content" data-toggle="item-${h.md5_safe(h.safe_str(val))[:6]}-details" style="display: none">
192 192 % for item in show_items:
193 193 <dt></dt>
194 194 <dd>${item}</dd>
195 195 % endfor
196 196 </div>
197 197 %endif
198 198 </td>
199 199 <td style="vertical-align: top">
200 200 %if show_items:
201 201 <span class="btn-collapse" data-toggle="item-${h.md5_safe(h.safe_str(val))[:6]}-details">${_('Show More')} </span>
202 202 %endif
203 203 </td>
204 204 </tr>
205 205
206 206 </%def>
207 207
208 208 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
209 209 <%
210 210 if size > 16:
211 211 gravatar_class = ['gravatar','gravatar-large']
212 212 else:
213 213 gravatar_class = ['gravatar']
214 214
215 215 data_hovercard_url = ''
216 216 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
217 217
218 218 if tooltip:
219 219 gravatar_class += ['tooltip-hovercard']
220 220 if extra_class:
221 221 gravatar_class += extra_class
222 222 if tooltip and user:
223 223 if user.username == h.DEFAULT_USER:
224 224 gravatar_class.pop(-1)
225 225 else:
226 226 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
227 227 gravatar_class = ' '.join(gravatar_class)
228 228
229 229 %>
230 230 <%doc>
231 231 TODO: johbo: For now we serve double size images to make it smooth
232 232 for retina. This is how it worked until now. Should be replaced
233 233 with a better solution at some point.
234 234 </%doc>
235 235
236 236 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2, request=request)}" />
237 237 </%def>
238 238
239 239
240 240 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
241 241 <%
242 242 email = h.email_or_none(contact)
243 243 rc_user = h.discover_user(contact)
244 244 %>
245 245
246 246 <div class="${_class}">
247 247 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
248 248 <span class="${('user user-disabled' if show_disabled else 'user')}">
249 249 ${h.link_to_user(rc_user or contact)}
250 250 </span>
251 251 </div>
252 252 </%def>
253 253
254 254
255 255 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
256 256 <%
257 257 if (size > 16):
258 258 gravatar_class = 'icon-user-group-alt'
259 259 else:
260 260 gravatar_class = 'icon-user-group-alt'
261 261
262 262 if tooltip:
263 263 gravatar_class += ' tooltip-hovercard'
264 264
265 265 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
266 266 %>
267 267 <%doc>
268 268 TODO: johbo: For now we serve double size images to make it smooth
269 269 for retina. This is how it worked until now. Should be replaced
270 270 with a better solution at some point.
271 271 </%doc>
272 272
273 273 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
274 274 </%def>
275 275
276 276 <%def name="repo_page_title(repo_instance)">
277 277 <div class="title-content repo-title">
278 278
279 279 <div class="title-main">
280 280 ## SVN/HG/GIT icons
281 281 %if h.is_hg(repo_instance):
282 282 <i class="icon-hg"></i>
283 283 %endif
284 284 %if h.is_git(repo_instance):
285 285 <i class="icon-git"></i>
286 286 %endif
287 287 %if h.is_svn(repo_instance):
288 288 <i class="icon-svn"></i>
289 289 %endif
290 290
291 291 ## public/private
292 292 %if repo_instance.private:
293 293 <i class="icon-repo-private"></i>
294 294 %else:
295 295 <i class="icon-repo-public"></i>
296 296 %endif
297 297
298 298 ## repo name with group name
299 299 ${h.breadcrumb_repo_link(repo_instance)}
300 300
301 301 ## Context Actions
302 302 <div class="pull-right">
303 303 %if c.rhodecode_user.username != h.DEFAULT_USER:
304 304 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
305 305
306 306 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
307 307 % if c.repository_is_user_following:
308 308 <i class="icon-eye-off"></i>${_('Unwatch')}
309 309 % else:
310 310 <i class="icon-eye"></i>${_('Watch')}
311 311 % endif
312 312
313 313 </a>
314 314 %else:
315 315 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
316 316 %endif
317 317 </div>
318 318
319 319 </div>
320 320
321 321 ## FORKED
322 322 %if repo_instance.fork:
323 323 <p class="discreet">
324 324 <i class="icon-code-fork"></i> ${_('Fork of')}
325 325 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
326 326 </p>
327 327 %endif
328 328
329 329 ## IMPORTED FROM REMOTE
330 330 %if repo_instance.clone_uri:
331 331 <p class="discreet">
332 332 <i class="icon-code-fork"></i> ${_('Clone from')}
333 333 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
334 334 </p>
335 335 %endif
336 336
337 337 ## LOCKING STATUS
338 338 %if repo_instance.locked[0]:
339 339 <p class="locking_locked discreet">
340 340 <i class="icon-repo-lock"></i>
341 341 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
342 342 </p>
343 343 %elif repo_instance.enable_locking:
344 344 <p class="locking_unlocked discreet">
345 <i class="icon-repo-unlock"></i>
346 345 ${_('Repository not locked. Pull repository to lock it.')}
347 346 </p>
348 347 %endif
349 348
350 349 </div>
351 350 </%def>
352 351
353 352 <%def name="repo_menu(active=None)">
354 353 <%
355 354 ## determine if we have "any" option available
356 355 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
357 356 has_actions = can_lock
358 357
359 358 %>
360 359 % if c.rhodecode_db_repo.archived:
361 360 <div class="alert alert-warning text-center">
362 361 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
363 362 </div>
364 363 % endif
365 364
366 365 <!--- REPO CONTEXT BAR -->
367 366 <div id="context-bar">
368 367 <div class="wrapper">
369 368
370 369 <div class="title">
371 370 ${self.repo_page_title(c.rhodecode_db_repo)}
372 371 </div>
373 372
374 373 <ul id="context-pages" class="navigation horizontal-list">
375 374 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
376 375 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
377 376 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
378 377 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
379 378
380 379 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
381 380 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
382 381 <li class="${h.is_active('showpullrequest', active)}">
383 382 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
384 383 <div class="menulabel">
385 384 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
386 385 </div>
387 386 </a>
388 387 </li>
389 388 %endif
390 389
391 390 <li class="${h.is_active('artifacts', active)}">
392 391 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
393 392 <div class="menulabel">
394 393 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
395 394 </div>
396 395 </a>
397 396 </li>
398 397
399 398 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
400 399 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
401 400 %endif
402 401
403 402 <li class="${h.is_active('options', active)}">
404 403 % if has_actions:
405 404 <a class="menulink dropdown">
406 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
405 <div class="menulabel">${_('Quick Actions')}<div class="show_more"></div></div>
407 406 </a>
408 407 <ul class="submenu">
409 408 %if can_lock:
410 409 %if c.rhodecode_db_repo.locked[0]:
411 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
410 <li><a class="locking_del" href="${h.route_path('repo_settings_quick_actions',repo_name=c.repo_name, _query={'action': 'toggle-lock', 'set_unlock': 1})}">${_('Unlock Repository')}</a></li>
412 411 %else:
413 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
412 <li><a class="locking_add" href="${h.route_path('repo_settings_quick_actions',repo_name=c.repo_name, _query={'action': 'toggle-lock', 'set_lock': 1})}">${_('Lock Repository')}</a></li>
414 413 %endif
415 414 %endif
416 415 </ul>
417 416 % endif
418 417 </li>
419 418
420 419 </ul>
421 420 </div>
422 421 <div class="clear"></div>
423 422 </div>
424 423
425 424 <!--- REPO END CONTEXT BAR -->
426 425
427 426 </%def>
428 427
429 428 <%def name="repo_group_page_title(repo_group_instance)">
430 429 <div class="title-content">
431 430 <div class="title-main">
432 431 ## Repository Group icon
433 432 <i class="icon-repo-group"></i>
434 433
435 434 ## repo name with group name
436 435 ${h.breadcrumb_repo_group_link(repo_group_instance)}
437 436 </div>
438 437
439 438 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
440 439 <div class="repo-group-desc discreet">
441 440 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
442 441 </div>
443 442
444 443 </div>
445 444 </%def>
446 445
447 446
448 447 <%def name="repo_group_menu(active=None)">
449 448 <%
450 449 gr_name = c.repo_group.group_name if c.repo_group else None
451 450 # create repositories with write permission on group is set to true
452 451 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
453 452
454 453 %>
455 454
456 455
457 456 <!--- REPO GROUP CONTEXT BAR -->
458 457 <div id="context-bar">
459 458 <div class="wrapper">
460 459 <div class="title">
461 460 ${self.repo_group_page_title(c.repo_group)}
462 461 </div>
463 462
464 463 <ul id="context-pages" class="navigation horizontal-list">
465 464 <li class="${h.is_active('home', active)}">
466 465 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
467 466 </li>
468 467 % if c.is_super_admin or group_admin:
469 468 <li class="${h.is_active('settings', active)}">
470 469 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
471 470 </li>
472 471 % endif
473 472
474 473 </ul>
475 474 </div>
476 475 <div class="clear"></div>
477 476 </div>
478 477
479 478 <!--- REPO GROUP CONTEXT BAR -->
480 479
481 480 </%def>
482 481
483 482
484 483 <%def name="usermenu(active=False)">
485 484 <%
486 485 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
487 486
488 487 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
489 488 # create repositories with write permission on group is set to true
490 489
491 490 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
492 491 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
493 492 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
494 493 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
495 494
496 495 can_create_repos = c.is_super_admin or c.can_create_repo
497 496 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
498 497
499 498 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
500 499 can_create_repo_groups_in_group = c.is_super_admin or group_admin
501 500 %>
502 501
503 502 % if not_anonymous:
504 503 <%
505 504 default_target_group = dict()
506 505 if c.rhodecode_user.personal_repo_group:
507 506 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
508 507 %>
509 508
510 509 ## create action
511 510 <li>
512 511 <a href="#create-actions" onclick="return false;" class="menulink childs">
513 512 <i class="icon-plus-circled"></i>
514 513 </a>
515 514
516 515 <div class="action-menu submenu">
517 516
518 517 <ol>
519 518 ## scope of within a repository
520 519 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
521 520 <li class="submenu-title">${_('This Repository')}</li>
522 521 <li>
523 522 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
524 523 </li>
525 524 % if can_fork:
526 525 <li>
527 526 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
528 527 </li>
529 528 % endif
530 529 % endif
531 530
532 531 ## scope of within repository groups
533 532 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
534 533 <li class="submenu-title">${_('This Repository Group')}</li>
535 534
536 535 % if can_create_repos_in_group:
537 536 <li>
538 537 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
539 538 </li>
540 539 % endif
541 540
542 541 % if can_create_repo_groups_in_group:
543 542 <li>
544 543 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
545 544 </li>
546 545 % endif
547 546 % endif
548 547
549 548 ## personal group
550 549 % if c.rhodecode_user.personal_repo_group:
551 550 <li class="submenu-title">Personal Group</li>
552 551
553 552 <li>
554 553 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
555 554 </li>
556 555
557 556 <li>
558 557 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
559 558 </li>
560 559 % endif
561 560
562 561 ## Global actions
563 562 <li class="submenu-title">RhodeCode</li>
564 563 % if can_create_repos:
565 564 <li>
566 565 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
567 566 </li>
568 567 % endif
569 568
570 569 % if can_create_repo_groups:
571 570 <li>
572 571 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
573 572 </li>
574 573 % endif
575 574
576 575 <li>
577 576 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
578 577 </li>
579 578
580 579 </ol>
581 580
582 581 </div>
583 582 </li>
584 583
585 584 ## notifications
586 585 <li>
587 586 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
588 587 ${c.unread_notifications}
589 588 </a>
590 589 </li>
591 590 % endif
592 591
593 592 ## USER MENU
594 593 <li id="quick_login_li" class="${'active' if active else ''}">
595 594 % if c.rhodecode_user.username == h.DEFAULT_USER:
596 595 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
597 596 ${gravatar(c.rhodecode_user.email, 20)}
598 597 <span class="user">
599 598 <span>${_('Sign in')}</span>
600 599 </span>
601 600 </a>
602 601 % else:
603 602 ## logged in user
604 603 <a id="quick_login_link" class="menulink childs">
605 604 ${gravatar(c.rhodecode_user.email, 20)}
606 605 <span class="user">
607 606 <span class="menu_link_user">${c.rhodecode_user.username}</span>
608 607 <div class="show_more"></div>
609 608 </span>
610 609 </a>
611 610 ## subnav with menu for logged in user
612 611 <div class="user-menu submenu">
613 612 <div id="quick_login">
614 613 %if c.rhodecode_user.username != h.DEFAULT_USER:
615 614 <div class="">
616 615 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
617 616 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
618 617 <div class="email">${c.rhodecode_user.email}</div>
619 618 </div>
620 619 <div class="">
621 620 <ol class="links">
622 621 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
623 622 % if c.rhodecode_user.personal_repo_group:
624 623 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
625 624 % endif
626 625 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
627 626
628 627 % if c.debug_style:
629 628 <li>
630 629 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
631 630 <div class="menulabel">${_('[Style]')}</div>
632 631 </a>
633 632 </li>
634 633 % endif
635 634
636 635 ## bookmark-items
637 636 <li class="bookmark-items">
638 637 ${_('Bookmarks')}
639 638 <div class="pull-right">
640 639 <a href="${h.route_path('my_account_bookmarks')}">
641 640
642 641 <i class="icon-cog"></i>
643 642 </a>
644 643 </div>
645 644 </li>
646 645 % if not c.bookmark_items:
647 646 <li>
648 647 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
649 648 </li>
650 649 % endif
651 650 % for item in c.bookmark_items:
652 651 <li>
653 652 % if item.repository:
654 653 <div>
655 654 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
656 655 <code>${item.position}</code>
657 656 % if item.repository.repo_type == 'hg':
658 657 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
659 658 % elif item.repository.repo_type == 'git':
660 659 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
661 660 % elif item.repository.repo_type == 'svn':
662 661 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
663 662 % endif
664 663 ${(item.title or h.shorter(item.repository.repo_name, 30))}
665 664 </a>
666 665 </div>
667 666 % elif item.repository_group:
668 667 <div>
669 668 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
670 669 <code>${item.position}</code>
671 670 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
672 671 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
673 672 </a>
674 673 </div>
675 674 % else:
676 675 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
677 676 <code>${item.position}</code>
678 677 ${item.title}
679 678 </a>
680 679 % endif
681 680 </li>
682 681 % endfor
683 682
684 683 <li class="logout">
685 684 ${h.secure_form(h.route_path('logout'), request=request)}
686 685 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
687 686 ${h.end_form()}
688 687 </li>
689 688 </ol>
690 689 </div>
691 690 %endif
692 691 </div>
693 692 </div>
694 693
695 694 % endif
696 695 </li>
697 696 </%def>
698 697
699 698 <%def name="menu_items(active=None)">
700 699 <%
701 700 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
702 701 notice_display = 'none' if len(notice_messages) == 0 else ''
703 702 %>
704 703
705 704 <ul id="quick" class="main_nav navigation horizontal-list">
706 705 ## notice box for important system messages
707 706 <li style="display: ${notice_display}">
708 707 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
709 708 <div class="menulabel-notice ${notice_level}" >
710 709 ${len(notice_messages)}
711 710 </div>
712 711 </a>
713 712 </li>
714 713 <div class="notice-messages-container" style="display: none">
715 714 <div class="notice-messages">
716 715 <table class="rctable">
717 716 % for notice in notice_messages:
718 717 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
719 718 <td style="vertical-align: text-top; width: 20px">
720 719 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
721 720 </td>
722 721 <td>
723 722 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
724 723 ${notice['subject']}
725 724
726 725 <div id="notice-${notice['msg_id']}" style="display: none">
727 726 ${h.render(notice['body'], renderer='markdown')}
728 727 </div>
729 728 </td>
730 729 <td style="vertical-align: text-top; width: 35px;">
731 730 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
732 731 <i class="icon-remove icon-filled-red"></i>
733 732 </a>
734 733 </td>
735 734 </tr>
736 735
737 736 % endfor
738 737 </table>
739 738 </div>
740 739 </div>
741 740 ## Main filter
742 741 <li>
743 742 <div class="menulabel main_filter_box">
744 743 <div class="main_filter_input_box">
745 744 <ul class="searchItems">
746 745
747 746 <li class="searchTag searchTagIcon">
748 747 <i class="icon-search"></i>
749 748 </li>
750 749
751 750 % if c.template_context['search_context']['repo_id']:
752 751 <li class="searchTag searchTagFilter searchTagHidable" >
753 752 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
754 753 <span class="tag">
755 754 This repo
756 755 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
757 756 </span>
758 757 ##</a>
759 758 </li>
760 759 % elif c.template_context['search_context']['repo_group_id']:
761 760 <li class="searchTag searchTagFilter searchTagHidable">
762 761 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
763 762 <span class="tag">
764 763 This group
765 764 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
766 765 </span>
767 766 ##</a>
768 767 </li>
769 768 % endif
770 769
771 770 <li class="searchTagInput">
772 771 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
773 772 </li>
774 773 <li class="searchTag searchTagHelp">
775 774 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
776 775 </li>
777 776 </ul>
778 777 </div>
779 778 </div>
780 779
781 780 <div id="main_filter_help" style="display: none">
782 781 - Use '/' key to quickly access this field.
783 782
784 783 - Enter a name of repository, or repository group for quick search.
785 784
786 785 - Prefix query to allow special search:
787 786
788 787 <strong>user:</strong>admin, to search for usernames, always global
789 788
790 789 <strong>user_group:</strong>devops, to search for user groups, always global
791 790
792 791 <strong>pr:</strong>303, to search for pull request number, title, or description, always global
793 792
794 793 <strong>commit:</strong>efced4, to search for commits, scoped to repositories or groups
795 794
796 795 <strong>file:</strong>models.py, to search for file paths, scoped to repositories or groups
797 796
798 797 % if c.template_context['search_context']['repo_id']:
799 798 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
800 799 % elif c.template_context['search_context']['repo_group_id']:
801 800 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
802 801 % else:
803 802 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
804 803 % endif
805 804 </div>
806 805 </li>
807 806
808 807 ## ROOT MENU
809 808 <li class="${h.is_active('home', active)}">
810 809 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
811 810 <div class="menulabel">${_('Home')}</div>
812 811 </a>
813 812 </li>
814 813
815 814 %if c.rhodecode_user.username != h.DEFAULT_USER:
816 815 <li class="${h.is_active('journal', active)}">
817 816 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
818 817 <div class="menulabel">${_('Journal')}</div>
819 818 </a>
820 819 </li>
821 820 %else:
822 821 <li class="${h.is_active('journal', active)}">
823 822 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
824 823 <div class="menulabel">${_('Public journal')}</div>
825 824 </a>
826 825 </li>
827 826 %endif
828 827
829 828 <li class="${h.is_active('gists', active)}">
830 829 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
831 830 <div class="menulabel">${_('Gists')}</div>
832 831 </a>
833 832 </li>
834 833
835 834 % if c.is_super_admin or c.is_delegated_admin:
836 835 <li class="${h.is_active('admin', active)}">
837 836 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
838 837 <div class="menulabel">${_('Admin')} </div>
839 838 </a>
840 839 </li>
841 840 % endif
842 841
843 842 ## render extra user menu
844 843 ${usermenu(active=(active=='my_account'))}
845 844
846 845 </ul>
847 846
848 847 <script type="text/javascript">
849 848 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
850 849
851 850 var formatRepoResult = function(result, container, query, escapeMarkup) {
852 851 return function(data, escapeMarkup) {
853 852 if (!data.repo_id){
854 853 return data.text; // optgroup text Repositories
855 854 }
856 855
857 856 var tmpl = '';
858 857 var repoType = data['repo_type'];
859 858 var repoName = data['text'];
860 859
861 860 if(data && data.type == 'repo'){
862 861 if(repoType === 'hg'){
863 862 tmpl += '<i class="icon-hg"></i> ';
864 863 }
865 864 else if(repoType === 'git'){
866 865 tmpl += '<i class="icon-git"></i> ';
867 866 }
868 867 else if(repoType === 'svn'){
869 868 tmpl += '<i class="icon-svn"></i> ';
870 869 }
871 870 if(data['private']){
872 871 tmpl += '<i class="icon-lock" ></i> ';
873 872 }
874 873 else if(visualShowPublicIcon){
875 874 tmpl += '<i class="icon-unlock-alt"></i> ';
876 875 }
877 876 }
878 877 tmpl += escapeMarkup(repoName);
879 878 return tmpl;
880 879
881 880 }(result, escapeMarkup);
882 881 };
883 882
884 883 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
885 884 return function(data, escapeMarkup) {
886 885 if (!data.repo_group_id){
887 886 return data.text; // optgroup text Repositories
888 887 }
889 888
890 889 var tmpl = '';
891 890 var repoGroupName = data['text'];
892 891
893 892 if(data){
894 893
895 894 tmpl += '<i class="icon-repo-group"></i> ';
896 895
897 896 }
898 897 tmpl += escapeMarkup(repoGroupName);
899 898 return tmpl;
900 899
901 900 }(result, escapeMarkup);
902 901 };
903 902
904 903 var escapeRegExChars = function (value) {
905 904 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
906 905 };
907 906
908 907 var getRepoIcon = function(repo_type) {
909 908 if (repo_type === 'hg') {
910 909 return '<i class="icon-hg"></i> ';
911 910 }
912 911 else if (repo_type === 'git') {
913 912 return '<i class="icon-git"></i> ';
914 913 }
915 914 else if (repo_type === 'svn') {
916 915 return '<i class="icon-svn"></i> ';
917 916 }
918 917 return ''
919 918 };
920 919
921 920 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
922 921
923 922 if (value.split(':').length === 2) {
924 923 value = value.split(':')[1]
925 924 }
926 925
927 926 var searchType = data['type'];
928 927 var searchSubType = data['subtype'];
929 928 var valueDisplay = data['value_display'];
930 929 var valueIcon = data['value_icon'];
931 930
932 931 var pattern = '(' + escapeRegExChars(value) + ')';
933 932
934 933 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
935 934
936 935 // highlight match
937 936 if (searchType != 'text') {
938 937 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
939 938 }
940 939
941 940 var icon = '';
942 941
943 942 if (searchType === 'hint') {
944 943 icon += '<i class="icon-repo-group"></i> ';
945 944 }
946 945 // full text search/hints
947 946 else if (searchType === 'search') {
948 947 if (valueIcon === undefined) {
949 948 icon += '<i class="icon-more"></i> ';
950 949 } else {
951 950 icon += valueIcon + ' ';
952 951 }
953 952
954 953 if (searchSubType !== undefined && searchSubType == 'repo') {
955 954 valueDisplay += '<div class="pull-right tag">repository</div>';
956 955 }
957 956 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
958 957 valueDisplay += '<div class="pull-right tag">repo group</div>';
959 958 }
960 959 }
961 960 // repository
962 961 else if (searchType === 'repo') {
963 962
964 963 var repoIcon = getRepoIcon(data['repo_type']);
965 964 icon += repoIcon;
966 965
967 966 if (data['private']) {
968 967 icon += '<i class="icon-lock" ></i> ';
969 968 }
970 969 else if (visualShowPublicIcon) {
971 970 icon += '<i class="icon-unlock-alt"></i> ';
972 971 }
973 972 }
974 973 // repository groups
975 974 else if (searchType === 'repo_group') {
976 975 icon += '<i class="icon-repo-group"></i> ';
977 976 }
978 977 // user group
979 978 else if (searchType === 'user_group') {
980 979 icon += '<i class="icon-group"></i> ';
981 980 }
982 981 // user
983 982 else if (searchType === 'user') {
984 983 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
985 984 }
986 985 // pull request
987 986 else if (searchType === 'pull_request') {
988 987 icon += '<i class="icon-merge"></i> ';
989 988 }
990 989 // commit
991 990 else if (searchType === 'commit') {
992 991 var repo_data = data['repo_data'];
993 992 var repoIcon = getRepoIcon(repo_data['repository_type']);
994 993 if (repoIcon) {
995 994 icon += repoIcon;
996 995 } else {
997 996 icon += '<i class="icon-tag"></i>';
998 997 }
999 998 }
1000 999 // file
1001 1000 else if (searchType === 'file') {
1002 1001 var repo_data = data['repo_data'];
1003 1002 var repoIcon = getRepoIcon(repo_data['repository_type']);
1004 1003 if (repoIcon) {
1005 1004 icon += repoIcon;
1006 1005 } else {
1007 1006 icon += '<i class="icon-tag"></i>';
1008 1007 }
1009 1008 }
1010 1009 // generic text
1011 1010 else if (searchType === 'text') {
1012 1011 icon = '';
1013 1012 }
1014 1013
1015 1014 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1016 1015 return tmpl.format(icon, valueDisplay);
1017 1016 };
1018 1017
1019 1018 var handleSelect = function(element, suggestion) {
1020 1019 if (suggestion.type === "hint") {
1021 1020 // we skip action
1022 1021 $('#main_filter').focus();
1023 1022 }
1024 1023 else if (suggestion.type === "text") {
1025 1024 // we skip action
1026 1025 $('#main_filter').focus();
1027 1026
1028 1027 } else {
1029 1028 window.location = suggestion['url'];
1030 1029 }
1031 1030 };
1032 1031
1033 1032 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1034 1033 if (queryLowerCase.split(':').length === 2) {
1035 1034 queryLowerCase = queryLowerCase.split(':')[1]
1036 1035 }
1037 1036 if (suggestion.type === "text") {
1038 1037 // special case we don't want to "skip" display for
1039 1038 return true
1040 1039 }
1041 1040 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1042 1041 };
1043 1042
1044 1043 var cleanContext = {
1045 1044 repo_view_type: null,
1046 1045
1047 1046 repo_id: null,
1048 1047 repo_name: "",
1049 1048
1050 1049 repo_group_id: null,
1051 1050 repo_group_name: null
1052 1051 };
1053 1052 var removeGoToFilter = function () {
1054 1053 $('.searchTagHidable').hide();
1055 1054 $('#main_filter').autocomplete(
1056 1055 'setOptions', {params:{search_context: cleanContext}});
1057 1056 };
1058 1057
1059 1058 $('#main_filter').autocomplete({
1060 1059 serviceUrl: pyroutes.url('goto_switcher_data'),
1061 1060 params: {
1062 1061 "search_context": templateContext.search_context
1063 1062 },
1064 1063 minChars:2,
1065 1064 maxHeight:400,
1066 1065 deferRequestBy: 300, //miliseconds
1067 1066 tabDisabled: true,
1068 1067 autoSelectFirst: false,
1069 1068 containerClass: 'autocomplete-qfilter-suggestions',
1070 1069 formatResult: autocompleteMainFilterFormatResult,
1071 1070 lookupFilter: autocompleteMainFilterResult,
1072 1071 onSelect: function (element, suggestion) {
1073 1072 handleSelect(element, suggestion);
1074 1073 return false;
1075 1074 },
1076 1075 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1077 1076 if (jqXHR !== 'abort') {
1078 1077 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1079 1078 SwalNoAnimation.fire({
1080 1079 icon: 'error',
1081 1080 title: _gettext('Error during search operation'),
1082 1081 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1083 1082 }).then(function(result) {
1084 1083 window.location.reload();
1085 1084 })
1086 1085 }
1087 1086 },
1088 1087 onSearchStart: function (params) {
1089 1088 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1090 1089 },
1091 1090 onSearchComplete: function (query, suggestions) {
1092 1091 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1093 1092 },
1094 1093 });
1095 1094
1096 1095 showMainFilterBox = function () {
1097 1096 $('#main_filter_help').toggle();
1098 1097 };
1099 1098
1100 1099 $('#main_filter').on('keydown.autocomplete', function (e) {
1101 1100
1102 1101 var BACKSPACE = 8;
1103 1102 var el = $(e.currentTarget);
1104 1103 if(e.which === BACKSPACE){
1105 1104 var inputVal = el.val();
1106 1105 if (inputVal === ""){
1107 1106 removeGoToFilter()
1108 1107 }
1109 1108 }
1110 1109 });
1111 1110
1112 1111 var dismissNotice = function(noticeId) {
1113 1112
1114 1113 var url = pyroutes.url('user_notice_dismiss',
1115 1114 {"user_id": templateContext.rhodecode_user.user_id});
1116 1115
1117 1116 var postData = {
1118 1117 'csrf_token': CSRF_TOKEN,
1119 1118 'notice_id': noticeId,
1120 1119 };
1121 1120
1122 1121 var success = function(response) {
1123 1122 $('#notice-message-' + noticeId).remove();
1124 1123 return false;
1125 1124 };
1126 1125 var failure = function(data, textStatus, xhr) {
1127 1126 alert("error processing request: " + textStatus);
1128 1127 return false;
1129 1128 };
1130 1129 ajaxPOST(url, postData, success, failure);
1131 1130 }
1132 1131
1133 1132 var hideLicenseWarning = function () {
1134 1133 var fingerprint = templateContext.session_attrs.license_fingerprint;
1135 1134 storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
1136 1135 $('#notifications').hide();
1137 1136 }
1138 1137
1139 1138 var hideLicenseError = function () {
1140 1139 var fingerprint = templateContext.session_attrs.license_fingerprint;
1141 1140 storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
1142 1141 $('#notifications').hide();
1143 1142 }
1144 1143
1145 1144 </script>
1146 1145 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1147 1146 </%def>
1148 1147
1149 1148 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1150 1149 <div class="modal-dialog">
1151 1150 <div class="modal-content">
1152 1151 <div class="modal-header">
1153 1152 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1154 1153 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1155 1154 </div>
1156 1155 <div class="modal-body">
1157 1156 <div class="block-left">
1158 1157 <table class="keyboard-mappings">
1159 1158 <tbody>
1160 1159 <tr>
1161 1160 <th></th>
1162 1161 <th>${_('Site-wide shortcuts')}</th>
1163 1162 </tr>
1164 1163 <%
1165 1164 elems = [
1166 1165 ('/', 'Use quick search box'),
1167 1166 ('g h', 'Goto home page'),
1168 1167 ('g g', 'Goto my private gists page'),
1169 1168 ('g G', 'Goto my public gists page'),
1170 1169 ('g 0-9', 'Goto bookmarked items from 0-9'),
1171 1170 ('n r', 'New repository page'),
1172 1171 ('n g', 'New gist page'),
1173 1172 ]
1174 1173 %>
1175 1174 %for key, desc in elems:
1176 1175 <tr>
1177 1176 <td class="keys">
1178 1177 <span class="key tag">${key}</span>
1179 1178 </td>
1180 1179 <td>${desc}</td>
1181 1180 </tr>
1182 1181 %endfor
1183 1182 </tbody>
1184 1183 </table>
1185 1184 </div>
1186 1185 <div class="block-left">
1187 1186 <table class="keyboard-mappings">
1188 1187 <tbody>
1189 1188 <tr>
1190 1189 <th></th>
1191 1190 <th>${_('Repositories')}</th>
1192 1191 </tr>
1193 1192 <%
1194 1193 elems = [
1195 1194 ('g s', 'Goto summary page'),
1196 1195 ('g c', 'Goto changelog page'),
1197 1196 ('g f', 'Goto files page'),
1198 1197 ('g F', 'Goto files page with file search activated'),
1199 1198 ('g p', 'Goto pull requests page'),
1200 1199 ('g o', 'Goto repository settings'),
1201 1200 ('g O', 'Goto repository access permissions settings'),
1202 1201 ('t s', 'Toggle sidebar on some pages'),
1203 1202 ]
1204 1203 %>
1205 1204 %for key, desc in elems:
1206 1205 <tr>
1207 1206 <td class="keys">
1208 1207 <span class="key tag">${key}</span>
1209 1208 </td>
1210 1209 <td>${desc}</td>
1211 1210 </tr>
1212 1211 %endfor
1213 1212 </tbody>
1214 1213 </table>
1215 1214 </div>
1216 1215 </div>
1217 1216 <div class="modal-footer">
1218 1217 </div>
1219 1218 </div><!-- /.modal-content -->
1220 1219 </div><!-- /.modal-dialog -->
1221 1220 </div><!-- /.modal -->
1222 1221
1223 1222
1224 1223 <script type="text/javascript">
1225 1224 (function () {
1226 1225 "use sctrict";
1227 1226
1228 1227 // details block auto-hide menu
1229 1228 $(document).mouseup(function(e) {
1230 1229 var container = $('.details-inline-block');
1231 1230 if (!container.is(e.target) && container.has(e.target).length === 0) {
1232 1231 $('.details-inline-block[open]').removeAttr('open')
1233 1232 }
1234 1233 });
1235 1234
1236 1235 var $sideBar = $('.right-sidebar');
1237 1236 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1238 1237 var sidebarState = templateContext.session_attrs.sidebarState;
1239 1238 var sidebarEnabled = $('aside.right-sidebar').get(0);
1240 1239
1241 1240 if (sidebarState === 'expanded') {
1242 1241 expanded = true
1243 1242 } else if (sidebarState === 'collapsed') {
1244 1243 expanded = false
1245 1244 }
1246 1245 if (sidebarEnabled) {
1247 1246 // show sidebar since it's hidden on load
1248 1247 $('.right-sidebar').show();
1249 1248
1250 1249 // init based on set initial class, or if defined user session attrs
1251 1250 if (expanded) {
1252 1251 window.expandSidebar();
1253 1252 window.updateStickyHeader();
1254 1253
1255 1254 } else {
1256 1255 window.collapseSidebar();
1257 1256 window.updateStickyHeader();
1258 1257 }
1259 1258 }
1260 1259 })()
1261 1260
1262 1261 </script>
@@ -1,168 +1,169 b''
1 1 ## snippet for sidebar elements
2 2 ## usage:
3 3 ## <%namespace name="sidebar" file="/base/sidebar.mako"/>
4 4 ## ${sidebar.comments_table()}
5 5 <%namespace name="base" file="/base/base.mako"/>
6 6
7 7 <%def name="comments_table(comments, counter_num, todo_comments=False, draft_comments=False, existing_ids=None, is_pr=True)">
8 8 <%
9 9 if todo_comments:
10 10 cls_ = 'todos-content-table'
11 11 def sorter(entry):
12 12 user_id = entry.author.user_id
13 13 resolved = '1' if entry.resolved else '0'
14 14 if user_id == c.rhodecode_user.user_id:
15 15 # own comments first
16 16 user_id = 0
17 17 return '{}'.format(str(entry.comment_id).zfill(10000))
18 18 elif draft_comments:
19 19 cls_ = 'drafts-content-table'
20 20 def sorter(entry):
21 21 return '{}'.format(str(entry.comment_id).zfill(10000))
22 22 else:
23 23 cls_ = 'comments-content-table'
24 24 def sorter(entry):
25 25 return '{}'.format(str(entry.comment_id).zfill(10000))
26 26
27 27 existing_ids = existing_ids or []
28 28
29 29 %>
30 30
31 31 <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}">
32 32
33 33 % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))):
34 34 <%
35 35 display = ''
36 36 _cls = ''
37 37 ## Extra precaution to not show drafts in the sidebar for todo/comments
38 38 if comment_obj.draft and not draft_comments:
39 39 continue
40 40 %>
41 41
42 42
43 43 <%
44 comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', []))
44 vers = getattr(c, 'versions', [])
45
46 comment_ver_index = comment_obj.get_index_version(vers)
45 47 prev_comment_ver_index = 0
46 48 if loop_obj.previous:
47 prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', []))
48
49 prev_comment_ver_index = loop_obj.previous.get_index_version(vers)
49 50 ver_info = None
50 if getattr(c, 'versions', []):
51 if vers:
51 52 ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None
52 53 %>
53 54 <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %>
54 55 <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %>
55 56 <%
56 if (prev_comment_ver_index > comment_ver_index):
57 if prev_comment_ver_index > comment_ver_index:
57 58 comments_ver_divider = comment_ver_index
58 59 else:
59 60 comments_ver_divider = None
60 61 %>
61 62
62 63 % if todo_comments:
63 64 % if comment_obj.resolved:
64 65 <% _cls = 'resolved-todo' %>
65 66 <% display = 'none' %>
66 67 % endif
67 68 % else:
68 69 ## SKIP TODOs we display them in other area
69 70 % if comment_obj.is_todo and not comment_obj.draft:
70 71 <% display = 'none' %>
71 72 % endif
72 73 ## Skip outdated comments
73 74 % if comment_obj.outdated:
74 75 <% display = 'none' %>
75 76 <% _cls = 'hidden-comment' %>
76 77 % endif
77 78 % endif
78 79
79 80 % if not todo_comments and comments_ver_divider:
80 81 <tr class="old-comments-marker">
81 82 <td colspan="3">
82 83 % if ver_info:
83 84 <code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code>
84 85 % else:
85 86 <code>v${comments_ver_divider}</code>
86 87 % endif
87 88 </td>
88 89 </tr>
89 90
90 91 % endif
91 92
92 93 <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}">
93 94 % if draft_comments:
94 95 <td style="width: 15px;">
95 96 ${h.checkbox('submit_draft', id=None, value=comment_obj.comment_id)}
96 97 </td>
97 98 % endif
98 99 <td class="td-todo-number">
99 100 <%
100 101 version_info = ''
101 102 if is_pr:
102 103 version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version')
103 104 %>
104 105 ## new comments, since refresh
105 106 % if existing_ids and comment_obj.comment_id not in existing_ids:
106 107 <div class="tooltip" style="position: absolute; left: 8px; color: #682668" title="New comment">
107 108 !
108 109 </div>
109 110 % endif
110 111
111 112 <%
112 113 data = h.str_json({
113 114 'comment_id': comment_obj.comment_id,
114 115 'version_info': version_info,
115 116 'file_name': comment_obj.f_path,
116 117 'line_no': comment_obj.line_no,
117 118 'outdated': comment_obj.outdated,
118 119 'inline': comment_obj.is_inline,
119 120 'is_todo': comment_obj.is_todo,
120 121 'created_on': h.format_date(comment_obj.created_on),
121 122 'datetime': '{}{}'.format(comment_obj.created_on, h.get_timezone(comment_obj.created_on, time_is_local=True)),
122 123 'review_status': (comment_obj.review_status or '')
123 124 })
124 125
125 126 icon = ''
126 127
127 128 if comment_obj.outdated:
128 129 icon += ' icon-comment-toggle'
129 130 elif comment_obj.is_inline:
130 131 icon += ' icon-code'
131 132 else:
132 133 icon += ' icon-comment'
133 134
134 135 if comment_obj.draft:
135 136 if comment_obj.is_todo:
136 137 icon = 'icon-flag-filled icon-draft'
137 138 else:
138 139 icon = 'icon-comment icon-draft'
139 140
140 141 %>
141 142
142 143 <i id="commentHovercard${comment_obj.comment_id}"
143 144 class="${icon} tooltip-hovercard"
144 145 data-hovercard-url="javascript:sidebarComment(${comment_obj.comment_id})"
145 146 data-comment-json-b64='${h.b64(data)}'>
146 147 </i>
147 148
148 149 </td>
149 150
150 151 <td class="td-todo-gravatar">
151 152 ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])}
152 153 </td>
153 154 <td class="todo-comment-text-wrapper">
154 155 <div class="todo-comment-text ${('todo-resolved' if comment_obj.resolved else '')}">
155 156 <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink"
156 157 href="#comment-${comment_obj.comment_id}"
157 158 onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})">
158 159
159 160 ${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}
160 161 </a>
161 162 </div>
162 163 </td>
163 164 </tr>
164 165 % endfor
165 166
166 167 </table>
167 168
168 169 </%def> No newline at end of file
@@ -1,1404 +1,1404 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
3 3
4 4 <%def name="diff_line_anchor(commit, filename, line, type)"><%
5 5 return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
6 6 %></%def>
7 7
8 8 <%def name="action_class(action)">
9 9 <%
10 10 return {
11 11 '-': 'cb-deletion',
12 12 '+': 'cb-addition',
13 13 ' ': 'cb-context',
14 14 }.get(action, 'cb-empty')
15 15 %>
16 16 </%def>
17 17
18 18 <%def name="op_class(op_id)">
19 19 <%
20 20 return {
21 21 DEL_FILENODE: 'deletion', # file deleted
22 22 BIN_FILENODE: 'warning' # binary diff hidden
23 23 }.get(op_id, 'addition')
24 24 %>
25 25 </%def>
26 26
27 27
28 28
29 29 <%def name="render_diffset(diffset, commit=None,
30 30
31 31 # collapse all file diff entries when there are more than this amount of files in the diff
32 32 collapse_when_files_over=20,
33 33
34 34 # collapse lines in the diff when more than this amount of lines changed in the file diff
35 35 lines_changed_limit=500,
36 36
37 37 # add a ruler at to the output
38 38 ruler_at_chars=0,
39 39
40 40 # show inline comments
41 41 use_comments=False,
42 42
43 43 # disable new comments
44 44 disable_new_comments=False,
45 45
46 46 # special file-comments that were deleted in previous versions
47 47 # it's used for showing outdated comments for deleted files in a PR
48 48 deleted_files_comments=None,
49 49
50 50 # for cache purpose
51 51 inline_comments=None,
52 52
53 53 # additional menu for PRs
54 54 pull_request_menu=None,
55 55
56 56 # show/hide todo next to comments
57 57 show_todos=True,
58 58
59 59 )">
60 60
61 61 <%
62 62 diffset_container_id = h.md5_safe(diffset.target_ref)
63 63 collapse_all = len(diffset.files) > collapse_when_files_over
64 64 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
65 65 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
66 66 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
67 67 %>
68 68
69 69 %if use_comments:
70 70
71 71 ## Template for injecting comments
72 72 <div id="cb-comments-inline-container-template" class="js-template">
73 73 ${inline_comments_container([])}
74 74 </div>
75 75
76 76 <div class="js-template" id="cb-comment-inline-form-template">
77 77 <div class="comment-inline-form ac">
78 78 %if not c.rhodecode_user.is_default:
79 79 ## render template for inline comments
80 80 ${commentblock.comment_form(form_type='inline')}
81 81 %endif
82 82 </div>
83 83 </div>
84 84
85 85 %endif
86 86
87 87 %if c.user_session_attrs["diffmode"] == 'sideside':
88 88 <style>
89 89 .wrapper {
90 90 max-width: 1600px !important;
91 91 }
92 92 </style>
93 93 %endif
94 94
95 95 %if ruler_at_chars:
96 96 <style>
97 97 .diff table.cb .cb-content:after {
98 98 content: "";
99 99 border-left: 1px solid blue;
100 100 position: absolute;
101 101 top: 0;
102 102 height: 18px;
103 103 opacity: .2;
104 104 z-index: 10;
105 105 //## +5 to account for diff action (+/-)
106 106 left: ${ruler_at_chars + 5}ch;
107 107 </style>
108 108 %endif
109 109
110 110 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
111 111
112 112 <div style="height: 20px; line-height: 20px">
113 113 ## expand/collapse action
114 114 <div class="pull-left">
115 115 <a class="${'collapsed' if collapse_all else ''}" href="#expand-files" onclick="toggleExpand(this, '${diffset_container_id}'); return false">
116 116 % if collapse_all:
117 117 <i class="icon-plus-squared-alt icon-no-margin"></i>${_('Expand all files')}
118 118 % else:
119 119 <i class="icon-minus-squared-alt icon-no-margin"></i>${_('Collapse all files')}
120 120 % endif
121 121 </a>
122 122
123 123 </div>
124 124
125 125 ## todos
126 126 % if show_todos and getattr(c, 'at_version', None):
127 127 <div class="pull-right">
128 128 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
129 129 ${_('not available in this view')}
130 130 </div>
131 131 % elif show_todos:
132 132 <div class="pull-right">
133 133 <div class="comments-number" style="padding-left: 10px">
134 134 % if hasattr(c, 'unresolved_comments') and hasattr(c, 'resolved_comments'):
135 135 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
136 136 % if c.unresolved_comments:
137 137 <a href="#show-todos" onclick="$('#todo-box').toggle(); return false">
138 138 ${_('{} unresolved').format(len(c.unresolved_comments))}
139 139 </a>
140 140 % else:
141 141 ${_('0 unresolved')}
142 142 % endif
143 143
144 144 ${_('{} Resolved').format(len(c.resolved_comments))}
145 145 % endif
146 146 </div>
147 147 </div>
148 148 % endif
149 149
150 150 ## ## comments
151 151 ## <div class="pull-right">
152 152 ## <div class="comments-number" style="padding-left: 10px">
153 153 ## % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
154 154 ## <i class="icon-comment" style="color: #949494">COMMENTS:</i>
155 155 ## % if c.comments:
156 156 ## <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
157 157 ## % else:
158 158 ## ${_('0 General')}
159 159 ## % endif
160 160 ##
161 161 ## % if c.inline_cnt:
162 162 ## <a href="#" onclick="return Rhodecode.comments.nextComment();"
163 163 ## id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
164 164 ## </a>
165 165 ## % else:
166 166 ## ${_('0 Inline')}
167 167 ## % endif
168 168 ## % endif
169 169 ##
170 170 ## % if pull_request_menu:
171 171 ## <%
172 172 ## outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
173 173 ## %>
174 174 ##
175 175 ## % if outdated_comm_count_ver:
176 176 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
177 177 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
178 178 ## </a>
179 179 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
180 180 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
181 181 ## % else:
182 182 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
183 183 ## % endif
184 184 ##
185 185 ## % endif
186 186 ##
187 187 ## </div>
188 188 ## </div>
189 189
190 190 </div>
191 191
192 192 % if diffset.limited_diff:
193 193 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
194 194 <h2 class="clearinner">
195 195 ${_('The requested changes are too big and content was truncated.')}
196 196 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
197 197 </h2>
198 198 </div>
199 199 % endif
200 200
201 201 <div id="todo-box">
202 202 % if hasattr(c, 'unresolved_comments') and c.unresolved_comments:
203 203 % for co in c.unresolved_comments:
204 204 <a class="permalink" href="#comment-${co.comment_id}"
205 205 onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))">
206 206 <i class="icon-flag-filled-red"></i>
207 207 ${co.comment_id}</a>${('' if loop.last else ',')}
208 208 % endfor
209 209 % endif
210 210 </div>
211 211 %if diffset.has_hidden_changes:
212 212 <p class="empty_data">${_('Some changes may be hidden')}</p>
213 213 %elif not diffset.files:
214 214 <p class="empty_data">${_('No files')}</p>
215 215 %endif
216 216
217 217 <div class="filediffs">
218 218
219 219 ## initial value could be marked as False later on
220 220 <% over_lines_changed_limit = False %>
221 221 %for i, filediff in enumerate(diffset.files):
222 222
223 223 %if filediff.source_file_path and filediff.target_file_path:
224 224 %if filediff.source_file_path != filediff.target_file_path:
225 225 ## file was renamed, or copied
226 226 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
227 227 <%
228 228 final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> <del>{}</del>'.format(filediff.target_file_path, filediff.source_file_path))
229 229 final_path = filediff.target_file_path
230 230 %>
231 231 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
232 232 <%
233 233 final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> {}'.format(filediff.target_file_path, filediff.source_file_path))
234 234 final_path = filediff.target_file_path
235 235 %>
236 236 %endif
237 237 %else:
238 238 ## file was modified
239 239 <%
240 240 final_file_name = filediff.source_file_path
241 241 final_path = final_file_name
242 242 %>
243 243 %endif
244 244 %else:
245 245 %if filediff.source_file_path:
246 246 ## file was deleted
247 247 <%
248 248 final_file_name = filediff.source_file_path
249 249 final_path = final_file_name
250 250 %>
251 251 %else:
252 252 ## file was added
253 253 <%
254 254 final_file_name = filediff.target_file_path
255 255 final_path = final_file_name
256 256 %>
257 257 %endif
258 258 %endif
259 259
260 260 <%
261 261 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
262 262 over_lines_changed_limit = lines_changed > lines_changed_limit
263 263 %>
264 264 ## anchor with support of sticky header
265 265 <div class="anchor" id="a_${h.FID(filediff.raw_id, filediff.patch['filename'])}"></div>
266 266
267 267 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filediff)}" type="checkbox" onchange="updateSticky();">
268 268 <div
269 269 class="filediff"
270 270 data-f-path="${filediff.patch['filename']}"
271 271 data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}"
272 272 >
273 273 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
274 274 <%
275 275 file_comments = (get_inline_comments(inline_comments, filediff.patch['filename']) or {}).values()
276 276 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not (_c.outdated or _c.draft)]
277 277 %>
278 278 <div class="filediff-collapse-indicator icon-"></div>
279 279
280 280 ## Comments/Options PILL
281 281 <span class="pill-group pull-right">
282 282 <span class="pill" op="comments">
283 283 <i class="icon-comment"></i> ${len(total_file_comments)}
284 284 </span>
285 285
286 286 <details class="details-reset details-inline-block">
287 287 <summary class="noselect">
288 288 <i class="pill icon-options cursor-pointer" op="options"></i>
289 289 </summary>
290 290 <details-menu class="details-dropdown">
291 291
292 292 <div class="dropdown-item">
293 293 <span>${final_path}</span>
294 294 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="Copy file path"></span>
295 295 </div>
296 296
297 297 <div class="dropdown-divider"></div>
298 298
299 299 <div class="dropdown-item">
300 300 <% permalink = request.current_route_url(_anchor='a_{}'.format(h.FID(filediff.raw_id, filediff.patch['filename']))) %>
301 301 <a href="${permalink}">ΒΆ permalink</a>
302 302 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${permalink}" title="Copy permalink"></span>
303 303 </div>
304 304
305 305
306 306 </details-menu>
307 307 </details>
308 308
309 309 </span>
310 310
311 311 ${diff_ops(final_file_name, filediff)}
312 312
313 313 </label>
314 314
315 315 ${diff_menu(filediff, use_comments=use_comments)}
316 <table id="file-${h.safeid(h.safe_unicode(filediff.patch['filename']))}" data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
316 <table id="file-${h.safeid(h.safe_str(filediff.patch['filename']))}" data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
317 317
318 318 ## new/deleted/empty content case
319 319 % if not filediff.hunks:
320 320 ## Comment container, on "fakes" hunk that contains all data to render comments
321 321 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], filediff.hunk_ops, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
322 322 % endif
323 323
324 324 %if filediff.limited_diff:
325 325 <tr class="cb-warning cb-collapser">
326 326 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
327 327 ${_('The requested commit or file is too big and content was truncated.')} <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
328 328 </td>
329 329 </tr>
330 330 %else:
331 331 %if over_lines_changed_limit:
332 332 <tr class="cb-warning cb-collapser">
333 333 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
334 334 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
335 335 <a href="#" class="cb-expand"
336 336 onclick="$(this).closest('table').removeClass('cb-collapsed'); updateSticky(); return false;">${_('Show them')}
337 337 </a>
338 338 <a href="#" class="cb-collapse"
339 339 onclick="$(this).closest('table').addClass('cb-collapsed'); updateSticky(); return false;">${_('Hide them')}
340 340 </a>
341 341 </td>
342 342 </tr>
343 343 %endif
344 344 %endif
345 345
346 346 % for hunk in filediff.hunks:
347 347 <tr class="cb-hunk">
348 348 <td ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=3' or '')}>
349 349 ## TODO: dan: add ajax loading of more context here
350 350 ## <a href="#">
351 351 <i class="icon-more"></i>
352 352 ## </a>
353 353 </td>
354 354 <td ${(c.user_session_attrs["diffmode"] == 'sideside' and 'colspan=5' or '')}>
355 355 @@
356 356 -${hunk.source_start},${hunk.source_length}
357 357 +${hunk.target_start},${hunk.target_length}
358 358 ${hunk.section_header}
359 359 </td>
360 360 </tr>
361 361
362 362 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
363 363 % endfor
364 364
365 365 <% unmatched_comments = (inline_comments or {}).get(filediff.patch['filename'], {}) %>
366 366
367 367 ## outdated comments that do not fit into currently displayed lines
368 368 % for lineno, comments in unmatched_comments.items():
369 369
370 370 %if c.user_session_attrs["diffmode"] == 'unified':
371 371 % if loop.index == 0:
372 372 <tr class="cb-hunk">
373 373 <td colspan="3"></td>
374 374 <td>
375 375 <div>
376 376 ${_('Unmatched/outdated inline comments below')}
377 377 </div>
378 378 </td>
379 379 </tr>
380 380 % endif
381 381 <tr class="cb-line">
382 382 <td class="cb-data cb-context"></td>
383 383 <td class="cb-lineno cb-context"></td>
384 384 <td class="cb-lineno cb-context"></td>
385 385 <td class="cb-content cb-context">
386 386 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
387 387 </td>
388 388 </tr>
389 389 %elif c.user_session_attrs["diffmode"] == 'sideside':
390 390 % if loop.index == 0:
391 391 <tr class="cb-comment-info">
392 392 <td colspan="2"></td>
393 393 <td class="cb-line">
394 394 <div>
395 395 ${_('Unmatched/outdated inline comments below')}
396 396 </div>
397 397 </td>
398 398 <td colspan="2"></td>
399 399 <td class="cb-line">
400 400 <div>
401 401 ${_('Unmatched/outdated comments below')}
402 402 </div>
403 403 </td>
404 404 </tr>
405 405 % endif
406 406 <tr class="cb-line">
407 407 <td class="cb-data cb-context"></td>
408 408 <td class="cb-lineno cb-context"></td>
409 409 <td class="cb-content cb-context">
410 410 % if lineno.startswith('o'):
411 411 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
412 412 % endif
413 413 </td>
414 414
415 415 <td class="cb-data cb-context"></td>
416 416 <td class="cb-lineno cb-context"></td>
417 417 <td class="cb-content cb-context">
418 418 % if lineno.startswith('n'):
419 419 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries)}
420 420 % endif
421 421 </td>
422 422 </tr>
423 423 %endif
424 424
425 425 % endfor
426 426
427 427 </table>
428 428 </div>
429 429 %endfor
430 430
431 431 ## outdated comments that are made for a file that has been deleted
432 432 % for filename, comments_dict in (deleted_files_comments or {}).items():
433 433
434 434 <%
435 435 display_state = 'display: none'
436 436 open_comments_in_file = [x for x in comments_dict['comments'] if x.outdated is False]
437 437 if open_comments_in_file:
438 438 display_state = ''
439 439 fid = str(id(filename))
440 440 %>
441 441 <div class="filediffs filediff-outdated" style="${display_state}">
442 442 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filename)}" type="checkbox" onchange="updateSticky();">
443 443 <div class="filediff" data-f-path="${filename}" id="a_${h.FID(fid, filename)}">
444 444 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
445 445 <div class="filediff-collapse-indicator icon-"></div>
446 446
447 447 <span class="pill">
448 448 ## file was deleted
449 449 ${filename}
450 450 </span>
451 451 <span class="pill-group pull-left" >
452 452 ## file op, doesn't need translation
453 453 <span class="pill" op="removed">unresolved comments</span>
454 454 </span>
455 455 <a class="pill filediff-anchor" href="#a_${h.FID(fid, filename)}">ΒΆ</a>
456 456 <span class="pill-group pull-right">
457 457 <span class="pill" op="deleted">
458 458 % if comments_dict['stats'] >0:
459 459 -${comments_dict['stats']}
460 460 % else:
461 461 ${comments_dict['stats']}
462 462 % endif
463 463 </span>
464 464 </span>
465 465 </label>
466 466
467 467 <table class="cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
468 468 <tr>
469 469 % if c.user_session_attrs["diffmode"] == 'unified':
470 470 <td></td>
471 471 %endif
472 472
473 473 <td></td>
474 474 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=5')}>
475 475 <strong>${_('This file was removed from diff during updates to this pull-request.')}</strong><br/>
476 476 ${_('There are still outdated/unresolved comments attached to it.')}
477 477 </td>
478 478 </tr>
479 479 %if c.user_session_attrs["diffmode"] == 'unified':
480 480 <tr class="cb-line">
481 481 <td class="cb-data cb-context"></td>
482 482 <td class="cb-lineno cb-context"></td>
483 483 <td class="cb-lineno cb-context"></td>
484 484 <td class="cb-content cb-context">
485 485 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
486 486 </td>
487 487 </tr>
488 488 %elif c.user_session_attrs["diffmode"] == 'sideside':
489 489 <tr class="cb-line">
490 490 <td class="cb-data cb-context"></td>
491 491 <td class="cb-lineno cb-context"></td>
492 492 <td class="cb-content cb-context"></td>
493 493
494 494 <td class="cb-data cb-context"></td>
495 495 <td class="cb-lineno cb-context"></td>
496 496 <td class="cb-content cb-context">
497 497 ${inline_comments_container(comments_dict['comments'], active_pattern_entries=active_pattern_entries)}
498 498 </td>
499 499 </tr>
500 500 %endif
501 501 </table>
502 502 </div>
503 503 </div>
504 504 % endfor
505 505
506 506 </div>
507 507 </div>
508 508 </%def>
509 509
510 510 <%def name="diff_ops(file_name, filediff)">
511 511 <%
512 512 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
513 513 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
514 514 %>
515 515 <span class="pill">
516 516 <i class="icon-file-text"></i>
517 517 ${file_name}
518 518 </span>
519 519
520 520 <span class="pill-group pull-right">
521 521
522 522 ## ops pills
523 523 %if filediff.limited_diff:
524 524 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
525 525 %endif
526 526
527 527 %if NEW_FILENODE in filediff.patch['stats']['ops']:
528 528 <span class="pill" op="created">created</span>
529 529 %if filediff['target_mode'].startswith('120'):
530 530 <span class="pill" op="symlink">symlink</span>
531 531 %else:
532 532 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
533 533 %endif
534 534 %endif
535 535
536 536 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
537 537 <span class="pill" op="renamed">renamed</span>
538 538 %endif
539 539
540 540 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
541 541 <span class="pill" op="copied">copied</span>
542 542 %endif
543 543
544 544 %if DEL_FILENODE in filediff.patch['stats']['ops']:
545 545 <span class="pill" op="removed">removed</span>
546 546 %endif
547 547
548 548 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
549 549 <span class="pill" op="mode">
550 550 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
551 551 </span>
552 552 %endif
553 553
554 554 %if BIN_FILENODE in filediff.patch['stats']['ops']:
555 555 <span class="pill" op="binary">binary</span>
556 556 %if MOD_FILENODE in filediff.patch['stats']['ops']:
557 557 <span class="pill" op="modified">modified</span>
558 558 %endif
559 559 %endif
560 560
561 561 <span class="pill" op="added">${('+' if filediff.patch['stats']['added'] else '')}${filediff.patch['stats']['added']}</span>
562 562 <span class="pill" op="deleted">${((h.safe_int(filediff.patch['stats']['deleted']) or 0) * -1)}</span>
563 563
564 564 </span>
565 565
566 566 </%def>
567 567
568 568 <%def name="nice_mode(filemode)">
569 569 ${(filemode.startswith('100') and filemode[3:] or filemode)}
570 570 </%def>
571 571
572 572 <%def name="diff_menu(filediff, use_comments=False)">
573 573 <div class="filediff-menu">
574 574
575 575 %if filediff.diffset.source_ref:
576 576
577 577 ## FILE BEFORE CHANGES
578 578 %if filediff.operation in ['D', 'M']:
579 579 <a
580 580 class="tooltip"
581 581 href="${h.route_path('repo_files',repo_name=filediff.diffset.target_repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
582 582 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
583 583 >
584 584 ${_('Show file before')}
585 585 </a> |
586 586 %else:
587 587 <span
588 588 class="tooltip"
589 589 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
590 590 >
591 591 ${_('Show file before')}
592 592 </span> |
593 593 %endif
594 594
595 595 ## FILE AFTER CHANGES
596 596 %if filediff.operation in ['A', 'M']:
597 597 <a
598 598 class="tooltip"
599 599 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
600 600 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
601 601 >
602 602 ${_('Show file after')}
603 603 </a>
604 604 %else:
605 605 <span
606 606 class="tooltip"
607 607 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
608 608 >
609 609 ${_('Show file after')}
610 610 </span>
611 611 %endif
612 612
613 613 % if use_comments:
614 614 |
615 615 <a href="#" onclick="Rhodecode.comments.toggleDiffComments(this);return toggleElement(this)"
616 616 data-toggle-on="${_('Hide comments')}"
617 617 data-toggle-off="${_('Show comments')}">
618 618 <span class="hide-comment-button">${_('Hide comments')}</span>
619 619 </a>
620 620 % endif
621 621
622 622 %endif
623 623
624 624 </div>
625 625 </%def>
626 626
627 627
628 628 <%def name="inline_comments_container(comments, active_pattern_entries=None, line_no='', f_path='')">
629 629
630 630 <div class="inline-comments">
631 631 %for comment in comments:
632 632 ${commentblock.comment_block(comment, inline=True, active_pattern_entries=active_pattern_entries)}
633 633 %endfor
634 634
635 635 <%
636 636 extra_class = ''
637 637 extra_style = ''
638 638
639 639 if comments and comments[-1].outdated_at_version(c.at_version_num):
640 640 extra_class = ' comment-outdated'
641 641 extra_style = 'display: none;'
642 642
643 643 %>
644 644
645 645 <div class="reply-thread-container-wrapper${extra_class}" style="${extra_style}">
646 646 <div class="reply-thread-container${extra_class}">
647 647 <div class="reply-thread-gravatar">
648 648 % if c.rhodecode_user.username != h.DEFAULT_USER:
649 649 ${base.gravatar(c.rhodecode_user.email, 20, tooltip=True, user=c.rhodecode_user)}
650 650 % endif
651 651 </div>
652 652
653 653 <div class="reply-thread-reply-button">
654 654 % if c.rhodecode_user.username != h.DEFAULT_USER:
655 655 ## initial reply button, some JS logic can append here a FORM to leave a first comment.
656 656 <button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">Reply...</button>
657 657 % endif
658 658 </div>
659 659 ##% endif
660 660 <div class="reply-thread-last"></div>
661 661 </div>
662 662 </div>
663 663 </div>
664 664
665 665 </%def>
666 666
667 667 <%!
668 668
669 669 def get_inline_comments(comments, filename):
670 670 if hasattr(filename, 'unicode_path'):
671 671 filename = filename.unicode_path
672 672
673 673 if not isinstance(filename, str):
674 674 return None
675 675
676 676 if comments and filename in comments:
677 677 return comments[filename]
678 678
679 679 return None
680 680
681 681 def get_comments_for(diff_type, comments, filename, line_version, line_number):
682 682 if hasattr(filename, 'unicode_path'):
683 683 filename = filename.unicode_path
684 684
685 685 if not isinstance(filename, str):
686 686 return None
687 687
688 688 file_comments = get_inline_comments(comments, filename)
689 689 if file_comments is None:
690 690 return None
691 691
692 692 line_key = '{}{}'.format(line_version, line_number) ## e.g o37, n12
693 693 if line_key in file_comments:
694 694 data = file_comments.pop(line_key)
695 695 return data
696 696 %>
697 697
698 698 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
699 699
700 700 <% chunk_count = 1 %>
701 701 %for loop_obj, item in h.looper(hunk.sideside):
702 702 <%
703 703 line = item
704 704 i = loop_obj.index
705 705 prev_line = loop_obj.previous
706 706 old_line_anchor, new_line_anchor = None, None
707 707
708 708 if line.original.lineno:
709 709 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
710 710 if line.modified.lineno:
711 711 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
712 712
713 713 line_action = line.modified.action or line.original.action
714 714 prev_line_action = prev_line and (prev_line.modified.action or prev_line.original.action)
715 715 %>
716 716
717 717 <tr class="cb-line">
718 718 <td class="cb-data ${action_class(line.original.action)}"
719 719 data-line-no="${line.original.lineno}"
720 720 >
721 721
722 722 <% line_old_comments, line_old_comments_no_drafts = None, None %>
723 723 %if line.original.get_comment_args:
724 724 <%
725 725 line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args)
726 726 line_old_comments_no_drafts = [c for c in line_old_comments if not c.draft] if line_old_comments else []
727 727 has_outdated = any([x.outdated for x in line_old_comments_no_drafts])
728 728 %>
729 729 %endif
730 730 %if line_old_comments_no_drafts:
731 731 % if has_outdated:
732 732 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
733 733 % else:
734 734 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_old_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
735 735 % endif
736 736 %endif
737 737 </td>
738 738 <td class="cb-lineno ${action_class(line.original.action)}"
739 739 data-line-no="${line.original.lineno}"
740 740 %if old_line_anchor:
741 741 id="${old_line_anchor}"
742 742 %endif
743 743 >
744 744 %if line.original.lineno:
745 745 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
746 746 %endif
747 747 </td>
748 748
749 749 <% line_no = 'o{}'.format(line.original.lineno) %>
750 750 <td class="cb-content ${action_class(line.original.action)}"
751 751 data-line-no="${line_no}"
752 752 >
753 753 %if use_comments and line.original.lineno:
754 754 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
755 755 %endif
756 756 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
757 757
758 758 %if use_comments and line.original.lineno and line_old_comments:
759 759 ${inline_comments_container(line_old_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
760 760 %endif
761 761
762 762 </td>
763 763 <td class="cb-data ${action_class(line.modified.action)}"
764 764 data-line-no="${line.modified.lineno}"
765 765 >
766 766 <div>
767 767
768 768 <% line_new_comments, line_new_comments_no_drafts = None, None %>
769 769 %if line.modified.get_comment_args:
770 770 <%
771 771 line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args)
772 772 line_new_comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
773 773 has_outdated = any([x.outdated for x in line_new_comments_no_drafts])
774 774 %>
775 775 %endif
776 776
777 777 %if line_new_comments_no_drafts:
778 778 % if has_outdated:
779 779 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
780 780 % else:
781 781 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(line_new_comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
782 782 % endif
783 783 %endif
784 784 </div>
785 785 </td>
786 786 <td class="cb-lineno ${action_class(line.modified.action)}"
787 787 data-line-no="${line.modified.lineno}"
788 788 %if new_line_anchor:
789 789 id="${new_line_anchor}"
790 790 %endif
791 791 >
792 792 %if line.modified.lineno:
793 793 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
794 794 %endif
795 795 </td>
796 796
797 797 <% line_no = 'n{}'.format(line.modified.lineno) %>
798 798 <td class="cb-content ${action_class(line.modified.action)}"
799 799 data-line-no="${line_no}"
800 800 >
801 801 %if use_comments and line.modified.lineno:
802 802 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
803 803 %endif
804 804 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
805 805 % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']:
806 806 <div class="nav-chunk" style="visibility: hidden">
807 807 <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i>
808 808 </div>
809 809 <% chunk_count +=1 %>
810 810 % endif
811 811 %if use_comments and line.modified.lineno and line_new_comments:
812 812 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
813 813 %endif
814 814
815 815 </td>
816 816 </tr>
817 817 %endfor
818 818 </%def>
819 819
820 820
821 821 <%def name="render_hunk_lines_unified(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
822 822 %for old_line_no, new_line_no, action, content, comments_args in hunk.unified:
823 823
824 824 <%
825 825 old_line_anchor, new_line_anchor = None, None
826 826 if old_line_no:
827 827 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, old_line_no, 'o')
828 828 if new_line_no:
829 829 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, new_line_no, 'n')
830 830 %>
831 831 <tr class="cb-line">
832 832 <td class="cb-data ${action_class(action)}">
833 833 <div>
834 834
835 835 <% comments, comments_no_drafts = None, None %>
836 836 %if comments_args:
837 837 <%
838 838 comments = get_comments_for('unified', inline_comments, *comments_args)
839 839 comments_no_drafts = [c for c in line_new_comments if not c.draft] if line_new_comments else []
840 840 has_outdated = any([x.outdated for x in comments_no_drafts])
841 841 %>
842 842 %endif
843 843
844 844 % if comments_no_drafts:
845 845 % if has_outdated:
846 846 <i class="tooltip toggle-comment-action icon-comment-toggle" title="${_('Comments including outdated: {}. Click here to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
847 847 % else:
848 848 <i class="tooltip toggle-comment-action icon-comment" title="${_('Comments: {}. Click to toggle them.').format(len(comments_no_drafts))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
849 849 % endif
850 850 % endif
851 851 </div>
852 852 </td>
853 853 <td class="cb-lineno ${action_class(action)}"
854 854 data-line-no="${old_line_no}"
855 855 %if old_line_anchor:
856 856 id="${old_line_anchor}"
857 857 %endif
858 858 >
859 859 %if old_line_anchor:
860 860 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
861 861 %endif
862 862 </td>
863 863 <td class="cb-lineno ${action_class(action)}"
864 864 data-line-no="${new_line_no}"
865 865 %if new_line_anchor:
866 866 id="${new_line_anchor}"
867 867 %endif
868 868 >
869 869 %if new_line_anchor:
870 870 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
871 871 %endif
872 872 </td>
873 873 <% line_no = '{}{}'.format(new_line_no and 'n' or 'o', new_line_no or old_line_no) %>
874 874 <td class="cb-content ${action_class(action)}"
875 875 data-line-no="${line_no}"
876 876 >
877 877 %if use_comments:
878 878 ${render_add_comment_button(line_no=line_no, f_path=filediff.patch['filename'])}
879 879 %endif
880 880 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
881 881 %if use_comments and comments:
882 882 ${inline_comments_container(comments, active_pattern_entries=active_pattern_entries, line_no=line_no, f_path=filediff.patch['filename'])}
883 883 %endif
884 884 </td>
885 885 </tr>
886 886 %endfor
887 887 </%def>
888 888
889 889
890 890 <%def name="render_hunk_lines(filediff, diff_mode, hunk, use_comments, inline_comments, active_pattern_entries)">
891 891 % if diff_mode == 'unified':
892 892 ${render_hunk_lines_unified(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
893 893 % elif diff_mode == 'sideside':
894 894 ${render_hunk_lines_sideside(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
895 895 % else:
896 896 <tr class="cb-line">
897 897 <td>unknown diff mode</td>
898 898 </tr>
899 899 % endif
900 900 </%def>file changes
901 901
902 902
903 903 <%def name="render_add_comment_button(line_no='', f_path='')">
904 904 % if not c.rhodecode_user.is_default:
905 905 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this, '${f_path}', '${line_no}', null)">
906 906 <span><i class="icon-comment"></i></span>
907 907 </button>
908 908 % endif
909 909 </%def>
910 910
911 911 <%def name="render_diffset_menu(diffset, range_diff_on=None, commit=None, pull_request_menu=None)">
912 912 <% diffset_container_id = h.md5_safe(diffset.target_ref) %>
913 913
914 914 <div id="diff-file-sticky" class="diffset-menu clearinner">
915 915 ## auto adjustable
916 916 <div class="sidebar__inner">
917 917 <div class="sidebar__bar">
918 918 <div class="pull-right">
919 919
920 920 <div class="btn-group" style="margin-right: 5px;">
921 921 <a class="tooltip btn" onclick="scrollDown();return false" title="${_('Scroll to page bottom')}">
922 922 <i class="icon-arrow_down"></i>
923 923 </a>
924 924 <a class="tooltip btn" onclick="scrollUp();return false" title="${_('Scroll to page top')}">
925 925 <i class="icon-arrow_up"></i>
926 926 </a>
927 927 </div>
928 928
929 929 <div class="btn-group">
930 930 <a class="btn tooltip toggle-wide-diff" href="#toggle-wide-diff" onclick="toggleWideDiff(this); return false" title="${h.tooltip(_('Toggle wide diff'))}">
931 931 <i class="icon-wide-mode"></i>
932 932 </a>
933 933 </div>
934 934 <div class="btn-group">
935 935
936 936 <a
937 937 class="btn ${(c.user_session_attrs["diffmode"] == 'sideside' and 'btn-active')} tooltip"
938 938 title="${h.tooltip(_('View diff as side by side'))}"
939 939 href="${h.current_route_path(request, diffmode='sideside')}">
940 940 <span>${_('Side by Side')}</span>
941 941 </a>
942 942
943 943 <a
944 944 class="btn ${(c.user_session_attrs["diffmode"] == 'unified' and 'btn-active')} tooltip"
945 945 title="${h.tooltip(_('View diff as unified'))}" href="${h.current_route_path(request, diffmode='unified')}">
946 946 <span>${_('Unified')}</span>
947 947 </a>
948 948
949 949 % if range_diff_on is True:
950 950 <a
951 951 title="${_('Turn off: Show the diff as commit range')}"
952 952 class="btn btn-primary"
953 953 href="${h.current_route_path(request, **{"range-diff":"0"})}">
954 954 <span>${_('Range Diff')}</span>
955 955 </a>
956 956 % elif range_diff_on is False:
957 957 <a
958 958 title="${_('Show the diff as commit range')}"
959 959 class="btn"
960 960 href="${h.current_route_path(request, **{"range-diff":"1"})}">
961 961 <span>${_('Range Diff')}</span>
962 962 </a>
963 963 % endif
964 964 </div>
965 965 <div class="btn-group">
966 966
967 967 <details class="details-reset details-inline-block">
968 968 <summary class="noselect btn">
969 969 <i class="icon-options cursor-pointer" op="options"></i>
970 970 </summary>
971 971
972 972 <div>
973 973 <details-menu class="details-dropdown" style="top: 35px;">
974 974
975 975 <div class="dropdown-item">
976 976 <div style="padding: 2px 0px">
977 977 % if request.GET.get('ignorews', '') == '1':
978 978 <a href="${h.current_route_path(request, ignorews=0)}">${_('Show whitespace changes')}</a>
979 979 % else:
980 980 <a href="${h.current_route_path(request, ignorews=1)}">${_('Hide whitespace changes')}</a>
981 981 % endif
982 982 </div>
983 983 </div>
984 984
985 985 <div class="dropdown-item">
986 986 <div style="padding: 2px 0px">
987 987 % if request.GET.get('fullcontext', '') == '1':
988 988 <a href="${h.current_route_path(request, fullcontext=0)}">${_('Hide full context diff')}</a>
989 989 % else:
990 990 <a href="${h.current_route_path(request, fullcontext=1)}">${_('Show full context diff')}</a>
991 991 % endif
992 992 </div>
993 993 </div>
994 994
995 995 </details-menu>
996 996 </div>
997 997 </details>
998 998
999 999 </div>
1000 1000 </div>
1001 1001 <div class="pull-left">
1002 1002 <div class="btn-group">
1003 1003 <div class="pull-left">
1004 1004 ${h.hidden('file_filter_{}'.format(diffset_container_id))}
1005 1005 </div>
1006 1006
1007 1007 </div>
1008 1008 </div>
1009 1009 </div>
1010 1010 <div class="fpath-placeholder pull-left">
1011 1011 <i class="icon-file-text"></i>
1012 1012 <strong class="fpath-placeholder-text">
1013 1013 Context file:
1014 1014 </strong>
1015 1015 </div>
1016 1016 <div class="pull-right noselect">
1017 1017 %if commit:
1018 1018 <span>
1019 1019 <code>${h.show_id(commit)}</code>
1020 1020 </span>
1021 1021 %elif pull_request_menu and pull_request_menu.get('pull_request'):
1022 1022 <span>
1023 1023 <code>!${pull_request_menu['pull_request'].pull_request_id}</code>
1024 1024 </span>
1025 1025 %endif
1026 1026 % if commit or pull_request_menu:
1027 1027 <span class="tooltip" title="Navigate to previous or next change inside files." id="diff_nav">Loading diff...:</span>
1028 1028 <span class="cursor-pointer" onclick="scrollToPrevChunk(); return false">
1029 1029 <i class="icon-angle-up"></i>
1030 1030 </span>
1031 1031 <span class="cursor-pointer" onclick="scrollToNextChunk(); return false">
1032 1032 <i class="icon-angle-down"></i>
1033 1033 </span>
1034 1034 % endif
1035 1035 </div>
1036 1036 <div class="sidebar_inner_shadow"></div>
1037 1037 </div>
1038 1038 </div>
1039 1039
1040 1040 % if diffset:
1041 1041 %if diffset.limited_diff:
1042 1042 <% file_placeholder = _ungettext('%(num)s file changed', '%(num)s files changed', diffset.changed_files) % {'num': diffset.changed_files} %>
1043 1043 %else:
1044 1044 <% file_placeholder = h.literal(_ungettext('%(num)s file changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>', '%(num)s files changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>',
1045 1045 diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}) %>
1046 1046
1047 1047 %endif
1048 1048 ## case on range-diff placeholder needs to be updated
1049 1049 % if range_diff_on is True:
1050 1050 <% file_placeholder = _('Disabled on range diff') %>
1051 1051 % endif
1052 1052
1053 1053 <script type="text/javascript">
1054 1054 var feedFilesOptions = function (query, initialData) {
1055 1055 var data = {results: []};
1056 1056 var isQuery = typeof query.term !== 'undefined';
1057 1057
1058 1058 var section = _gettext('Changed files');
1059 1059 var filteredData = [];
1060 1060
1061 1061 //filter results
1062 1062 $.each(initialData.results, function (idx, value) {
1063 1063
1064 1064 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
1065 1065 filteredData.push({
1066 1066 'id': this.id,
1067 1067 'text': this.text,
1068 1068 "ops": this.ops,
1069 1069 })
1070 1070 }
1071 1071
1072 1072 });
1073 1073
1074 1074 data.results = filteredData;
1075 1075
1076 1076 query.callback(data);
1077 1077 };
1078 1078
1079 1079 var selectionFormatter = function(data, escapeMarkup) {
1080 1080 var container = '<div class="filelist" style="padding-right:100px">{0}</div>';
1081 1081 var tmpl = '<div><strong>{0}</strong></div>'.format(escapeMarkup(data['text']));
1082 1082 var pill = '<div class="pill-group" style="position: absolute; top:7px; right: 0">' +
1083 1083 '<span class="pill" op="added">{0}</span>' +
1084 1084 '<span class="pill" op="deleted">{1}</span>' +
1085 1085 '</div>'
1086 1086 ;
1087 1087 var added = data['ops']['added'];
1088 1088 if (added === 0) {
1089 1089 // don't show +0
1090 1090 added = 0;
1091 1091 } else {
1092 1092 added = '+' + added;
1093 1093 }
1094 1094
1095 1095 var deleted = -1*data['ops']['deleted'];
1096 1096
1097 1097 tmpl += pill.format(added, deleted);
1098 1098 return container.format(tmpl);
1099 1099 };
1100 1100 var formatFileResult = function(result, container, query, escapeMarkup) {
1101 1101 return selectionFormatter(result, escapeMarkup);
1102 1102 };
1103 1103
1104 1104 var formatSelection = function (data, container) {
1105 1105 return '${file_placeholder}'
1106 1106 };
1107 1107
1108 1108 if (window.preloadFileFilterData === undefined) {
1109 1109 window.preloadFileFilterData = {}
1110 1110 }
1111 1111
1112 1112 preloadFileFilterData["${diffset_container_id}"] = {
1113 1113 results: [
1114 1114 % for filediff in diffset.files:
1115 1115 {id:"a_${h.FID(filediff.raw_id, filediff.patch['filename'])}",
1116 1116 text:"${filediff.patch['filename']}",
1117 1117 ops:${h.str_json(filediff.patch['stats'])|n}}${('' if loop.last else ',')}
1118 1118 % endfor
1119 1119 ]
1120 1120 };
1121 1121
1122 1122 var diffFileFilterId = "#file_filter_" + "${diffset_container_id}";
1123 1123 var diffFileFilter = $(diffFileFilterId).select2({
1124 1124 'dropdownAutoWidth': true,
1125 1125 'width': 'auto',
1126 1126
1127 1127 containerCssClass: "drop-menu",
1128 1128 dropdownCssClass: "drop-menu-dropdown",
1129 1129 data: preloadFileFilterData["${diffset_container_id}"],
1130 1130 query: function(query) {
1131 1131 feedFilesOptions(query, preloadFileFilterData["${diffset_container_id}"]);
1132 1132 },
1133 1133 initSelection: function(element, callback) {
1134 1134 callback({'init': true});
1135 1135 },
1136 1136 formatResult: formatFileResult,
1137 1137 formatSelection: formatSelection
1138 1138 });
1139 1139
1140 1140 % if range_diff_on is True:
1141 1141 diffFileFilter.select2("enable", false);
1142 1142 % endif
1143 1143
1144 1144 $(diffFileFilterId).on('select2-selecting', function (e) {
1145 1145 var idSelector = e.choice.id;
1146 1146
1147 1147 // expand the container if we quick-select the field
1148 1148 $('#'+idSelector).next().prop('checked', false);
1149 1149 // hide the mast as we later do preventDefault()
1150 1150 $("#select2-drop-mask").click();
1151 1151
1152 1152 window.location.hash = '#'+idSelector;
1153 1153 updateSticky();
1154 1154
1155 1155 e.preventDefault();
1156 1156 });
1157 1157
1158 1158 diffNavText = 'diff navigation:'
1159 1159
1160 1160 getCurrentChunk = function () {
1161 1161
1162 1162 var chunksAll = $('.nav-chunk').filter(function () {
1163 1163 return $(this).parents('.filediff').prev().get(0).checked !== true
1164 1164 })
1165 1165 var chunkSelected = $('.nav-chunk.selected');
1166 1166 var initial = false;
1167 1167
1168 1168 if (chunkSelected.length === 0) {
1169 1169 // no initial chunk selected, we pick first
1170 1170 chunkSelected = $(chunksAll.get(0));
1171 1171 var initial = true;
1172 1172 }
1173 1173
1174 1174 return {
1175 1175 'all': chunksAll,
1176 1176 'selected': chunkSelected,
1177 1177 'initial': initial,
1178 1178 }
1179 1179 }
1180 1180
1181 1181 animateDiffNavText = function () {
1182 1182 var $diffNav = $('#diff_nav')
1183 1183
1184 1184 var callback = function () {
1185 1185 $diffNav.animate({'opacity': 1.00}, 200)
1186 1186 };
1187 1187 $diffNav.animate({'opacity': 0.15}, 200, callback);
1188 1188 }
1189 1189
1190 1190 scrollToChunk = function (moveBy) {
1191 1191 var chunk = getCurrentChunk();
1192 1192 var all = chunk.all
1193 1193 var selected = chunk.selected
1194 1194
1195 1195 var curPos = all.index(selected);
1196 1196 var newPos = curPos;
1197 1197 if (!chunk.initial) {
1198 1198 var newPos = curPos + moveBy;
1199 1199 }
1200 1200
1201 1201 var curElem = all.get(newPos);
1202 1202
1203 1203 if (curElem === undefined) {
1204 1204 // end or back
1205 1205 $('#diff_nav').html('no next diff element:')
1206 1206 animateDiffNavText()
1207 1207 return
1208 1208 } else if (newPos < 0) {
1209 1209 $('#diff_nav').html('no previous diff element:')
1210 1210 animateDiffNavText()
1211 1211 return
1212 1212 } else {
1213 1213 $('#diff_nav').html(diffNavText)
1214 1214 }
1215 1215
1216 1216 curElem = $(curElem)
1217 1217 var offset = 100;
1218 1218 $(window).scrollTop(curElem.position().top - offset);
1219 1219
1220 1220 //clear selection
1221 1221 all.removeClass('selected')
1222 1222 curElem.addClass('selected')
1223 1223 }
1224 1224
1225 1225 scrollToPrevChunk = function () {
1226 1226 scrollToChunk(-1)
1227 1227 }
1228 1228 scrollToNextChunk = function () {
1229 1229 scrollToChunk(1)
1230 1230 }
1231 1231
1232 1232 </script>
1233 1233 % endif
1234 1234
1235 1235 <script type="text/javascript">
1236 1236 $('#diff_nav').html('loading diff...') // wait until whole page is loaded
1237 1237
1238 1238 $(document).ready(function () {
1239 1239
1240 1240 var contextPrefix = _gettext('Context file: ');
1241 1241 ## sticky sidebar
1242 1242 var sidebarElement = document.getElementById('diff-file-sticky');
1243 1243 sidebar = new StickySidebar(sidebarElement, {
1244 1244 topSpacing: 0,
1245 1245 bottomSpacing: 0,
1246 1246 innerWrapperSelector: '.sidebar__inner'
1247 1247 });
1248 1248 sidebarElement.addEventListener('affixed.static.stickySidebar', function () {
1249 1249 // reset our file so it's not holding new value
1250 1250 $('.fpath-placeholder-text').html(contextPrefix + ' - ')
1251 1251 });
1252 1252
1253 1253 updateSticky = function () {
1254 1254 sidebar.updateSticky();
1255 1255 Waypoint.refreshAll();
1256 1256 };
1257 1257
1258 1258 var animateText = function (fPath, anchorId) {
1259 1259 fPath = Select2.util.escapeMarkup(fPath);
1260 1260 $('.fpath-placeholder-text').html(contextPrefix + '<a href="#a_' + anchorId + '">' + fPath + '</a>')
1261 1261 };
1262 1262
1263 1263 ## dynamic file waypoints
1264 1264 var setFPathInfo = function(fPath, anchorId){
1265 1265 animateText(fPath, anchorId)
1266 1266 };
1267 1267
1268 1268 var codeBlock = $('.filediff');
1269 1269
1270 1270 // forward waypoint
1271 1271 codeBlock.waypoint(
1272 1272 function(direction) {
1273 1273 if (direction === "down"){
1274 1274 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1275 1275 }
1276 1276 }, {
1277 1277 offset: function () {
1278 1278 return 70;
1279 1279 },
1280 1280 context: '.fpath-placeholder'
1281 1281 }
1282 1282 );
1283 1283
1284 1284 // backward waypoint
1285 1285 codeBlock.waypoint(
1286 1286 function(direction) {
1287 1287 if (direction === "up"){
1288 1288 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1289 1289 }
1290 1290 }, {
1291 1291 offset: function () {
1292 1292 return -this.element.clientHeight + 90;
1293 1293 },
1294 1294 context: '.fpath-placeholder'
1295 1295 }
1296 1296 );
1297 1297
1298 1298 toggleWideDiff = function (el) {
1299 1299 updateSticky();
1300 1300 var wide = Rhodecode.comments.toggleWideMode(this);
1301 1301 storeUserSessionAttr('rc_user_session_attr.wide_diff_mode', wide);
1302 1302 if (wide === true) {
1303 1303 $(el).addClass('btn-active');
1304 1304 } else {
1305 1305 $(el).removeClass('btn-active');
1306 1306 }
1307 1307 return null;
1308 1308 };
1309 1309
1310 1310 toggleExpand = function (el, diffsetEl) {
1311 1311 var el = $(el);
1312 1312 if (el.hasClass('collapsed')) {
1313 1313 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', false);
1314 1314 el.removeClass('collapsed');
1315 1315 el.html(
1316 1316 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1317 1317 _gettext('Collapse all files'));
1318 1318 }
1319 1319 else {
1320 1320 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', true);
1321 1321 el.addClass('collapsed');
1322 1322 el.html(
1323 1323 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1324 1324 _gettext('Expand all files'));
1325 1325 }
1326 1326 updateSticky()
1327 1327 };
1328 1328
1329 1329 toggleCommitExpand = function (el) {
1330 1330 var $el = $(el);
1331 1331 var commits = $el.data('toggleCommitsCnt');
1332 1332 var collapseMsg = _ngettext('Collapse {0} commit', 'Collapse {0} commits', commits).format(commits);
1333 1333 var expandMsg = _ngettext('Expand {0} commit', 'Expand {0} commits', commits).format(commits);
1334 1334
1335 1335 if ($el.hasClass('collapsed')) {
1336 1336 $('.compare_select').show();
1337 1337 $('.compare_select_hidden').hide();
1338 1338
1339 1339 $el.removeClass('collapsed');
1340 1340 $el.html(
1341 1341 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1342 1342 collapseMsg);
1343 1343 }
1344 1344 else {
1345 1345 $('.compare_select').hide();
1346 1346 $('.compare_select_hidden').show();
1347 1347 $el.addClass('collapsed');
1348 1348 $el.html(
1349 1349 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1350 1350 expandMsg);
1351 1351 }
1352 1352 updateSticky();
1353 1353 };
1354 1354
1355 1355 // get stored diff mode and pre-enable it
1356 1356 if (templateContext.session_attrs.wide_diff_mode === "true") {
1357 1357 Rhodecode.comments.toggleWideMode(null);
1358 1358 $('.toggle-wide-diff').addClass('btn-active');
1359 1359 updateSticky();
1360 1360 }
1361 1361
1362 1362 // DIFF NAV //
1363 1363
1364 1364 // element to detect scroll direction of
1365 1365 var $window = $(window);
1366 1366
1367 1367 // initialize last scroll position
1368 1368 var lastScrollY = $window.scrollTop();
1369 1369
1370 1370 $window.on('resize scrollstop', {latency: 350}, function () {
1371 1371 var visibleChunks = $('.nav-chunk').withinviewport({top: 75});
1372 1372
1373 1373 // get current scroll position
1374 1374 var currentScrollY = $window.scrollTop();
1375 1375
1376 1376 // determine current scroll direction
1377 1377 if (currentScrollY > lastScrollY) {
1378 1378 var y = 'down'
1379 1379 } else if (currentScrollY !== lastScrollY) {
1380 1380 var y = 'up';
1381 1381 }
1382 1382
1383 1383 var pos = -1; // by default we use last element in viewport
1384 1384 if (y === 'down') {
1385 1385 pos = -1;
1386 1386 } else if (y === 'up') {
1387 1387 pos = 0;
1388 1388 }
1389 1389
1390 1390 if (visibleChunks.length > 0) {
1391 1391 $('.nav-chunk').removeClass('selected');
1392 1392 $(visibleChunks.get(pos)).addClass('selected');
1393 1393 }
1394 1394
1395 1395 // update last scroll position to current position
1396 1396 lastScrollY = currentScrollY;
1397 1397
1398 1398 });
1399 1399 $('#diff_nav').html(diffNavText);
1400 1400
1401 1401 });
1402 1402 </script>
1403 1403
1404 1404 </%def>
General Comments 0
You need to be logged in to leave comments. Login now