##// END OF EJS Templates
optimized speed for browsing git changesets
marcink -
r1959:7a7ffe24 beta
parent child Browse files
Show More
@@ -1,846 +1,870 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 12
13 13 from datetime import datetime
14 14 from pygments.formatters.html import HtmlFormatter
15 15 from pygments import highlight as code_highlight
16 16 from pylons import url, request, config
17 17 from pylons.i18n.translation import _, ungettext
18 18 from hashlib import md5
19 19
20 20 from webhelpers.html import literal, HTML, escape
21 21 from webhelpers.html.tools import *
22 22 from webhelpers.html.builder import make_tag
23 23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 29 from webhelpers.number import format_byte_size, format_bit_size
30 30 from webhelpers.pylonslib import Flash as _Flash
31 31 from webhelpers.pylonslib.secure_form import secure_form
32 32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 35 from webhelpers.date import time_ago_in_words
36 36 from webhelpers.paginate import Page
37 37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39 39
40 40 from rhodecode.lib.annotate import annotate_highlight
41 41 from rhodecode.lib.utils import repo_name_slug
42 42 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
43 43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 49 """
50 50 Reset button
51 51 """
52 52 _set_input_attrs(attrs, type, name, value)
53 53 _set_id_attr(attrs, id, name)
54 54 convert_boolean_attrs(attrs, ["disabled"])
55 55 return HTML.input(**attrs)
56 56
57 57 reset = _reset
58 58 safeid = _make_safe_id_component
59 59
60 60
61 61 def FID(raw_id, path):
62 62 """
63 63 Creates a uniqe ID for filenode based on it's hash of path and revision
64 64 it's safe to use in urls
65 65
66 66 :param raw_id:
67 67 :param path:
68 68 """
69 69
70 70 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
71 71
72 72
73 73 def get_token():
74 74 """Return the current authentication token, creating one if one doesn't
75 75 already exist.
76 76 """
77 77 token_key = "_authentication_token"
78 78 from pylons import session
79 79 if not token_key in session:
80 80 try:
81 81 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
82 82 except AttributeError: # Python < 2.4
83 83 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
84 84 session[token_key] = token
85 85 if hasattr(session, 'save'):
86 86 session.save()
87 87 return session[token_key]
88 88
89 89 class _GetError(object):
90 90 """Get error from form_errors, and represent it as span wrapped error
91 91 message
92 92
93 93 :param field_name: field to fetch errors for
94 94 :param form_errors: form errors dict
95 95 """
96 96
97 97 def __call__(self, field_name, form_errors):
98 98 tmpl = """<span class="error_msg">%s</span>"""
99 99 if form_errors and form_errors.has_key(field_name):
100 100 return literal(tmpl % form_errors.get(field_name))
101 101
102 102 get_error = _GetError()
103 103
104 104 class _ToolTip(object):
105 105
106 106 def __call__(self, tooltip_title, trim_at=50):
107 107 """Special function just to wrap our text into nice formatted
108 108 autowrapped text
109 109
110 110 :param tooltip_title:
111 111 """
112 112 return escape(tooltip_title)
113 113 tooltip = _ToolTip()
114 114
115 115 class _FilesBreadCrumbs(object):
116 116
117 117 def __call__(self, repo_name, rev, paths):
118 118 if isinstance(paths, str):
119 119 paths = safe_unicode(paths)
120 120 url_l = [link_to(repo_name, url('files_home',
121 121 repo_name=repo_name,
122 122 revision=rev, f_path=''))]
123 123 paths_l = paths.split('/')
124 124 for cnt, p in enumerate(paths_l):
125 125 if p != '':
126 126 url_l.append(link_to(p,
127 127 url('files_home',
128 128 repo_name=repo_name,
129 129 revision=rev,
130 130 f_path='/'.join(paths_l[:cnt + 1])
131 131 )
132 132 )
133 133 )
134 134
135 135 return literal('/'.join(url_l))
136 136
137 137 files_breadcrumbs = _FilesBreadCrumbs()
138 138
139 139 class CodeHtmlFormatter(HtmlFormatter):
140 140 """My code Html Formatter for source codes
141 141 """
142 142
143 143 def wrap(self, source, outfile):
144 144 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
145 145
146 146 def _wrap_code(self, source):
147 147 for cnt, it in enumerate(source):
148 148 i, t = it
149 149 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
150 150 yield i, t
151 151
152 152 def _wrap_tablelinenos(self, inner):
153 153 dummyoutfile = StringIO.StringIO()
154 154 lncount = 0
155 155 for t, line in inner:
156 156 if t:
157 157 lncount += 1
158 158 dummyoutfile.write(line)
159 159
160 160 fl = self.linenostart
161 161 mw = len(str(lncount + fl - 1))
162 162 sp = self.linenospecial
163 163 st = self.linenostep
164 164 la = self.lineanchors
165 165 aln = self.anchorlinenos
166 166 nocls = self.noclasses
167 167 if sp:
168 168 lines = []
169 169
170 170 for i in range(fl, fl + lncount):
171 171 if i % st == 0:
172 172 if i % sp == 0:
173 173 if aln:
174 174 lines.append('<a href="#%s%d" class="special">%*d</a>' %
175 175 (la, i, mw, i))
176 176 else:
177 177 lines.append('<span class="special">%*d</span>' % (mw, i))
178 178 else:
179 179 if aln:
180 180 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
181 181 else:
182 182 lines.append('%*d' % (mw, i))
183 183 else:
184 184 lines.append('')
185 185 ls = '\n'.join(lines)
186 186 else:
187 187 lines = []
188 188 for i in range(fl, fl + lncount):
189 189 if i % st == 0:
190 190 if aln:
191 191 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
192 192 else:
193 193 lines.append('%*d' % (mw, i))
194 194 else:
195 195 lines.append('')
196 196 ls = '\n'.join(lines)
197 197
198 198 # in case you wonder about the seemingly redundant <div> here: since the
199 199 # content in the other cell also is wrapped in a div, some browsers in
200 200 # some configurations seem to mess up the formatting...
201 201 if nocls:
202 202 yield 0, ('<table class="%stable">' % self.cssclass +
203 203 '<tr><td><div class="linenodiv" '
204 204 'style="background-color: #f0f0f0; padding-right: 10px">'
205 205 '<pre style="line-height: 125%">' +
206 206 ls + '</pre></div></td><td id="hlcode" class="code">')
207 207 else:
208 208 yield 0, ('<table class="%stable">' % self.cssclass +
209 209 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
210 210 ls + '</pre></div></td><td id="hlcode" class="code">')
211 211 yield 0, dummyoutfile.getvalue()
212 212 yield 0, '</td></tr></table>'
213 213
214 214
215 215 def pygmentize(filenode, **kwargs):
216 216 """pygmentize function using pygments
217 217
218 218 :param filenode:
219 219 """
220 220
221 221 return literal(code_highlight(filenode.content,
222 222 filenode.lexer, CodeHtmlFormatter(**kwargs)))
223 223
224 224
225 225 def pygmentize_annotation(repo_name, filenode, **kwargs):
226 226 """
227 227 pygmentize function for annotation
228 228
229 229 :param filenode:
230 230 """
231 231
232 232 color_dict = {}
233 233
234 234 def gen_color(n=10000):
235 235 """generator for getting n of evenly distributed colors using
236 236 hsv color and golden ratio. It always return same order of colors
237 237
238 238 :returns: RGB tuple
239 239 """
240 240
241 241 def hsv_to_rgb(h, s, v):
242 242 if s == 0.0:
243 243 return v, v, v
244 244 i = int(h * 6.0) # XXX assume int() truncates!
245 245 f = (h * 6.0) - i
246 246 p = v * (1.0 - s)
247 247 q = v * (1.0 - s * f)
248 248 t = v * (1.0 - s * (1.0 - f))
249 249 i = i % 6
250 250 if i == 0:
251 251 return v, t, p
252 252 if i == 1:
253 253 return q, v, p
254 254 if i == 2:
255 255 return p, v, t
256 256 if i == 3:
257 257 return p, q, v
258 258 if i == 4:
259 259 return t, p, v
260 260 if i == 5:
261 261 return v, p, q
262 262
263 263 golden_ratio = 0.618033988749895
264 264 h = 0.22717784590367374
265 265
266 266 for _ in xrange(n):
267 267 h += golden_ratio
268 268 h %= 1
269 269 HSV_tuple = [h, 0.95, 0.95]
270 270 RGB_tuple = hsv_to_rgb(*HSV_tuple)
271 271 yield map(lambda x: str(int(x * 256)), RGB_tuple)
272 272
273 273 cgenerator = gen_color()
274 274
275 275 def get_color_string(cs):
276 276 if cs in color_dict:
277 277 col = color_dict[cs]
278 278 else:
279 279 col = color_dict[cs] = cgenerator.next()
280 280 return "color: rgb(%s)! important;" % (', '.join(col))
281 281
282 282 def url_func(repo_name):
283 283
284 284 def _url_func(changeset):
285 285 author = changeset.author
286 286 date = changeset.date
287 287 message = tooltip(changeset.message)
288 288
289 289 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
290 290 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
291 291 "</b> %s<br/></div>")
292 292
293 293 tooltip_html = tooltip_html % (author, date, message)
294 294 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
295 295 short_id(changeset.raw_id))
296 296 uri = link_to(
297 297 lnk_format,
298 298 url('changeset_home', repo_name=repo_name,
299 299 revision=changeset.raw_id),
300 300 style=get_color_string(changeset.raw_id),
301 301 class_='tooltip',
302 302 title=tooltip_html
303 303 )
304 304
305 305 uri += '\n'
306 306 return uri
307 307 return _url_func
308 308
309 309 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
310 310
311 311
312 312 def is_following_repo(repo_name, user_id):
313 313 from rhodecode.model.scm import ScmModel
314 314 return ScmModel().is_following_repo(repo_name, user_id)
315 315
316 316 flash = _Flash()
317 317
318 318 #==============================================================================
319 319 # SCM FILTERS available via h.
320 320 #==============================================================================
321 321 from vcs.utils import author_name, author_email
322 322 from rhodecode.lib import credentials_filter, age as _age
323 323 from rhodecode.model.db import User
324 324
325 325 age = lambda x: _age(x)
326 326 capitalize = lambda x: x.capitalize()
327 327 email = author_email
328 328 short_id = lambda x: x[:12]
329 329 hide_credentials = lambda x: ''.join(credentials_filter(x))
330 330
331 331
332 def is_git(repository):
333 if hasattr(repository, 'alias'):
334 _type = repository.alias
335 elif hasattr(repository, 'repo_type'):
336 _type = repository.repo_type
337 else:
338 _type = repository
339 return _type == 'git'
340
341
342 def is_hg(repository):
343 if hasattr(repository, 'alias'):
344 _type = repository.alias
345 elif hasattr(repository, 'repo_type'):
346 _type = repository.repo_type
347 else:
348 _type = repository
349 return _type == 'hg'
350
351
332 352 def email_or_none(author):
333 353 _email = email(author)
334 354 if _email != '':
335 355 return _email
336 356
337 357 # See if it contains a username we can get an email from
338 358 user = User.get_by_username(author_name(author), case_insensitive=True,
339 359 cache=True)
340 360 if user is not None:
341 361 return user.email
342 362
343 363 # No valid email, not a valid user in the system, none!
344 364 return None
345 365
346 366
347 367 def person(author):
348 368 # attr to return from fetched user
349 369 person_getter = lambda usr: usr.username
350 370
351 371 # Valid email in the attribute passed, see if they're in the system
352 372 _email = email(author)
353 373 if _email != '':
354 374 user = User.get_by_email(_email, case_insensitive=True, cache=True)
355 375 if user is not None:
356 376 return person_getter(user)
357 377 return _email
358 378
359 379 # Maybe it's a username?
360 380 _author = author_name(author)
361 381 user = User.get_by_username(_author, case_insensitive=True,
362 382 cache=True)
363 383 if user is not None:
364 384 return person_getter(user)
365 385
366 386 # Still nothing? Just pass back the author name then
367 387 return _author
368 388
389
369 390 def bool2icon(value):
370 391 """Returns True/False values represented as small html image of true/false
371 392 icons
372 393
373 394 :param value: bool value
374 395 """
375 396
376 397 if value is True:
377 398 return HTML.tag('img', src=url("/images/icons/accept.png"),
378 399 alt=_('True'))
379 400
380 401 if value is False:
381 402 return HTML.tag('img', src=url("/images/icons/cancel.png"),
382 403 alt=_('False'))
383 404
384 405 return value
385 406
386 407
387 408 def action_parser(user_log, feed=False):
388 409 """This helper will action_map the specified string action into translated
389 410 fancy names with icons and links
390 411
391 412 :param user_log: user log instance
392 413 :param feed: use output for feeds (no html and fancy icons)
393 414 """
394 415
395 416 action = user_log.action
396 417 action_params = ' '
397 418
398 419 x = action.split(':')
399 420
400 421 if len(x) > 1:
401 422 action, action_params = x
402 423
403 424 def get_cs_links():
404 425 revs_limit = 3 #display this amount always
405 426 revs_top_limit = 50 #show upto this amount of changesets hidden
406 427 revs = action_params.split(',')
407 428 repo_name = user_log.repository.repo_name
408 429
409 430 from rhodecode.model.scm import ScmModel
410 431 repo = user_log.repository.scm_instance
411 432
412 433 message = lambda rev: get_changeset_safe(repo, rev).message
413 434 cs_links = []
414 435 cs_links.append(" " + ', '.join ([link_to(rev,
415 436 url('changeset_home',
416 437 repo_name=repo_name,
417 438 revision=rev), title=tooltip(message(rev)),
418 439 class_='tooltip') for rev in revs[:revs_limit] ]))
419 440
420 441 compare_view = (' <div class="compare_view tooltip" title="%s">'
421 442 '<a href="%s">%s</a> '
422 443 '</div>' % (_('Show all combined changesets %s->%s' \
423 444 % (revs[0], revs[-1])),
424 445 url('changeset_home', repo_name=repo_name,
425 446 revision='%s...%s' % (revs[0], revs[-1])
426 447 ),
427 448 _('compare view'))
428 449 )
429 450
430 451 if len(revs) > revs_limit:
431 452 uniq_id = revs[0]
432 453 html_tmpl = ('<span> %s '
433 454 '<a class="show_more" id="_%s" href="#more">%s</a> '
434 455 '%s</span>')
435 456 if not feed:
436 457 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
437 458 % (len(revs) - revs_limit),
438 459 _('revisions')))
439 460
440 461 if not feed:
441 462 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 463 else:
443 464 html_tmpl = '<span id="%s"> %s </span>'
444 465
445 466 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
446 467 url('changeset_home',
447 468 repo_name=repo_name, revision=rev),
448 469 title=message(rev), class_='tooltip')
449 470 for rev in revs[revs_limit:revs_top_limit]])))
450 471 if len(revs) > 1:
451 472 cs_links.append(compare_view)
452 473 return ''.join(cs_links)
453 474
454 475 def get_fork_name():
455 476 repo_name = action_params
456 477 return _('fork name ') + str(link_to(action_params, url('summary_home',
457 478 repo_name=repo_name,)))
458 479
459 480 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
460 481 'user_created_repo':(_('[created] repository'), None),
461 482 'user_created_fork':(_('[created] repository as fork'), None),
462 483 'user_forked_repo':(_('[forked] repository'), get_fork_name),
463 484 'user_updated_repo':(_('[updated] repository'), None),
464 485 'admin_deleted_repo':(_('[delete] repository'), None),
465 486 'admin_created_repo':(_('[created] repository'), None),
466 487 'admin_forked_repo':(_('[forked] repository'), None),
467 488 'admin_updated_repo':(_('[updated] repository'), None),
468 489 'push':(_('[pushed] into'), get_cs_links),
469 490 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
470 491 'push_remote':(_('[pulled from remote] into'), get_cs_links),
471 492 'pull':(_('[pulled] from'), None),
472 493 'started_following_repo':(_('[started following] repository'), None),
473 494 'stopped_following_repo':(_('[stopped following] repository'), None),
474 495 }
475 496
476 497 action_str = action_map.get(action, action)
477 498 if feed:
478 499 action = action_str[0].replace('[', '').replace(']', '')
479 500 else:
480 501 action = action_str[0].replace('[', '<span class="journal_highlight">')\
481 502 .replace(']', '</span>')
482 503
483 504 action_params_func = lambda :""
484 505
485 506 if callable(action_str[1]):
486 507 action_params_func = action_str[1]
487 508
488 509 return [literal(action), action_params_func]
489 510
511
490 512 def action_parser_icon(user_log):
491 513 action = user_log.action
492 514 action_params = None
493 515 x = action.split(':')
494 516
495 517 if len(x) > 1:
496 518 action, action_params = x
497 519
498 520 tmpl = """<img src="%s%s" alt="%s"/>"""
499 521 map = {'user_deleted_repo':'database_delete.png',
500 522 'user_created_repo':'database_add.png',
501 523 'user_created_fork':'arrow_divide.png',
502 524 'user_forked_repo':'arrow_divide.png',
503 525 'user_updated_repo':'database_edit.png',
504 526 'admin_deleted_repo':'database_delete.png',
505 527 'admin_created_repo':'database_add.png',
506 528 'admin_forked_repo':'arrow_divide.png',
507 529 'admin_updated_repo':'database_edit.png',
508 530 'push':'script_add.png',
509 531 'push_local':'script_edit.png',
510 532 'push_remote':'connect.png',
511 533 'pull':'down_16.png',
512 534 'started_following_repo':'heart_add.png',
513 535 'stopped_following_repo':'heart_delete.png',
514 536 }
515 537 return literal(tmpl % ((url('/images/icons/')),
516 538 map.get(action, action), action))
517 539
518 540
519 541 #==============================================================================
520 542 # PERMS
521 543 #==============================================================================
522 544 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
523 545 HasRepoPermissionAny, HasRepoPermissionAll
524 546
547
525 548 #==============================================================================
526 549 # GRAVATAR URL
527 550 #==============================================================================
528 551
529 552 def gravatar_url(email_address, size=30):
530 553 if (not str2bool(config['app_conf'].get('use_gravatar')) or
531 554 not email_address or email_address == 'anonymous@rhodecode.org'):
532 555 f=lambda a,l:min(l,key=lambda x:abs(x-a))
533 556 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
534 557
535 558 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
536 559 default = 'identicon'
537 560 baseurl_nossl = "http://www.gravatar.com/avatar/"
538 561 baseurl_ssl = "https://secure.gravatar.com/avatar/"
539 562 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
540 563
541 564 if isinstance(email_address, unicode):
542 565 #hashlib crashes on unicode items
543 566 email_address = safe_str(email_address)
544 567 # construct the url
545 568 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
546 569 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
547 570
548 571 return gravatar_url
549 572
550 573
551 574 #==============================================================================
552 575 # REPO PAGER, PAGER FOR REPOSITORY
553 576 #==============================================================================
554 577 class RepoPage(Page):
555 578
556 579 def __init__(self, collection, page=1, items_per_page=20,
557 580 item_count=None, url=None, **kwargs):
558 581
559 582 """Create a "RepoPage" instance. special pager for paging
560 583 repository
561 584 """
562 585 self._url_generator = url
563 586
564 587 # Safe the kwargs class-wide so they can be used in the pager() method
565 588 self.kwargs = kwargs
566 589
567 590 # Save a reference to the collection
568 591 self.original_collection = collection
569 592
570 593 self.collection = collection
571 594
572 595 # The self.page is the number of the current page.
573 596 # The first page has the number 1!
574 597 try:
575 598 self.page = int(page) # make it int() if we get it as a string
576 599 except (ValueError, TypeError):
577 600 self.page = 1
578 601
579 602 self.items_per_page = items_per_page
580 603
581 604 # Unless the user tells us how many items the collections has
582 605 # we calculate that ourselves.
583 606 if item_count is not None:
584 607 self.item_count = item_count
585 608 else:
586 609 self.item_count = len(self.collection)
587 610
588 611 # Compute the number of the first and last available page
589 612 if self.item_count > 0:
590 613 self.first_page = 1
591 614 self.page_count = int(math.ceil(float(self.item_count) /
592 615 self.items_per_page))
593 616 self.last_page = self.first_page + self.page_count - 1
594 617
595 618 # Make sure that the requested page number is the range of
596 619 # valid pages
597 620 if self.page > self.last_page:
598 621 self.page = self.last_page
599 622 elif self.page < self.first_page:
600 623 self.page = self.first_page
601 624
602 625 # Note: the number of items on this page can be less than
603 626 # items_per_page if the last page is not full
604 627 self.first_item = max(0, (self.item_count) - (self.page *
605 628 items_per_page))
606 629 self.last_item = ((self.item_count - 1) - items_per_page *
607 630 (self.page - 1))
608 631
609 632 self.items = list(self.collection[self.first_item:self.last_item + 1])
610 633
611 634 # Links to previous and next page
612 635 if self.page > self.first_page:
613 636 self.previous_page = self.page - 1
614 637 else:
615 638 self.previous_page = None
616 639
617 640 if self.page < self.last_page:
618 641 self.next_page = self.page + 1
619 642 else:
620 643 self.next_page = None
621 644
622 645 # No items available
623 646 else:
624 647 self.first_page = None
625 648 self.page_count = 0
626 649 self.last_page = None
627 650 self.first_item = None
628 651 self.last_item = None
629 652 self.previous_page = None
630 653 self.next_page = None
631 654 self.items = []
632 655
633 656 # This is a subclass of the 'list' type. Initialise the list now.
634 657 list.__init__(self, reversed(self.items))
635 658
636 659
637 660 def changed_tooltip(nodes):
638 661 """
639 662 Generates a html string for changed nodes in changeset page.
640 663 It limits the output to 30 entries
641 664
642 665 :param nodes: LazyNodesGenerator
643 666 """
644 667 if nodes:
645 668 pref = ': <br/> '
646 669 suf = ''
647 670 if len(nodes) > 30:
648 671 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
649 672 return literal(pref + '<br/> '.join([safe_unicode(x.path)
650 673 for x in nodes[:30]]) + suf)
651 674 else:
652 675 return ': ' + _('No Files')
653 676
654 677
655
656 678 def repo_link(groups_and_repos):
657 679 """
658 680 Makes a breadcrumbs link to repo within a group
659 681 joins &raquo; on each group to create a fancy link
660 682
661 683 ex::
662 684 group >> subgroup >> repo
663 685
664 686 :param groups_and_repos:
665 687 """
666 688 groups, repo_name = groups_and_repos
667 689
668 690 if not groups:
669 691 return repo_name
670 692 else:
671 693 def make_link(group):
672 694 return link_to(group.name, url('repos_group_home',
673 695 group_name=group.group_name))
674 696 return literal(' &raquo; '.join(map(make_link, groups)) + \
675 697 " &raquo; " + repo_name)
676 698
699
677 700 def fancy_file_stats(stats):
678 701 """
679 702 Displays a fancy two colored bar for number of added/deleted
680 703 lines of code on file
681 704
682 705 :param stats: two element list of added/deleted lines of code
683 706 """
684 707
685 708 a, d, t = stats[0], stats[1], stats[0] + stats[1]
686 709 width = 100
687 710 unit = float(width) / (t or 1)
688 711
689 712 # needs > 9% of width to be visible or 0 to be hidden
690 713 a_p = max(9, unit * a) if a > 0 else 0
691 714 d_p = max(9, unit * d) if d > 0 else 0
692 715 p_sum = a_p + d_p
693 716
694 717 if p_sum > width:
695 718 #adjust the percentage to be == 100% since we adjusted to 9
696 719 if a_p > d_p:
697 720 a_p = a_p - (p_sum - width)
698 721 else:
699 722 d_p = d_p - (p_sum - width)
700 723
701 724 a_v = a if a > 0 else ''
702 725 d_v = d if d > 0 else ''
703 726
704
705 727 def cgen(l_type):
706 mapping = {'tr':'top-right-rounded-corner',
707 'tl':'top-left-rounded-corner',
708 'br':'bottom-right-rounded-corner',
709 'bl':'bottom-left-rounded-corner'}
710 map_getter = lambda x:mapping[x]
728 mapping = {'tr': 'top-right-rounded-corner',
729 'tl': 'top-left-rounded-corner',
730 'br': 'bottom-right-rounded-corner',
731 'bl': 'bottom-left-rounded-corner'}
732 map_getter = lambda x: mapping[x]
711 733
712 734 if l_type == 'a' and d_v:
713 735 #case when added and deleted are present
714 736 return ' '.join(map(map_getter, ['tl', 'bl']))
715 737
716 738 if l_type == 'a' and not d_v:
717 739 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
718 740
719 741 if l_type == 'd' and a_v:
720 742 return ' '.join(map(map_getter, ['tr', 'br']))
721 743
722 744 if l_type == 'd' and not a_v:
723 745 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
724 746
725
726
727 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
728 a_p, a_v)
729 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
730 d_p, d_v)
747 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
748 cgen('a'),a_p, a_v
749 )
750 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
751 cgen('d'),d_p, d_v
752 )
731 753 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
732 754
733 755
734 756 def urlify_text(text_):
735 757 import re
736 758
737 759 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
738 760 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
739 761
740 762 def url_func(match_obj):
741 763 url_full = match_obj.groups()[0]
742 764 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
743 765
744 766 return literal(url_pat.sub(url_func, text_))
745 767
768
746 769 def urlify_changesets(text_, repository):
747 770 import re
748 771 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
749 772
750 773 def url_func(match_obj):
751 774 rev = match_obj.groups()[0]
752 775 pref = ''
753 776 if match_obj.group().startswith(' '):
754 777 pref = ' '
755 778 tmpl = (
756 779 '%(pref)s<a class="%(cls)s" href="%(url)s">'
757 780 '%(rev)s'
758 781 '</a>'
759 782 )
760 783 return tmpl % {
761 784 'pref': pref,
762 785 'cls': 'revision-link',
763 786 'url': url('changeset_home', repo_name=repository, revision=rev),
764 787 'rev': rev,
765 788 }
766 789
767 790 newtext = URL_PAT.sub(url_func, text_)
768 791
769 792 return newtext
770 793
794
771 795 def urlify_commit(text_, repository=None, link_=None):
772 796 import re
773 797 import traceback
774 798
775 799 # urlify changesets
776 800 text_ = urlify_changesets(text_, repository)
777 801
778 802 def linkify_others(t,l):
779 803 urls = re.compile(r'(\<a.*?\<\/a\>)',)
780 804 links = []
781 805 for e in urls.split(t):
782 806 if not urls.match(e):
783 807 links.append('<a class="message-link" href="%s">%s</a>' % (l,e))
784 808 else:
785 809 links.append(e)
786 810
787 811 return ''.join(links)
788 812 try:
789 813 conf = config['app_conf']
790 814
791 815 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
792 816
793 817 if URL_PAT:
794 818 ISSUE_SERVER_LNK = conf.get('issue_server_link')
795 819 ISSUE_PREFIX = conf.get('issue_prefix')
796 820
797 821 def url_func(match_obj):
798 822 pref = ''
799 823 if match_obj.group().startswith(' '):
800 824 pref = ' '
801 825
802 826 issue_id = ''.join(match_obj.groups())
803 827 tmpl = (
804 828 '%(pref)s<a class="%(cls)s" href="%(url)s">'
805 829 '%(issue-prefix)s%(id-repr)s'
806 830 '</a>'
807 831 )
808 832 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
809 833 if repository:
810 834 url = url.replace('{repo}', repository)
811 835
812 836 return tmpl % {
813 837 'pref': pref,
814 838 'cls': 'issue-tracker-link',
815 839 'url': url,
816 840 'id-repr': issue_id,
817 841 'issue-prefix': ISSUE_PREFIX,
818 842 'serv': ISSUE_SERVER_LNK,
819 843 }
820 844
821 845 newtext = URL_PAT.sub(url_func, text_)
822 846
823 847 # wrap not links into final link => link_
824 848 newtext = linkify_others(newtext, link_)
825 849
826 850 return literal(newtext)
827 851 except:
828 852 log.error(traceback.format_exc())
829 853 pass
830 854
831 855 return text_
832 856
833 857
834 858 def rst(source):
835 859 return literal('<div class="rst-block">%s</div>' %
836 860 MarkupRenderer.rst(source))
837 861
838 862
839 863 def rst_w_mentions(source):
840 864 """
841 865 Wrapped rst renderer with @mention highlighting
842 866
843 867 :param source:
844 868 """
845 869 return literal('<div class="rst-block">%s</div>' %
846 870 MarkupRenderer.rst_with_mentions(source))
@@ -1,78 +1,78 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
4 4
5 5 <%def name="quick_menu(repo_name)">
6 6 <ul class="menu_items hidden">
7 7 <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li>
8 8 <li>
9 9 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
10 10 <span class="icon">
11 11 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
12 12 </span>
13 13 <span>${_('Summary')}</span>
14 14 </a>
15 15 </li>
16 16 <li>
17 17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 18 <span class="icon">
19 19 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
20 20 </span>
21 21 <span>${_('Changelog')}</span>
22 22 </a>
23 23 </li>
24 24 <li>
25 25 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
26 26 <span class="icon">
27 27 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
28 28 </span>
29 29 <span>${_('Files')}</span>
30 30 </a>
31 31 </li>
32 32 <li>
33 33 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
34 34 <span class="icon">
35 35 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Fork')}" />
36 36 </span>
37 37 <span>${_('Fork')}</span>
38 38 </a>
39 39 </li>
40 40 </ul>
41 41 </%def>
42 42
43 43 <%def name="repo_name(name,rtype,private,fork_of)">
44 44 <div style="white-space: nowrap">
45 45 ##TYPE OF REPO
46 %if rtype =='hg':
46 %if h.is_hg(rtype):
47 47 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
48 %elif rtype =='git':
48 %elif h.is_git(rtype):
49 49 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
50 50 %endif
51 51
52 52 ##PRIVATE/PUBLIC
53 53 %if private:
54 54 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
55 55 %else:
56 56 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
57 57 %endif
58 58
59 59 ##NAME
60 60 ${h.link_to(name,h.url('summary_home',repo_name=name),class_="repo_name")}
61 61 %if fork_of:
62 62 <a href="${h.url('summary_home',repo_name=fork_of)}">
63 63 <img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
64 64 %endif
65 65 </div>
66 66 </%def>
67 67
68 68
69 69
70 70 <%def name="revision(name,rev,tip,author,last_msg)">
71 71 <div>
72 72 %if rev >= 0:
73 73 <pre><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></pre>
74 74 %else:
75 75 ${_('No changesets yet')}
76 76 %endif
77 77 </div>
78 78 </%def>
@@ -1,230 +1,230 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(u'Home',h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27 <div class="table">
28 28 % if c.pagination:
29 29 <div id="graph">
30 30 <div id="graph_nodes">
31 31 <canvas id="graph_canvas"></canvas>
32 32 </div>
33 33 <div id="graph_content">
34 34 <div class="container_header">
35 35 ${h.form(h.url.current(),method='get')}
36 36 <div class="info_box" style="float:left">
37 37 ${h.submit('set',_('Show'),class_="ui-btn")}
38 38 ${h.text('size',size=1,value=c.size)}
39 39 ${_('revisions')}
40 40 </div>
41 41 ${h.end_form()}
42 42 <div id="rev_range_container" style="display:none"></div>
43 43 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
44 44 </div>
45 45
46 46 %for cnt,cs in enumerate(c.pagination):
47 47 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
48 48 <div class="left">
49 49 <div>
50 50 ${h.checkbox(cs.short_id,class_="changeset_range")}
51 51 <span class="tooltip" title="${h.age(cs.date)}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
52 52 </div>
53 53 <div class="author">
54 54 <div class="gravatar">
55 55 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
56 56 </div>
57 57 <div title="${cs.author}" class="user">${h.person(cs.author)}</div>
58 58 </div>
59 59 <div class="date">${cs.date}</div>
60 60 </div>
61 61 <div class="mid">
62 62 <div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
63 63 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
64 64 </div>
65 65 <div class="right">
66 66 <div id="${cs.raw_id}_changes_info" class="changes">
67 67 <div id="${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</div>
68 68 <div class="comments-container">
69 69 %if len(c.comments.get(cs.raw_id,[])) > 0:
70 70 <div class="comments-cnt" title="${('comments')}">
71 71 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
72 72 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
73 73 <img src="${h.url('/images/icons/comments.png')}">
74 74 </a>
75 75 </div>
76 76 %endif
77 77 </div>
78 78 </div>
79 79 %if cs.parents:
80 80 %for p_cs in reversed(cs.parents):
81 81 <div class="parent">${_('Parent')}
82 82 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
83 83 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
84 84 </div>
85 85 %endfor
86 86 %else:
87 87 <div class="parent">${_('No parents')}</div>
88 88 %endif
89 89
90 90 <span class="logtags">
91 91 %if len(cs.parents)>1:
92 92 <span class="merge">${_('merge')}</span>
93 93 %endif
94 %if cs.branch:
94 %if h.is_hg(c.rhodecode_repo) and cs.branch:
95 95 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
96 96 ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
97 97 %endif
98 98 %for tag in cs.tags:
99 99 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
100 100 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
101 101 %endfor
102 102 </span>
103 103 </div>
104 104 </div>
105 105
106 106 %endfor
107 107 <div class="pagination-wh pagination-left">
108 108 ${c.pagination.pager('$link_previous ~2~ $link_next')}
109 109 </div>
110 110 </div>
111 111 </div>
112 112
113 113 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
114 114 <script type="text/javascript">
115 115 YAHOO.util.Event.onDOMReady(function(){
116 116
117 117 //Monitor range checkboxes and build a link to changesets
118 118 //ranges
119 119 var checkboxes = YUD.getElementsByClassName('changeset_range');
120 120 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
121 121 YUE.on(checkboxes,'click',function(e){
122 122 var checked_checkboxes = [];
123 123 for (pos in checkboxes){
124 124 if(checkboxes[pos].checked){
125 125 checked_checkboxes.push(checkboxes[pos]);
126 126 }
127 127 }
128 128 if(checked_checkboxes.length>1){
129 129 var rev_end = checked_checkboxes[0].name;
130 130 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
131 131
132 132 var url = url_tmpl.replace('__REVRANGE__',
133 133 rev_start+'...'+rev_end);
134 134
135 135 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
136 136 link = link.replace('__S',rev_start);
137 137 link = link.replace('__E',rev_end);
138 138 YUD.get('rev_range_container').innerHTML = link;
139 139 YUD.setStyle('rev_range_container','display','');
140 140 }
141 141 else{
142 142 YUD.setStyle('rev_range_container','display','none');
143 143
144 144 }
145 145 });
146 146
147 147 var msgs = YUQ('.message');
148 148 // get first element height
149 149 var el = YUQ('#graph_content .container')[0];
150 150 var row_h = el.clientHeight;
151 151 for(var i=0;i<msgs.length;i++){
152 152 var m = msgs[i];
153 153
154 154 var h = m.clientHeight;
155 155 var pad = YUD.getStyle(m,'padding');
156 156 if(h > row_h){
157 157 var offset = row_h - (h+12);
158 158 YUD.setStyle(m.nextElementSibling,'display','block');
159 159 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
160 160 };
161 161 }
162 162 YUE.on(YUQ('.expand'),'click',function(e){
163 163 var elem = e.currentTarget.parentNode.parentNode;
164 164 YUD.setStyle(e.currentTarget,'display','none');
165 165 YUD.setStyle(elem,'height','auto');
166 166
167 167 //redraw the graph, max_w and jsdata are global vars
168 168 set_canvas(max_w);
169 169
170 170 var r = new BranchRenderer();
171 171 r.render(jsdata,max_w);
172 172
173 173 })
174 174
175 175 // Fetch changeset details
176 176 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
177 177 var id = e.currentTarget.id
178 178 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
179 179 var url = url.replace('__CS__',id);
180 180 ypjax(url,id+'_changes_info',function(){tooltip_activate()});
181 181 });
182 182
183 183 // change branch filter
184 184 YUE.on(YUD.get('branch_filter'),'change',function(e){
185 185 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
186 186 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
187 187 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
188 188 var url = url.replace('__BRANCH__',selected_branch);
189 189 if(selected_branch != ''){
190 190 window.location = url;
191 191 }else{
192 192 window.location = url_main;
193 193 }
194 194
195 195 });
196 196
197 197 function set_canvas(heads) {
198 198 var c = document.getElementById('graph_nodes');
199 199 var t = document.getElementById('graph_content');
200 200 canvas = document.getElementById('graph_canvas');
201 201 var div_h = t.clientHeight;
202 202 c.style.height=div_h+'px';
203 203 canvas.setAttribute('height',div_h);
204 204 c.style.height=max_w+'px';
205 205 canvas.setAttribute('width',max_w);
206 206 };
207 207 var heads = 1;
208 208 var max_heads = 0;
209 209 var jsdata = ${c.jsdata|n};
210 210
211 211 for( var i=0;i<jsdata.length;i++){
212 212 var m = Math.max.apply(Math, jsdata[i][1]);
213 213 if (m>max_heads){
214 214 max_heads = m;
215 215 }
216 216 }
217 217 var max_w = Math.max(100,max_heads*25);
218 218 set_canvas(max_w);
219 219
220 220 var r = new BranchRenderer();
221 221 r.render(jsdata,max_w);
222 222
223 223 });
224 224 </script>
225 225 %else:
226 226 ${_('There are no changes yet')}
227 227 %endif
228 228 </div>
229 229 </div>
230 230 </%def>
@@ -1,220 +1,220 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Journal')} - ${c.rhodecode_name}
5 5 </%def>
6 6 <%def name="breadcrumbs()">
7 7 ${c.rhodecode_name}
8 8 </%def>
9 9 <%def name="page_nav()">
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="main()">
13 13
14 14 <div class="box box-left">
15 15 <!-- box / title -->
16 16 <div class="title">
17 17 <h5>${_('Journal')}</h5>
18 18 <ul class="links">
19 19 <li>
20 20 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
21 21 </a></span>
22 22 </li>
23 23 </ul>
24 24 </div>
25 25 <div id="journal">${c.journal_data}</div>
26 26 </div>
27 27 <div class="box box-right">
28 28 <!-- box / title -->
29 29 <div class="title">
30 30 <h5>
31 31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
32 32 <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a>
33 33 </h5>
34 34 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
35 35 <ul class="links">
36 36 <li>
37 37 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
38 38 </li>
39 39 </ul>
40 40 %endif
41 41 </div>
42 42 <!-- end box / title -->
43 43 <div id="my" class="table">
44 44 %if c.user_repos:
45 45 <div id='repos_list_wrap' class="yui-skin-sam">
46 46 <table id="repos_list">
47 47 <thead>
48 48 <tr>
49 49 <th></th>
50 50 <th class="left">${_('Name')}</th>
51 51 <th class="left">${_('Tip')}</th>
52 52 <th class="left">${_('Action')}</th>
53 53 <th class="left">${_('Action')}</th>
54 54 </thead>
55 55 <tbody>
56 56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
57 57 %for repo in c.user_repos:
58 58 <tr>
59 59 ##QUICK MENU
60 60 <td class="quick_repo_menu">
61 61 ${dt.quick_menu(repo['name'])}
62 62 </td>
63 63 ##REPO NAME AND ICONS
64 64 <td class="reponame">
65 65 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
66 66 </td>
67 67 ##LAST REVISION
68 68 <td>
69 69 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
70 70 </td>
71 71 ##
72 72 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
73 73 <td>
74 74 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
75 75 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
76 76 ${h.end_form()}
77 77 </td>
78 78 </tr>
79 79 %endfor
80 80 </tbody>
81 81 </table>
82 82 </div>
83 83 %else:
84 84 <div style="padding:5px 0px 10px 0px;">
85 85 ${_('No repositories yet')}
86 86 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
87 87 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
88 88 %endif
89 89 </div>
90 90 %endif
91 91 </div>
92 92
93 93 <div id="watched" class="table" style="display:none">
94 94 %if c.following:
95 95 <table>
96 96 <thead>
97 97 <tr>
98 98 <th class="left">${_('Name')}</th>
99 99 </thead>
100 100 <tbody>
101 101 %for entry in c.following:
102 102 <tr>
103 103 <td>
104 104 %if entry.follows_user_id:
105 105 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
106 106 ${entry.follows_user.full_contact}
107 107 %endif
108 108
109 109 %if entry.follows_repo_id:
110 110 <div style="float:right;padding-right:5px">
111 111 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
112 112 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
113 113 </span>
114 114 </div>
115 115
116 %if entry.follows_repository.repo_type == 'hg':
116 %if h.is_hg(entry.follows_repository):
117 117 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
118 %elif entry.follows_repository.repo_type == 'git':
118 %elif h.is_git(entry.follows_repository):
119 119 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
120 120 %endif
121 121
122 122 %if entry.follows_repository.private:
123 123 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
124 124 %else:
125 125 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
126 126 %endif
127 127 <span class="watched_repo">
128 128 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
129 129 </span>
130 130 %endif
131 131 </td>
132 132 </tr>
133 133 %endfor
134 134 </tbody>
135 135 </table>
136 136 %else:
137 137 <div style="padding:5px 0px 10px 0px;">
138 138 ${_('You are not following any users or repositories')}
139 139 </div>
140 140 %endif
141 141 </div>
142 142 </div>
143 143
144 144 <script type="text/javascript">
145 145
146 146 YUE.on('show_my','click',function(e){
147 147 YUD.setStyle('watched','display','none');
148 148 YUD.setStyle('my','display','');
149 149 var nodes = YUQ('#my tr td a.repo_name');
150 150 var target = 'q_filter';
151 151 var func = function(node){
152 152 return node.parentNode.parentNode.parentNode.parentNode;
153 153 }
154 154 q_filter(target,nodes,func);
155 155 YUE.preventDefault(e);
156 156 })
157 157 YUE.on('show_watched','click',function(e){
158 158 YUD.setStyle('my','display','none');
159 159 YUD.setStyle('watched','display','');
160 160 var nodes = YUQ('#watched .watched_repo a');
161 161 var target = 'q_filter';
162 162 var func = function(node){
163 163 return node.parentNode.parentNode;
164 164 }
165 165 q_filter(target,nodes,func);
166 166 YUE.preventDefault(e);
167 167 })
168 168 YUE.on('refresh','click',function(e){
169 169 ypjax(e.currentTarget.href,"journal",function(){show_more_event();tooltip_activate();});
170 170 YUE.preventDefault(e);
171 171 });
172 172
173 173
174 174 // main table sorting
175 175 var myColumnDefs = [
176 176 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
177 177 {key:"name",label:"${_('Name')}",sortable:true,
178 178 sortOptions: { sortFunction: nameSort }},
179 179 {key:"tip",label:"${_('Tip')}",sortable:true,
180 180 sortOptions: { sortFunction: revisionSort }},
181 181 {key:"action1",label:"",sortable:false},
182 182 {key:"action2",label:"",sortable:false},
183 183 ];
184 184
185 185 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
186 186
187 187 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
188 188
189 189 myDataSource.responseSchema = {
190 190 fields: [
191 191 {key:"menu"},
192 192 {key:"name"},
193 193 {key:"tip"},
194 194 {key:"action1"},
195 195 {key:"action2"}
196 196 ]
197 197 };
198 198
199 199 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
200 200 {
201 201 sortedBy:{key:"name",dir:"asc"},
202 202 MSG_SORTASC:"${_('Click to sort ascending')}",
203 203 MSG_SORTDESC:"${_('Click to sort descending')}",
204 204 MSG_EMPTY:"${_('No records found.')}",
205 205 MSG_ERROR:"${_('Data error.')}",
206 206 MSG_LOADING:"${_('Loading...')}",
207 207 }
208 208 );
209 209 myDataTable.subscribe('postRenderEvent',function(oArgs) {
210 210 tooltip_activate();
211 211 quick_repo_menu();
212 212 var func = function(node){
213 213 return node.parentNode.parentNode.parentNode.parentNode;
214 214 }
215 215 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
216 216 });
217 217
218 218
219 219 </script>
220 220 </%def>
@@ -1,77 +1,83 b''
1 1 ## -*- coding: utf-8 -*-
2 2 %if c.repo_changesets:
3 3 <table class="table_disp">
4 4 <tr>
5 5 <th class="left">${_('revision')}</th>
6 6 <th class="left">${_('commit message')}</th>
7 7 <th class="left">${_('age')}</th>
8 8 <th class="left">${_('author')}</th>
9 9 <th class="left">${_('branch')}</th>
10 10 <th class="left">${_('tags')}</th>
11 11 </tr>
12 12 %for cnt,cs in enumerate(c.repo_changesets):
13 13 <tr class="parity${cnt%2}">
14 14 <td>
15 15 <div><pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre></div>
16 16 </td>
17 17 <td>
18 ${h.urlify_commit(h.truncate(cs.message,50),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
18 ${h.link_to(h.truncate(cs.message,50),
19 h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
20 title=cs.message)}
19 21 </td>
20 22 <td><span class="tooltip" title="${cs.date}">
21 23 ${h.age(cs.date)}</span>
22 24 </td>
23 25 <td title="${cs.author}">${h.person(cs.author)}</td>
24 26 <td>
25 27 <span class="logtags">
26 <span class="branchtag">${cs.branch}</span>
28 <span class="branchtag">
29 %if h.is_hg(c.rhodecode_repo):
30 ${cs.branch}
31 %endif
32 </span>
27 33 </span>
28 34 </td>
29 35 <td>
30 36 <span class="logtags">
31 37 %for tag in cs.tags:
32 38 <span class="tagtag">${tag}</span>
33 39 %endfor
34 40 </span>
35 41 </td>
36 42 </tr>
37 43 %endfor
38 44
39 45 </table>
40 46
41 47 <script type="text/javascript">
42 48 YUE.onDOMReady(function(){
43 49 YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
44 50 ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
45 51 YUE.preventDefault(e);
46 52 },'.pager_link');
47 53 });
48 54 </script>
49 55
50 56 <div class="pagination-wh pagination-left">
51 57 ${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
52 58 </div>
53 59 %else:
54 60
55 61 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
56 62 <h4>${_('Add or upload files directly via RhodeCode')}</h4>
57 63 <div style="margin: 20px 30px;">
58 64 <div id="add_node_id" class="add_node">
59 65 <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
60 66 </div>
61 67 </div>
62 68 %endif
63 69
64 70
65 71 <h4>${_('Push new repo')}</h4>
66 72 <pre>
67 73 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
68 74 ${c.rhodecode_repo.alias} add README # add first file
69 75 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
70 76 ${c.rhodecode_repo.alias} push # push changes back
71 77 </pre>
72 78
73 79 <h4>${_('Existing repository?')}</h4>
74 80 <pre>
75 81 ${c.rhodecode_repo.alias} push ${c.clone_repo_url}
76 82 </pre>
77 83 %endif
@@ -1,698 +1,698 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <%
21 21 summary = lambda n:{False:'summary-short'}.get(n)
22 22 %>
23 23 %if c.show_stats:
24 24 <div class="box box-left">
25 25 %else:
26 26 <div class="box">
27 27 %endif
28 28 <!-- box / title -->
29 29 <div class="title">
30 30 ${self.breadcrumbs()}
31 31 </div>
32 32 <!-- end box / title -->
33 33 <div class="form">
34 34 <div id="summary" class="fields">
35 35
36 36 <div class="field">
37 37 <div class="label-summary">
38 38 <label>${_('Name')}:</label>
39 39 </div>
40 40 <div class="input ${summary(c.show_stats)}">
41 41 <div style="float:right;padding:5px 0px 0px 5px">
42 42 %if c.rhodecode_user.username != 'default':
43 43 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
44 44 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
45 45 %else:
46 46 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
47 47 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
48 48 %endif
49 49 </div>
50 50 %if c.rhodecode_user.username != 'default':
51 51 %if c.following:
52 52 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
53 53 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
54 54 </span>
55 55 %else:
56 56 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
57 57 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
58 58 </span>
59 59 %endif
60 60 %endif:
61 61 ##REPO TYPE
62 %if c.dbrepo.repo_type =='hg':
62 %if h.is_hg(c.dbrepo):
63 63 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
64 64 %endif
65 %if c.dbrepo.repo_type =='git':
65 %if h.is_git(c.dbrepo):
66 66 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
67 67 %endif
68 68
69 69 ##PUBLIC/PRIVATE
70 70 %if c.dbrepo.private:
71 71 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
72 72 %else:
73 73 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
74 74 %endif
75 75
76 76 ##REPO NAME
77 77 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
78 78
79 79 ##FORK
80 80 %if c.dbrepo.fork:
81 81 <div style="margin-top:5px;clear:both"">
82 82 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
83 83 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
84 84 </a>
85 85 </div>
86 86 %endif
87 87 ##REMOTE
88 88 %if c.dbrepo.clone_uri:
89 89 <div style="margin-top:5px;clear:both">
90 90 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
91 91 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
92 92 </a>
93 93 </div>
94 94 %endif
95 95 </div>
96 96 </div>
97 97
98 98 <div class="field">
99 99 <div class="label-summary">
100 100 <label>${_('Description')}:</label>
101 101 </div>
102 102 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
103 103 </div>
104 104
105 105 <div class="field">
106 106 <div class="label-summary">
107 107 <label>${_('Contact')}:</label>
108 108 </div>
109 109 <div class="input ${summary(c.show_stats)}">
110 110 <div class="gravatar">
111 111 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
112 112 </div>
113 113 ${_('Username')}: ${c.dbrepo.user.username}<br/>
114 114 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
115 115 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
116 116 </div>
117 117 </div>
118 118
119 119 <div class="field">
120 120 <div class="label-summary">
121 121 <label>${_('Clone url')}:</label>
122 122 </div>
123 123 <div class="input ${summary(c.show_stats)}">
124 124 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
125 125 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
126 126 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
127 127 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
128 128 </div>
129 129 </div>
130 130
131 131 <div class="field">
132 132 <div class="label-summary">
133 133 <label>${_('Trending files')}:</label>
134 134 </div>
135 135 <div class="input ${summary(c.show_stats)}">
136 136 %if c.show_stats:
137 137 <div id="lang_stats"></div>
138 138 %else:
139 139 ${_('Statistics are disabled for this repository')}
140 140 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
141 141 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
142 142 %endif
143 143 %endif
144 144 </div>
145 145 </div>
146 146
147 147 <div class="field">
148 148 <div class="label-summary">
149 149 <label>${_('Download')}:</label>
150 150 </div>
151 151 <div class="input ${summary(c.show_stats)}">
152 152 %if len(c.rhodecode_repo.revisions) == 0:
153 153 ${_('There are no downloads yet')}
154 154 %elif c.enable_downloads is False:
155 155 ${_('Downloads are disabled for this repository')}
156 156 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
157 157 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
158 158 %endif
159 159 %else:
160 160 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
161 161 <span id="${'zip_link'}">${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
162 162 <span style="vertical-align: bottom">
163 163 <input id="archive_subrepos" type="checkbox" name="subrepos" />
164 164 <label for="archive_subrepos" class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</label>
165 165 </span>
166 166 %endif
167 167 </div>
168 168 </div>
169 169 </div>
170 170 </div>
171 171 </div>
172 172
173 173 %if c.show_stats:
174 174 <div class="box box-right" style="min-height:455px">
175 175 <!-- box / title -->
176 176 <div class="title">
177 177 <h5>${_('Commit activity by day / author')}</h5>
178 178 </div>
179 179
180 180 <div class="graph">
181 181 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
182 182 %if c.no_data:
183 183 ${c.no_data_msg}
184 184 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
185 185 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
186 186 %endif
187 187 %else:
188 188 ${_('Loaded in')} ${c.stats_percentage} %
189 189 %endif
190 190 </div>
191 191 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
192 192 <div style="clear: both;height: 10px"></div>
193 193 <div id="overview" style="width:450px;height:100px;float:left"></div>
194 194
195 195 <div id="legend_data" style="clear:both;margin-top:10px;">
196 196 <div id="legend_container"></div>
197 197 <div id="legend_choices">
198 198 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
199 199 </div>
200 200 </div>
201 201 </div>
202 202 </div>
203 203 %endif
204 204
205 205 <div class="box">
206 206 <div class="title">
207 207 <div class="breadcrumbs">
208 208 %if c.repo_changesets:
209 209 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
210 210 %else:
211 211 ${_('Quick start')}
212 212 %endif
213 213 </div>
214 214 </div>
215 215 <div class="table">
216 216 <div id="shortlog_data">
217 217 <%include file='../shortlog/shortlog_data.html'/>
218 218 </div>
219 219 </div>
220 220 </div>
221 221
222 222 %if c.readme_data:
223 223 <div class="box" style="background-color: #FAFAFA">
224 224 <div class="title">
225 225 <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
226 226 </div>
227 227 <div class="readme">
228 228 <div class="readme_box">
229 229 ${c.readme_data|n}
230 230 </div>
231 231 </div>
232 232 </div>
233 233 %endif
234 234
235 235 <script type="text/javascript">
236 236 var clone_url = 'clone_url';
237 237 YUE.on(clone_url,'click',function(e){
238 238 if(YUD.hasClass(clone_url,'selected')){
239 239 return
240 240 }
241 241 else{
242 242 YUD.addClass(clone_url,'selected');
243 243 YUD.get(clone_url).select();
244 244 }
245 245 })
246 246
247 247 YUE.on('clone_by_name','click',function(e){
248 248 // show url by name and hide name button
249 249 YUD.setStyle('clone_url','display','');
250 250 YUD.setStyle('clone_by_name','display','none');
251 251
252 252 // hide url by id and show name button
253 253 YUD.setStyle('clone_by_id','display','');
254 254 YUD.setStyle('clone_url_id','display','none');
255 255
256 256 })
257 257 YUE.on('clone_by_id','click',function(e){
258 258
259 259 // show url by id and hide id button
260 260 YUD.setStyle('clone_by_id','display','none');
261 261 YUD.setStyle('clone_url_id','display','');
262 262
263 263 // hide url by name and show id button
264 264 YUD.setStyle('clone_by_name','display','');
265 265 YUD.setStyle('clone_url','display','none');
266 266 })
267 267
268 268
269 269 var tmpl_links = {};
270 270 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
271 271 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
272 272 %endfor
273 273
274 274 YUE.on(['download_options','archive_subrepos'],'change',function(e){
275 275 var sm = YUD.get('download_options');
276 276 var new_cs = sm.options[sm.selectedIndex];
277 277
278 278 for(k in tmpl_links){
279 279 var s = YUD.get(k+'_link');
280 280 if(s){
281 281 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
282 282 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
283 283 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
284 284
285 285 var url = tmpl_links[k].replace('__CS__',new_cs.value);
286 286 var subrepos = YUD.get('archive_subrepos').checked;
287 287 url = url.replace('__SUB__',subrepos);
288 288 url = url.replace('__NAME__',title_tmpl);
289 289 s.innerHTML = url
290 290 }
291 291 }
292 292 });
293 293 </script>
294 294 %if c.show_stats:
295 295 <script type="text/javascript">
296 296 var data = ${c.trending_languages|n};
297 297 var total = 0;
298 298 var no_data = true;
299 299 var tbl = document.createElement('table');
300 300 tbl.setAttribute('class','trending_language_tbl');
301 301 var cnt = 0;
302 302 for (var i=0;i<data.length;i++){
303 303 total+= data[i][1].count;
304 304 }
305 305 for (var i=0;i<data.length;i++){
306 306 cnt += 1;
307 307 no_data = false;
308 308
309 309 var hide = cnt>2;
310 310 var tr = document.createElement('tr');
311 311 if (hide){
312 312 tr.setAttribute('style','display:none');
313 313 tr.setAttribute('class','stats_hidden');
314 314 }
315 315 var k = data[i][0];
316 316 var obj = data[i][1];
317 317 var percentage = Math.round((obj.count/total*100),2);
318 318
319 319 var td1 = document.createElement('td');
320 320 td1.width = 150;
321 321 var trending_language_label = document.createElement('div');
322 322 trending_language_label.innerHTML = obj.desc+" ("+k+")";
323 323 td1.appendChild(trending_language_label);
324 324
325 325 var td2 = document.createElement('td');
326 326 td2.setAttribute('style','padding-right:14px !important');
327 327 var trending_language = document.createElement('div');
328 328 var nr_files = obj.count+" ${_('files')}";
329 329
330 330 trending_language.title = k+" "+nr_files;
331 331
332 332 if (percentage>22){
333 333 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
334 334 }
335 335 else{
336 336 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
337 337 }
338 338
339 339 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
340 340 trending_language.style.width=percentage+"%";
341 341 td2.appendChild(trending_language);
342 342
343 343 tr.appendChild(td1);
344 344 tr.appendChild(td2);
345 345 tbl.appendChild(tr);
346 346 if(cnt == 3){
347 347 var show_more = document.createElement('tr');
348 348 var td = document.createElement('td');
349 349 lnk = document.createElement('a');
350 350
351 351 lnk.href='#';
352 352 lnk.innerHTML = "${_('show more')}";
353 353 lnk.id='code_stats_show_more';
354 354 td.appendChild(lnk);
355 355
356 356 show_more.appendChild(td);
357 357 show_more.appendChild(document.createElement('td'));
358 358 tbl.appendChild(show_more);
359 359 }
360 360
361 361 }
362 362
363 363 YUD.get('lang_stats').appendChild(tbl);
364 364 YUE.on('code_stats_show_more','click',function(){
365 365 l = YUD.getElementsByClassName('stats_hidden')
366 366 for (e in l){
367 367 YUD.setStyle(l[e],'display','');
368 368 };
369 369 YUD.setStyle(YUD.get('code_stats_show_more'),
370 370 'display','none');
371 371 });
372 372 </script>
373 373 <script type="text/javascript">
374 374 /**
375 375 * Plots summary graph
376 376 *
377 377 * @class SummaryPlot
378 378 * @param {from} initial from for detailed graph
379 379 * @param {to} initial to for detailed graph
380 380 * @param {dataset}
381 381 * @param {overview_dataset}
382 382 */
383 383 function SummaryPlot(from,to,dataset,overview_dataset) {
384 384 var initial_ranges = {
385 385 "xaxis":{
386 386 "from":from,
387 387 "to":to,
388 388 },
389 389 };
390 390 var dataset = dataset;
391 391 var overview_dataset = [overview_dataset];
392 392 var choiceContainer = YUD.get("legend_choices");
393 393 var choiceContainerTable = YUD.get("legend_choices_tables");
394 394 var plotContainer = YUD.get('commit_history');
395 395 var overviewContainer = YUD.get('overview');
396 396
397 397 var plot_options = {
398 398 bars: {show:true,align:'center',lineWidth:4},
399 399 legend: {show:true, container:"legend_container"},
400 400 points: {show:true,radius:0,fill:false},
401 401 yaxis: {tickDecimals:0,},
402 402 xaxis: {
403 403 mode: "time",
404 404 timeformat: "%d/%m",
405 405 min:from,
406 406 max:to,
407 407 },
408 408 grid: {
409 409 hoverable: true,
410 410 clickable: true,
411 411 autoHighlight:true,
412 412 color: "#999"
413 413 },
414 414 //selection: {mode: "x"}
415 415 };
416 416 var overview_options = {
417 417 legend:{show:false},
418 418 bars: {show:true,barWidth: 2,},
419 419 shadowSize: 0,
420 420 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
421 421 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
422 422 grid: {color: "#999",},
423 423 selection: {mode: "x"}
424 424 };
425 425
426 426 /**
427 427 *get dummy data needed in few places
428 428 */
429 429 function getDummyData(label){
430 430 return {"label":label,
431 431 "data":[{"time":0,
432 432 "commits":0,
433 433 "added":0,
434 434 "changed":0,
435 435 "removed":0,
436 436 }],
437 437 "schema":["commits"],
438 438 "color":'#ffffff',
439 439 }
440 440 }
441 441
442 442 /**
443 443 * generate checkboxes accordindly to data
444 444 * @param keys
445 445 * @returns
446 446 */
447 447 function generateCheckboxes(data) {
448 448 //append checkboxes
449 449 var i = 0;
450 450 choiceContainerTable.innerHTML = '';
451 451 for(var pos in data) {
452 452
453 453 data[pos].color = i;
454 454 i++;
455 455 if(data[pos].label != ''){
456 456 choiceContainerTable.innerHTML +=
457 457 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
458 458 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
459 459 }
460 460 }
461 461 }
462 462
463 463 /**
464 464 * ToolTip show
465 465 */
466 466 function showTooltip(x, y, contents) {
467 467 var div=document.getElementById('tooltip');
468 468 if(!div) {
469 469 div = document.createElement('div');
470 470 div.id="tooltip";
471 471 div.style.position="absolute";
472 472 div.style.border='1px solid #fdd';
473 473 div.style.padding='2px';
474 474 div.style.backgroundColor='#fee';
475 475 document.body.appendChild(div);
476 476 }
477 477 YUD.setStyle(div, 'opacity', 0);
478 478 div.innerHTML = contents;
479 479 div.style.top=(y + 5) + "px";
480 480 div.style.left=(x + 5) + "px";
481 481
482 482 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
483 483 anim.animate();
484 484 }
485 485
486 486 /**
487 487 * This function will detect if selected period has some changesets
488 488 for this user if it does this data is then pushed for displaying
489 489 Additionally it will only display users that are selected by the checkbox
490 490 */
491 491 function getDataAccordingToRanges(ranges) {
492 492
493 493 var data = [];
494 494 var new_dataset = {};
495 495 var keys = [];
496 496 var max_commits = 0;
497 497 for(var key in dataset){
498 498
499 499 for(var ds in dataset[key].data){
500 500 commit_data = dataset[key].data[ds];
501 501 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
502 502
503 503 if(new_dataset[key] === undefined){
504 504 new_dataset[key] = {data:[],schema:["commits"],label:key};
505 505 }
506 506 new_dataset[key].data.push(commit_data);
507 507 }
508 508 }
509 509 if (new_dataset[key] !== undefined){
510 510 data.push(new_dataset[key]);
511 511 }
512 512 }
513 513
514 514 if (data.length > 0){
515 515 return data;
516 516 }
517 517 else{
518 518 //just return dummy data for graph to plot itself
519 519 return [getDummyData('')];
520 520 }
521 521 }
522 522
523 523 /**
524 524 * redraw using new checkbox data
525 525 */
526 526 function plotchoiced(e,args){
527 527 var cur_data = args[0];
528 528 var cur_ranges = args[1];
529 529
530 530 var new_data = [];
531 531 var inputs = choiceContainer.getElementsByTagName("input");
532 532
533 533 //show only checked labels
534 534 for(var i=0; i<inputs.length; i++) {
535 535 var checkbox_key = inputs[i].name;
536 536
537 537 if(inputs[i].checked){
538 538 for(var d in cur_data){
539 539 if(cur_data[d].label == checkbox_key){
540 540 new_data.push(cur_data[d]);
541 541 }
542 542 }
543 543 }
544 544 else{
545 545 //push dummy data to not hide the label
546 546 new_data.push(getDummyData(checkbox_key));
547 547 }
548 548 }
549 549
550 550 var new_options = YAHOO.lang.merge(plot_options, {
551 551 xaxis: {
552 552 min: cur_ranges.xaxis.from,
553 553 max: cur_ranges.xaxis.to,
554 554 mode:"time",
555 555 timeformat: "%d/%m",
556 556 },
557 557 });
558 558 if (!new_data){
559 559 new_data = [[0,1]];
560 560 }
561 561 // do the zooming
562 562 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
563 563
564 564 plot.subscribe("plotselected", plotselected);
565 565
566 566 //resubscribe plothover
567 567 plot.subscribe("plothover", plothover);
568 568
569 569 // don't fire event on the overview to prevent eternal loop
570 570 overview.setSelection(cur_ranges, true);
571 571
572 572 }
573 573
574 574 /**
575 575 * plot only selected items from overview
576 576 * @param ranges
577 577 * @returns
578 578 */
579 579 function plotselected(ranges,cur_data) {
580 580 //updates the data for new plot
581 581 var data = getDataAccordingToRanges(ranges);
582 582 generateCheckboxes(data);
583 583
584 584 var new_options = YAHOO.lang.merge(plot_options, {
585 585 xaxis: {
586 586 min: ranges.xaxis.from,
587 587 max: ranges.xaxis.to,
588 588 mode:"time",
589 589 timeformat: "%d/%m",
590 590 },
591 591 });
592 592 // do the zooming
593 593 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
594 594
595 595 plot.subscribe("plotselected", plotselected);
596 596
597 597 //resubscribe plothover
598 598 plot.subscribe("plothover", plothover);
599 599
600 600 // don't fire event on the overview to prevent eternal loop
601 601 overview.setSelection(ranges, true);
602 602
603 603 //resubscribe choiced
604 604 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
605 605 }
606 606
607 607 var previousPoint = null;
608 608
609 609 function plothover(o) {
610 610 var pos = o.pos;
611 611 var item = o.item;
612 612
613 613 //YUD.get("x").innerHTML = pos.x.toFixed(2);
614 614 //YUD.get("y").innerHTML = pos.y.toFixed(2);
615 615 if (item) {
616 616 if (previousPoint != item.datapoint) {
617 617 previousPoint = item.datapoint;
618 618
619 619 var tooltip = YUD.get("tooltip");
620 620 if(tooltip) {
621 621 tooltip.parentNode.removeChild(tooltip);
622 622 }
623 623 var x = item.datapoint.x.toFixed(2);
624 624 var y = item.datapoint.y.toFixed(2);
625 625
626 626 if (!item.series.label){
627 627 item.series.label = 'commits';
628 628 }
629 629 var d = new Date(x*1000);
630 630 var fd = d.toDateString()
631 631 var nr_commits = parseInt(y);
632 632
633 633 var cur_data = dataset[item.series.label].data[item.dataIndex];
634 634 var added = cur_data.added;
635 635 var changed = cur_data.changed;
636 636 var removed = cur_data.removed;
637 637
638 638 var nr_commits_suffix = " ${_('commits')} ";
639 639 var added_suffix = " ${_('files added')} ";
640 640 var changed_suffix = " ${_('files changed')} ";
641 641 var removed_suffix = " ${_('files removed')} ";
642 642
643 643
644 644 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
645 645 if(added==1){added_suffix=" ${_('file added')} ";}
646 646 if(changed==1){changed_suffix=" ${_('file changed')} ";}
647 647 if(removed==1){removed_suffix=" ${_('file removed')} ";}
648 648
649 649 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
650 650 +'<br/>'+
651 651 nr_commits + nr_commits_suffix+'<br/>'+
652 652 added + added_suffix +'<br/>'+
653 653 changed + changed_suffix + '<br/>'+
654 654 removed + removed_suffix + '<br/>');
655 655 }
656 656 }
657 657 else {
658 658 var tooltip = YUD.get("tooltip");
659 659
660 660 if(tooltip) {
661 661 tooltip.parentNode.removeChild(tooltip);
662 662 }
663 663 previousPoint = null;
664 664 }
665 665 }
666 666
667 667 /**
668 668 * MAIN EXECUTION
669 669 */
670 670
671 671 var data = getDataAccordingToRanges(initial_ranges);
672 672 generateCheckboxes(data);
673 673
674 674 //main plot
675 675 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
676 676
677 677 //overview
678 678 var overview = YAHOO.widget.Flot(overviewContainer,
679 679 overview_dataset, overview_options);
680 680
681 681 //show initial selection on overview
682 682 overview.setSelection(initial_ranges);
683 683
684 684 plot.subscribe("plotselected", plotselected);
685 685 plot.subscribe("plothover", plothover)
686 686
687 687 overview.subscribe("plotselected", function (ranges) {
688 688 plot.setSelection(ranges);
689 689 });
690 690
691 691 // user choices on overview
692 692 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
693 693 }
694 694 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
695 695 </script>
696 696 %endif
697 697
698 698 </%def>
General Comments 0
You need to be logged in to leave comments. Login now