##// END OF EJS Templates
Links in summary page are now active
marcink -
r1438:002f4f46 beta
parent child Browse files
Show More
@@ -1,661 +1,673 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
12 12 from datetime import datetime
13 13 from pygments.formatters import HtmlFormatter
14 14 from pygments import highlight as code_highlight
15 15 from pylons import url, request, config
16 16 from pylons.i18n.translation import _, ungettext
17 17
18 18 from webhelpers.html import literal, HTML, escape
19 19 from webhelpers.html.tools import *
20 20 from webhelpers.html.builder import make_tag
21 21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
23 23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 24 password, textarea, title, ul, xml_declaration, radio
25 25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 26 mail_to, strip_links, strip_tags, tag_re
27 27 from webhelpers.number import format_byte_size, format_bit_size
28 28 from webhelpers.pylonslib import Flash as _Flash
29 29 from webhelpers.pylonslib.secure_form import secure_form
30 30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
31 31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
32 32 replace_whitespace, urlify, truncate, wrap_paragraphs
33 33 from webhelpers.date import time_ago_in_words
34 34 from webhelpers.paginate import Page
35 35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 36 convert_boolean_attrs, NotGiven
37 37
38 38 from vcs.utils.annotate import annotate_highlight
39 39 from rhodecode.lib.utils import repo_name_slug
40 40 from rhodecode.lib import str2bool, safe_unicode, safe_str
41 41
42 42 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
43 43 """
44 44 Reset button
45 45 """
46 46 _set_input_attrs(attrs, type, name, value)
47 47 _set_id_attr(attrs, id, name)
48 48 convert_boolean_attrs(attrs, ["disabled"])
49 49 return HTML.input(**attrs)
50 50
51 51 reset = _reset
52 52
53 53
54 54 def get_token():
55 55 """Return the current authentication token, creating one if one doesn't
56 56 already exist.
57 57 """
58 58 token_key = "_authentication_token"
59 59 from pylons import session
60 60 if not token_key in session:
61 61 try:
62 62 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
63 63 except AttributeError: # Python < 2.4
64 64 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
65 65 session[token_key] = token
66 66 if hasattr(session, 'save'):
67 67 session.save()
68 68 return session[token_key]
69 69
70 70 class _GetError(object):
71 71 """Get error from form_errors, and represent it as span wrapped error
72 72 message
73 73
74 74 :param field_name: field to fetch errors for
75 75 :param form_errors: form errors dict
76 76 """
77 77
78 78 def __call__(self, field_name, form_errors):
79 79 tmpl = """<span class="error_msg">%s</span>"""
80 80 if form_errors and form_errors.has_key(field_name):
81 81 return literal(tmpl % form_errors.get(field_name))
82 82
83 83 get_error = _GetError()
84 84
85 85 class _ToolTip(object):
86 86
87 87 def __call__(self, tooltip_title, trim_at=50):
88 88 """Special function just to wrap our text into nice formatted
89 89 autowrapped text
90 90
91 91 :param tooltip_title:
92 92 """
93 93 return escape(tooltip_title)
94 94 tooltip = _ToolTip()
95 95
96 96 class _FilesBreadCrumbs(object):
97 97
98 98 def __call__(self, repo_name, rev, paths):
99 99 if isinstance(paths, str):
100 100 paths = safe_unicode(paths)
101 101 url_l = [link_to(repo_name, url('files_home',
102 102 repo_name=repo_name,
103 103 revision=rev, f_path=''))]
104 104 paths_l = paths.split('/')
105 105 for cnt, p in enumerate(paths_l):
106 106 if p != '':
107 107 url_l.append(link_to(p, url('files_home',
108 108 repo_name=repo_name,
109 109 revision=rev,
110 110 f_path='/'.join(paths_l[:cnt + 1]))))
111 111
112 112 return literal('/'.join(url_l))
113 113
114 114 files_breadcrumbs = _FilesBreadCrumbs()
115 115
116 116 class CodeHtmlFormatter(HtmlFormatter):
117 117 """My code Html Formatter for source codes
118 118 """
119 119
120 120 def wrap(self, source, outfile):
121 121 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
122 122
123 123 def _wrap_code(self, source):
124 124 for cnt, it in enumerate(source):
125 125 i, t = it
126 126 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
127 127 yield i, t
128 128
129 129 def _wrap_tablelinenos(self, inner):
130 130 dummyoutfile = StringIO.StringIO()
131 131 lncount = 0
132 132 for t, line in inner:
133 133 if t:
134 134 lncount += 1
135 135 dummyoutfile.write(line)
136 136
137 137 fl = self.linenostart
138 138 mw = len(str(lncount + fl - 1))
139 139 sp = self.linenospecial
140 140 st = self.linenostep
141 141 la = self.lineanchors
142 142 aln = self.anchorlinenos
143 143 nocls = self.noclasses
144 144 if sp:
145 145 lines = []
146 146
147 147 for i in range(fl, fl + lncount):
148 148 if i % st == 0:
149 149 if i % sp == 0:
150 150 if aln:
151 151 lines.append('<a href="#%s%d" class="special">%*d</a>' %
152 152 (la, i, mw, i))
153 153 else:
154 154 lines.append('<span class="special">%*d</span>' % (mw, i))
155 155 else:
156 156 if aln:
157 157 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
158 158 else:
159 159 lines.append('%*d' % (mw, i))
160 160 else:
161 161 lines.append('')
162 162 ls = '\n'.join(lines)
163 163 else:
164 164 lines = []
165 165 for i in range(fl, fl + lncount):
166 166 if i % st == 0:
167 167 if aln:
168 168 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
169 169 else:
170 170 lines.append('%*d' % (mw, i))
171 171 else:
172 172 lines.append('')
173 173 ls = '\n'.join(lines)
174 174
175 175 # in case you wonder about the seemingly redundant <div> here: since the
176 176 # content in the other cell also is wrapped in a div, some browsers in
177 177 # some configurations seem to mess up the formatting...
178 178 if nocls:
179 179 yield 0, ('<table class="%stable">' % self.cssclass +
180 180 '<tr><td><div class="linenodiv" '
181 181 'style="background-color: #f0f0f0; padding-right: 10px">'
182 182 '<pre style="line-height: 125%">' +
183 183 ls + '</pre></div></td><td id="hlcode" class="code">')
184 184 else:
185 185 yield 0, ('<table class="%stable">' % self.cssclass +
186 186 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
187 187 ls + '</pre></div></td><td id="hlcode" class="code">')
188 188 yield 0, dummyoutfile.getvalue()
189 189 yield 0, '</td></tr></table>'
190 190
191 191
192 192 def pygmentize(filenode, **kwargs):
193 193 """pygmentize function using pygments
194 194
195 195 :param filenode:
196 196 """
197 197
198 198 return literal(code_highlight(filenode.content,
199 199 filenode.lexer, CodeHtmlFormatter(**kwargs)))
200 200
201 201 def pygmentize_annotation(repo_name, filenode, **kwargs):
202 202 """pygmentize function for annotation
203 203
204 204 :param filenode:
205 205 """
206 206
207 207 color_dict = {}
208 208 def gen_color(n=10000):
209 209 """generator for getting n of evenly distributed colors using
210 210 hsv color and golden ratio. It always return same order of colors
211 211
212 212 :returns: RGB tuple
213 213 """
214 214 import colorsys
215 215 golden_ratio = 0.618033988749895
216 216 h = 0.22717784590367374
217 217
218 218 for _ in xrange(n):
219 219 h += golden_ratio
220 220 h %= 1
221 221 HSV_tuple = [h, 0.95, 0.95]
222 222 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
223 223 yield map(lambda x:str(int(x * 256)), RGB_tuple)
224 224
225 225 cgenerator = gen_color()
226 226
227 227 def get_color_string(cs):
228 228 if color_dict.has_key(cs):
229 229 col = color_dict[cs]
230 230 else:
231 231 col = color_dict[cs] = cgenerator.next()
232 232 return "color: rgb(%s)! important;" % (', '.join(col))
233 233
234 234 def url_func(repo_name):
235 235
236 236 def _url_func(changeset):
237 237 author = changeset.author
238 238 date = changeset.date
239 239 message = tooltip(changeset.message)
240 240
241 241 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
242 242 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
243 243 "</b> %s<br/></div>")
244 244
245 245 tooltip_html = tooltip_html % (author, date, message)
246 246 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
247 247 short_id(changeset.raw_id))
248 248 uri = link_to(
249 249 lnk_format,
250 250 url('changeset_home', repo_name=repo_name,
251 251 revision=changeset.raw_id),
252 252 style=get_color_string(changeset.raw_id),
253 253 class_='tooltip',
254 254 title=tooltip_html
255 255 )
256 256
257 257 uri += '\n'
258 258 return uri
259 259 return _url_func
260 260
261 261 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
262 262
263 263 def get_changeset_safe(repo, rev):
264 264 from vcs.backends.base import BaseRepository
265 265 from vcs.exceptions import RepositoryError
266 266 if not isinstance(repo, BaseRepository):
267 267 raise Exception('You must pass an Repository '
268 268 'object as first argument got %s', type(repo))
269 269
270 270 try:
271 271 cs = repo.get_changeset(rev)
272 272 except RepositoryError:
273 273 from rhodecode.lib.utils import EmptyChangeset
274 274 cs = EmptyChangeset()
275 275 return cs
276 276
277 277
278 278 def is_following_repo(repo_name, user_id):
279 279 from rhodecode.model.scm import ScmModel
280 280 return ScmModel().is_following_repo(repo_name, user_id)
281 281
282 282 flash = _Flash()
283 283
284 284 #==============================================================================
285 285 # SCM FILTERS available via h.
286 286 #==============================================================================
287 287 from vcs.utils import author_name, author_email
288 288 from rhodecode.lib import credentials_filter, age as _age
289 289
290 290 age = lambda x:_age(x)
291 291 capitalize = lambda x: x.capitalize()
292 292 email = author_email
293 293 email_or_none = lambda x: email(x) if email(x) != x else None
294 294 person = lambda x: author_name(x)
295 295 short_id = lambda x: x[:12]
296 296 hide_credentials = lambda x: ''.join(credentials_filter(x))
297 297
298 298 def bool2icon(value):
299 299 """Returns True/False values represented as small html image of true/false
300 300 icons
301 301
302 302 :param value: bool value
303 303 """
304 304
305 305 if value is True:
306 306 return HTML.tag('img', src=url("/images/icons/accept.png"),
307 307 alt=_('True'))
308 308
309 309 if value is False:
310 310 return HTML.tag('img', src=url("/images/icons/cancel.png"),
311 311 alt=_('False'))
312 312
313 313 return value
314 314
315 315
316 316 def action_parser(user_log, feed=False):
317 317 """This helper will action_map the specified string action into translated
318 318 fancy names with icons and links
319 319
320 320 :param user_log: user log instance
321 321 :param feed: use output for feeds (no html and fancy icons)
322 322 """
323 323
324 324 action = user_log.action
325 325 action_params = ' '
326 326
327 327 x = action.split(':')
328 328
329 329 if len(x) > 1:
330 330 action, action_params = x
331 331
332 332 def get_cs_links():
333 333 revs_limit = 3 #display this amount always
334 334 revs_top_limit = 50 #show upto this amount of changesets hidden
335 335 revs = action_params.split(',')
336 336 repo_name = user_log.repository.repo_name
337 337
338 338 from rhodecode.model.scm import ScmModel
339 339 repo = user_log.repository.scm_instance
340 340
341 341 message = lambda rev: get_changeset_safe(repo, rev).message
342 342 cs_links = []
343 343 cs_links.append(" " + ', '.join ([link_to(rev,
344 344 url('changeset_home',
345 345 repo_name=repo_name,
346 346 revision=rev), title=tooltip(message(rev)),
347 347 class_='tooltip') for rev in revs[:revs_limit] ]))
348 348
349 349 compare_view = (' <div class="compare_view tooltip" title="%s">'
350 350 '<a href="%s">%s</a> '
351 351 '</div>' % (_('Show all combined changesets %s->%s' \
352 352 % (revs[0], revs[-1])),
353 353 url('changeset_home', repo_name=repo_name,
354 354 revision='%s...%s' % (revs[0], revs[-1])
355 355 ),
356 356 _('compare view'))
357 357 )
358 358
359 359 if len(revs) > revs_limit:
360 360 uniq_id = revs[0]
361 361 html_tmpl = ('<span> %s '
362 362 '<a class="show_more" id="_%s" href="#more">%s</a> '
363 363 '%s</span>')
364 364 if not feed:
365 365 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
366 366 % (len(revs) - revs_limit),
367 367 _('revisions')))
368 368
369 369 if not feed:
370 370 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
371 371 else:
372 372 html_tmpl = '<span id="%s"> %s </span>'
373 373
374 374 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
375 375 url('changeset_home',
376 376 repo_name=repo_name, revision=rev),
377 377 title=message(rev), class_='tooltip')
378 378 for rev in revs[revs_limit:revs_top_limit]])))
379 379 if len(revs) > 1:
380 380 cs_links.append(compare_view)
381 381 return ''.join(cs_links)
382 382
383 383 def get_fork_name():
384 384 repo_name = action_params
385 385 return _('fork name ') + str(link_to(action_params, url('summary_home',
386 386 repo_name=repo_name,)))
387 387
388 388 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
389 389 'user_created_repo':(_('[created] repository'), None),
390 390 'user_forked_repo':(_('[forked] repository'), get_fork_name),
391 391 'user_updated_repo':(_('[updated] repository'), None),
392 392 'admin_deleted_repo':(_('[delete] repository'), None),
393 393 'admin_created_repo':(_('[created] repository'), None),
394 394 'admin_forked_repo':(_('[forked] repository'), None),
395 395 'admin_updated_repo':(_('[updated] repository'), None),
396 396 'push':(_('[pushed] into'), get_cs_links),
397 397 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
398 398 'push_remote':(_('[pulled from remote] into'), get_cs_links),
399 399 'pull':(_('[pulled] from'), None),
400 400 'started_following_repo':(_('[started following] repository'), None),
401 401 'stopped_following_repo':(_('[stopped following] repository'), None),
402 402 }
403 403
404 404 action_str = action_map.get(action, action)
405 405 if feed:
406 406 action = action_str[0].replace('[', '').replace(']', '')
407 407 else:
408 408 action = action_str[0].replace('[', '<span class="journal_highlight">')\
409 409 .replace(']', '</span>')
410 410
411 411 action_params_func = lambda :""
412 412
413 413 if callable(action_str[1]):
414 414 action_params_func = action_str[1]
415 415
416 416 return [literal(action), action_params_func]
417 417
418 418 def action_parser_icon(user_log):
419 419 action = user_log.action
420 420 action_params = None
421 421 x = action.split(':')
422 422
423 423 if len(x) > 1:
424 424 action, action_params = x
425 425
426 426 tmpl = """<img src="%s%s" alt="%s"/>"""
427 427 map = {'user_deleted_repo':'database_delete.png',
428 428 'user_created_repo':'database_add.png',
429 429 'user_forked_repo':'arrow_divide.png',
430 430 'user_updated_repo':'database_edit.png',
431 431 'admin_deleted_repo':'database_delete.png',
432 432 'admin_created_repo':'database_add.png',
433 433 'admin_forked_repo':'arrow_divide.png',
434 434 'admin_updated_repo':'database_edit.png',
435 435 'push':'script_add.png',
436 436 'push_local':'script_edit.png',
437 437 'push_remote':'connect.png',
438 438 'pull':'down_16.png',
439 439 'started_following_repo':'heart_add.png',
440 440 'stopped_following_repo':'heart_delete.png',
441 441 }
442 442 return literal(tmpl % ((url('/images/icons/')),
443 443 map.get(action, action), action))
444 444
445 445
446 446 #==============================================================================
447 447 # PERMS
448 448 #==============================================================================
449 449 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
450 450 HasRepoPermissionAny, HasRepoPermissionAll
451 451
452 452 #==============================================================================
453 453 # GRAVATAR URL
454 454 #==============================================================================
455 455
456 456 def gravatar_url(email_address, size=30):
457 457 if not str2bool(config['app_conf'].get('use_gravatar')) or \
458 458 email_address == 'anonymous@rhodecode.org':
459 459 return "/images/user%s.png" % size
460 460
461 461 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
462 462 default = 'identicon'
463 463 baseurl_nossl = "http://www.gravatar.com/avatar/"
464 464 baseurl_ssl = "https://secure.gravatar.com/avatar/"
465 465 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
466 466
467 467 if isinstance(email_address, unicode):
468 468 #hashlib crashes on unicode items
469 469 email_address = safe_str(email_address)
470 470 # construct the url
471 471 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
472 472 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
473 473
474 474 return gravatar_url
475 475
476 476
477 477 #==============================================================================
478 478 # REPO PAGER, PAGER FOR REPOSITORY
479 479 #==============================================================================
480 480 class RepoPage(Page):
481 481
482 482 def __init__(self, collection, page=1, items_per_page=20,
483 483 item_count=None, url=None, branch_name=None, **kwargs):
484 484
485 485 """Create a "RepoPage" instance. special pager for paging
486 486 repository
487 487 """
488 488 self._url_generator = url
489 489
490 490 # Safe the kwargs class-wide so they can be used in the pager() method
491 491 self.kwargs = kwargs
492 492
493 493 # Save a reference to the collection
494 494 self.original_collection = collection
495 495
496 496 self.collection = collection
497 497
498 498 # The self.page is the number of the current page.
499 499 # The first page has the number 1!
500 500 try:
501 501 self.page = int(page) # make it int() if we get it as a string
502 502 except (ValueError, TypeError):
503 503 self.page = 1
504 504
505 505 self.items_per_page = items_per_page
506 506
507 507 # Unless the user tells us how many items the collections has
508 508 # we calculate that ourselves.
509 509 if item_count is not None:
510 510 self.item_count = item_count
511 511 else:
512 512 self.item_count = len(self.collection)
513 513
514 514 # Compute the number of the first and last available page
515 515 if self.item_count > 0:
516 516 self.first_page = 1
517 517 self.page_count = int(math.ceil(float(self.item_count) /
518 518 self.items_per_page))
519 519 self.last_page = self.first_page + self.page_count - 1
520 520
521 521 # Make sure that the requested page number is the range of valid pages
522 522 if self.page > self.last_page:
523 523 self.page = self.last_page
524 524 elif self.page < self.first_page:
525 525 self.page = self.first_page
526 526
527 527 # Note: the number of items on this page can be less than
528 528 # items_per_page if the last page is not full
529 529 self.first_item = max(0, (self.item_count) - (self.page *
530 530 items_per_page))
531 531 self.last_item = ((self.item_count - 1) - items_per_page *
532 532 (self.page - 1))
533 533
534 534 iterator = self.collection.get_changesets(start=self.first_item,
535 535 end=self.last_item,
536 536 reverse=True,
537 537 branch_name=branch_name)
538 538 self.items = list(iterator)
539 539
540 540 # Links to previous and next page
541 541 if self.page > self.first_page:
542 542 self.previous_page = self.page - 1
543 543 else:
544 544 self.previous_page = None
545 545
546 546 if self.page < self.last_page:
547 547 self.next_page = self.page + 1
548 548 else:
549 549 self.next_page = None
550 550
551 551 # No items available
552 552 else:
553 553 self.first_page = None
554 554 self.page_count = 0
555 555 self.last_page = None
556 556 self.first_item = None
557 557 self.last_item = None
558 558 self.previous_page = None
559 559 self.next_page = None
560 560 self.items = []
561 561
562 562 # This is a subclass of the 'list' type. Initialise the list now.
563 563 list.__init__(self, self.items)
564 564
565 565
566 566 def changed_tooltip(nodes):
567 567 """
568 568 Generates a html string for changed nodes in changeset page.
569 569 It limits the output to 30 entries
570 570
571 571 :param nodes: LazyNodesGenerator
572 572 """
573 573 if nodes:
574 574 pref = ': <br/> '
575 575 suf = ''
576 576 if len(nodes) > 30:
577 577 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
578 578 return literal(pref + '<br/> '.join([safe_unicode(x.path)
579 579 for x in nodes[:30]]) + suf)
580 580 else:
581 581 return ': ' + _('No Files')
582 582
583 583
584 584
585 585 def repo_link(groups_and_repos):
586 586 """
587 587 Makes a breadcrumbs link to repo within a group
588 588 joins &raquo; on each group to create a fancy link
589 589
590 590 ex::
591 591 group >> subgroup >> repo
592 592
593 593 :param groups_and_repos:
594 594 """
595 595 groups, repo_name = groups_and_repos
596 596
597 597 if not groups:
598 598 return repo_name
599 599 else:
600 600 def make_link(group):
601 601 return link_to(group.group_name, url('repos_group',
602 602 id=group.group_id))
603 603 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 604 " &raquo; " + repo_name)
605 605
606 606
607 607 def fancy_file_stats(stats):
608 608 """
609 609 Displays a fancy two colored bar for number of added/deleted
610 610 lines of code on file
611 611
612 612 :param stats: two element list of added/deleted lines of code
613 613 """
614 614
615 615 a, d, t = stats[0], stats[1], stats[0] + stats[1]
616 616 width = 100
617 617 unit = float(width) / (t or 1)
618 618
619 619 # needs > 9% of width to be visible or 0 to be hidden
620 620 a_p = max(9, unit * a) if a > 0 else 0
621 621 d_p = max(9, unit * d) if d > 0 else 0
622 622 p_sum = a_p + d_p
623 623
624 624 if p_sum > width:
625 625 #adjust the percentage to be == 100% since we adjusted to 9
626 626 if a_p > d_p:
627 627 a_p = a_p - (p_sum - width)
628 628 else:
629 629 d_p = d_p - (p_sum - width)
630 630
631 631 a_v = a if a > 0 else ''
632 632 d_v = d if d > 0 else ''
633 633
634 634
635 635 def cgen(l_type):
636 636 mapping = {'tr':'top-right-rounded-corner',
637 637 'tl':'top-left-rounded-corner',
638 638 'br':'bottom-right-rounded-corner',
639 639 'bl':'bottom-left-rounded-corner'}
640 640 map_getter = lambda x:mapping[x]
641 641
642 642 if l_type == 'a' and d_v:
643 643 #case when added and deleted are present
644 644 return ' '.join(map(map_getter, ['tl', 'bl']))
645 645
646 646 if l_type == 'a' and not d_v:
647 647 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
648 648
649 649 if l_type == 'd' and a_v:
650 650 return ' '.join(map(map_getter, ['tr', 'br']))
651 651
652 652 if l_type == 'd' and not a_v:
653 653 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
654 654
655 655
656 656
657 657 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
658 658 a_p, a_v)
659 659 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
660 660 d_p, d_v)
661 661 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
662
663
664 def urlify_text(text):
665 import re
666
667 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
668
669 def url_func(match_obj):
670 url_full = match_obj.groups()[0]
671 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
672
673 return literal(url_pat.sub(url_func, text))
@@ -1,696 +1,696 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 <div class="box box-left">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 <div class="form">
27 27 <div id="summary" class="fields">
28 28
29 29 <div class="field">
30 30 <div class="label">
31 31 <label>${_('Name')}:</label>
32 32 </div>
33 33 <div class="input-short">
34 34 %if c.rhodecode_user.username != 'default':
35 35 %if c.following:
36 36 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
37 37 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
38 38 </span>
39 39 %else:
40 40 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
41 41 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
42 42 </span>
43 43 %endif
44 44 %endif:
45 45
46 46 ##REPO TYPE
47 47 %if c.dbrepo.repo_type =='hg':
48 48 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
49 49 %endif
50 50 %if c.dbrepo.repo_type =='git':
51 51 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
52 52 %endif
53 53
54 54 ##PUBLIC/PRIVATE
55 55 %if c.dbrepo.private:
56 56 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
57 57 %else:
58 58 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
59 59 %endif
60 60
61 61 ##REPO NAME
62 62 <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
63 63
64 64 ##FORK
65 65 %if c.dbrepo.fork:
66 66 <div style="margin-top:5px;clear:both"">
67 67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
68 68 <img class="icon" alt="${_('public')}"
69 69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
70 70 src="${h.url("/images/icons/arrow_divide.png")}"/>
71 71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
72 72 </a>
73 73 </div>
74 74 %endif
75 75 ##REMOTE
76 76 %if c.dbrepo.clone_uri:
77 77 <div style="margin-top:5px;clear:both">
78 78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
79 79 <img class="icon" alt="${_('remote clone')}"
80 80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
81 81 src="${h.url("/images/icons/connect.png")}"/>
82 82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
83 83 </a>
84 84 </div>
85 85 %endif
86 86 </div>
87 87 </div>
88 88
89 89
90 90 <div class="field">
91 91 <div class="label">
92 92 <label>${_('Description')}:</label>
93 93 </div>
94 <div class="input-short desc">${c.dbrepo.description}</div>
94 <div class="input-short desc">${h.urlify_text(c.dbrepo.description)}</div>
95 95 </div>
96 96
97 97
98 98 <div class="field">
99 99 <div class="label">
100 100 <label>${_('Contact')}:</label>
101 101 </div>
102 102 <div class="input-short">
103 103 <div class="gravatar">
104 104 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
105 105 </div>
106 106 ${_('Username')}: ${c.dbrepo.user.username}<br/>
107 107 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
108 108 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
109 109 </div>
110 110 </div>
111 111
112 112 <div class="field">
113 113 <div class="label">
114 114 <label>${_('Last change')}:</label>
115 115 </div>
116 116 <div class="input-short">
117 117 <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
118 118 h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
119 119 <span class="tooltip" title="${c.rhodecode_repo.last_change}">
120 120 ${h.age(c.rhodecode_repo.last_change)}</span><br/>
121 121 ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
122 122
123 123 </div>
124 124 </div>
125 125
126 126 <div class="field">
127 127 <div class="label">
128 128 <label>${_('Clone url')}:</label>
129 129 </div>
130 130 <div class="input-short">
131 131 <input type="text" id="clone_url" readonly="readonly" value="${c.rhodecode_repo.alias} clone ${c.clone_repo_url}" size="70"/>
132 132 </div>
133 133 </div>
134 134
135 135 <div class="field">
136 136 <div class="label">
137 137 <label>${_('Trending source files')}:</label>
138 138 </div>
139 139 <div class="input-short">
140 140 <div id="lang_stats"></div>
141 141 </div>
142 142 </div>
143 143
144 144 <div class="field">
145 145 <div class="label">
146 146 <label>${_('Download')}:</label>
147 147 </div>
148 148 <div class="input-short">
149 149 %if len(c.rhodecode_repo.revisions) == 0:
150 150 ${_('There are no downloads yet')}
151 151 %elif c.enable_downloads is False:
152 152 ${_('Downloads are disabled for this repository')}
153 153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
154 154 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
155 155 %endif
156 156 %else:
157 157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
158 158 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
159 159 %if cnt >=1:
160 160 |
161 161 %endif
162 162 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
163 163 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
164 164 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
165 165 fname='tip'+archive['extension']),class_="archive_icon")}</span>
166 166 %endfor
167 167 %endif
168 168 </div>
169 169 </div>
170 170
171 171 <div class="field">
172 172 <div class="label">
173 173 <label>${_('Feeds')}:</label>
174 174 </div>
175 175 <div class="input-short">
176 176 %if c.rhodecode_user.username != 'default':
177 177 ${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')}
178 178 ${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')}
179 179 %else:
180 180 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
181 181 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
182 182 %endif
183 183 </div>
184 184 </div>
185 185 </div>
186 186 </div>
187 187 <script type="text/javascript">
188 188 YUE.onDOMReady(function(e){
189 189 id = 'clone_url';
190 190 YUE.on(id,'click',function(e){
191 191 if(YUD.hasClass(id,'selected')){
192 192 return
193 193 }
194 194 else{
195 195 YUD.addClass(id,'selected');
196 196 YUD.get(id).select();
197 197 }
198 198
199 199 })
200 200 })
201 201 var data = ${c.trending_languages|n};
202 202 var total = 0;
203 203 var no_data = true;
204 204 for (k in data){
205 205 total += data[k].count;
206 206 no_data = false;
207 207 }
208 208 var tbl = document.createElement('table');
209 209 tbl.setAttribute('class','trending_language_tbl');
210 210 var cnt = 0;
211 211 for (k in data){
212 212 cnt += 1;
213 213 var hide = cnt>2;
214 214 var tr = document.createElement('tr');
215 215 if (hide){
216 216 tr.setAttribute('style','display:none');
217 217 tr.setAttribute('class','stats_hidden');
218 218 }
219 219 var percentage = Math.round((data[k].count/total*100),2);
220 220 var value = data[k].count;
221 221 var td1 = document.createElement('td');
222 222 td1.width = 150;
223 223 var trending_language_label = document.createElement('div');
224 224 trending_language_label.innerHTML = data[k].desc+" ("+k+")";
225 225 td1.appendChild(trending_language_label);
226 226
227 227 var td2 = document.createElement('td');
228 228 td2.setAttribute('style','padding-right:14px !important');
229 229 var trending_language = document.createElement('div');
230 230 var nr_files = value+" ${_('files')}";
231 231
232 232 trending_language.title = k+" "+nr_files;
233 233
234 234 if (percentage>22){
235 235 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
236 236 }
237 237 else{
238 238 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
239 239 }
240 240
241 241 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
242 242 trending_language.style.width=percentage+"%";
243 243 td2.appendChild(trending_language);
244 244
245 245 tr.appendChild(td1);
246 246 tr.appendChild(td2);
247 247 tbl.appendChild(tr);
248 248 if(cnt == 3){
249 249 var show_more = document.createElement('tr');
250 250 var td = document.createElement('td');
251 251 lnk = document.createElement('a');
252 252
253 253 lnk.href='#';
254 254 lnk.innerHTML = "${_('show more')}";
255 255 lnk.id='code_stats_show_more';
256 256 td.appendChild(lnk);
257 257
258 258 show_more.appendChild(td);
259 259 show_more.appendChild(document.createElement('td'));
260 260 tbl.appendChild(show_more);
261 261 }
262 262
263 263 }
264 264 if(no_data){
265 265 var tr = document.createElement('tr');
266 266 var td1 = document.createElement('td');
267 267 td1.innerHTML = "${c.no_data_msg}";
268 268 tr.appendChild(td1);
269 269 tbl.appendChild(tr);
270 270 }
271 271 YUD.get('lang_stats').appendChild(tbl);
272 272 YUE.on('code_stats_show_more','click',function(){
273 273 l = YUD.getElementsByClassName('stats_hidden')
274 274 for (e in l){
275 275 YUD.setStyle(l[e],'display','');
276 276 };
277 277 YUD.setStyle(YUD.get('code_stats_show_more'),
278 278 'display','none');
279 279 })
280 280
281 281
282 282 YUE.on('download_options','change',function(e){
283 283 var new_cs = e.target.options[e.target.selectedIndex];
284 284 var tmpl_links = {}
285 285 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
286 286 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
287 287 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
288 288 fname='__CS__'+archive['extension']),class_="archive_icon")}';
289 289 %endfor
290 290
291 291
292 292 for(k in tmpl_links){
293 293 var s = YUD.get(k+'_link')
294 294 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
295 295 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text);
296 296 s.title = s.title.replace('__CS_EXT__',k);
297 297 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
298 298 }
299 299
300 300 })
301 301
302 302 </script>
303 303 </div>
304 304
305 305 <div class="box box-right" style="min-height:455px">
306 306 <!-- box / title -->
307 307 <div class="title">
308 308 <h5>${_('Commit activity by day / author')}</h5>
309 309 </div>
310 310
311 311 <div class="graph">
312 312 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
313 313 %if c.no_data:
314 314 ${c.no_data_msg}
315 315 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
316 316 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
317 317 %endif
318 318
319 319 %else:
320 320 ${_('Loaded in')} ${c.stats_percentage} %
321 321 %endif
322 322 </div>
323 323 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
324 324 <div style="clear: both;height: 10px"></div>
325 325 <div id="overview" style="width:450px;height:100px;float:left"></div>
326 326
327 327 <div id="legend_data" style="clear:both;margin-top:10px;">
328 328 <div id="legend_container"></div>
329 329 <div id="legend_choices">
330 330 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
331 331 </div>
332 332 </div>
333 333 <script type="text/javascript">
334 334 /**
335 335 * Plots summary graph
336 336 *
337 337 * @class SummaryPlot
338 338 * @param {from} initial from for detailed graph
339 339 * @param {to} initial to for detailed graph
340 340 * @param {dataset}
341 341 * @param {overview_dataset}
342 342 */
343 343 function SummaryPlot(from,to,dataset,overview_dataset) {
344 344 var initial_ranges = {
345 345 "xaxis":{
346 346 "from":from,
347 347 "to":to,
348 348 },
349 349 };
350 350 var dataset = dataset;
351 351 var overview_dataset = [overview_dataset];
352 352 var choiceContainer = YUD.get("legend_choices");
353 353 var choiceContainerTable = YUD.get("legend_choices_tables");
354 354 var plotContainer = YUD.get('commit_history');
355 355 var overviewContainer = YUD.get('overview');
356 356
357 357 var plot_options = {
358 358 bars: {show:true,align:'center',lineWidth:4},
359 359 legend: {show:true, container:"legend_container"},
360 360 points: {show:true,radius:0,fill:false},
361 361 yaxis: {tickDecimals:0,},
362 362 xaxis: {
363 363 mode: "time",
364 364 timeformat: "%d/%m",
365 365 min:from,
366 366 max:to,
367 367 },
368 368 grid: {
369 369 hoverable: true,
370 370 clickable: true,
371 371 autoHighlight:true,
372 372 color: "#999"
373 373 },
374 374 //selection: {mode: "x"}
375 375 };
376 376 var overview_options = {
377 377 legend:{show:false},
378 378 bars: {show:true,barWidth: 2,},
379 379 shadowSize: 0,
380 380 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
381 381 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
382 382 grid: {color: "#999",},
383 383 selection: {mode: "x"}
384 384 };
385 385
386 386 /**
387 387 *get dummy data needed in few places
388 388 */
389 389 function getDummyData(label){
390 390 return {"label":label,
391 391 "data":[{"time":0,
392 392 "commits":0,
393 393 "added":0,
394 394 "changed":0,
395 395 "removed":0,
396 396 }],
397 397 "schema":["commits"],
398 398 "color":'#ffffff',
399 399 }
400 400 }
401 401
402 402 /**
403 403 * generate checkboxes accordindly to data
404 404 * @param keys
405 405 * @returns
406 406 */
407 407 function generateCheckboxes(data) {
408 408 //append checkboxes
409 409 var i = 0;
410 410 choiceContainerTable.innerHTML = '';
411 411 for(var pos in data) {
412 412
413 413 data[pos].color = i;
414 414 i++;
415 415 if(data[pos].label != ''){
416 416 choiceContainerTable.innerHTML += '<tr><td>'+
417 417 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
418 418 +data[pos].label+
419 419 '</td></tr>';
420 420 }
421 421 }
422 422 }
423 423
424 424 /**
425 425 * ToolTip show
426 426 */
427 427 function showTooltip(x, y, contents) {
428 428 var div=document.getElementById('tooltip');
429 429 if(!div) {
430 430 div = document.createElement('div');
431 431 div.id="tooltip";
432 432 div.style.position="absolute";
433 433 div.style.border='1px solid #fdd';
434 434 div.style.padding='2px';
435 435 div.style.backgroundColor='#fee';
436 436 document.body.appendChild(div);
437 437 }
438 438 YUD.setStyle(div, 'opacity', 0);
439 439 div.innerHTML = contents;
440 440 div.style.top=(y + 5) + "px";
441 441 div.style.left=(x + 5) + "px";
442 442
443 443 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
444 444 anim.animate();
445 445 }
446 446
447 447 /**
448 448 * This function will detect if selected period has some changesets
449 449 for this user if it does this data is then pushed for displaying
450 450 Additionally it will only display users that are selected by the checkbox
451 451 */
452 452 function getDataAccordingToRanges(ranges) {
453 453
454 454 var data = [];
455 455 var new_dataset = {};
456 456 var keys = [];
457 457 var max_commits = 0;
458 458 for(var key in dataset){
459 459
460 460 for(var ds in dataset[key].data){
461 461 commit_data = dataset[key].data[ds];
462 462 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
463 463
464 464 if(new_dataset[key] === undefined){
465 465 new_dataset[key] = {data:[],schema:["commits"],label:key};
466 466 }
467 467 new_dataset[key].data.push(commit_data);
468 468 }
469 469 }
470 470 if (new_dataset[key] !== undefined){
471 471 data.push(new_dataset[key]);
472 472 }
473 473 }
474 474
475 475 if (data.length > 0){
476 476 return data;
477 477 }
478 478 else{
479 479 //just return dummy data for graph to plot itself
480 480 return [getDummyData('')];
481 481 }
482 482 }
483 483
484 484 /**
485 485 * redraw using new checkbox data
486 486 */
487 487 function plotchoiced(e,args){
488 488 var cur_data = args[0];
489 489 var cur_ranges = args[1];
490 490
491 491 var new_data = [];
492 492 var inputs = choiceContainer.getElementsByTagName("input");
493 493
494 494 //show only checked labels
495 495 for(var i=0; i<inputs.length; i++) {
496 496 var checkbox_key = inputs[i].name;
497 497
498 498 if(inputs[i].checked){
499 499 for(var d in cur_data){
500 500 if(cur_data[d].label == checkbox_key){
501 501 new_data.push(cur_data[d]);
502 502 }
503 503 }
504 504 }
505 505 else{
506 506 //push dummy data to not hide the label
507 507 new_data.push(getDummyData(checkbox_key));
508 508 }
509 509 }
510 510
511 511 var new_options = YAHOO.lang.merge(plot_options, {
512 512 xaxis: {
513 513 min: cur_ranges.xaxis.from,
514 514 max: cur_ranges.xaxis.to,
515 515 mode:"time",
516 516 timeformat: "%d/%m",
517 517 },
518 518 });
519 519 if (!new_data){
520 520 new_data = [[0,1]];
521 521 }
522 522 // do the zooming
523 523 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
524 524
525 525 plot.subscribe("plotselected", plotselected);
526 526
527 527 //resubscribe plothover
528 528 plot.subscribe("plothover", plothover);
529 529
530 530 // don't fire event on the overview to prevent eternal loop
531 531 overview.setSelection(cur_ranges, true);
532 532
533 533 }
534 534
535 535 /**
536 536 * plot only selected items from overview
537 537 * @param ranges
538 538 * @returns
539 539 */
540 540 function plotselected(ranges,cur_data) {
541 541 //updates the data for new plot
542 542 var data = getDataAccordingToRanges(ranges);
543 543 generateCheckboxes(data);
544 544
545 545 var new_options = YAHOO.lang.merge(plot_options, {
546 546 xaxis: {
547 547 min: ranges.xaxis.from,
548 548 max: ranges.xaxis.to,
549 549 mode:"time",
550 550 timeformat: "%d/%m",
551 551 },
552 552 });
553 553 // do the zooming
554 554 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
555 555
556 556 plot.subscribe("plotselected", plotselected);
557 557
558 558 //resubscribe plothover
559 559 plot.subscribe("plothover", plothover);
560 560
561 561 // don't fire event on the overview to prevent eternal loop
562 562 overview.setSelection(ranges, true);
563 563
564 564 //resubscribe choiced
565 565 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
566 566 }
567 567
568 568 var previousPoint = null;
569 569
570 570 function plothover(o) {
571 571 var pos = o.pos;
572 572 var item = o.item;
573 573
574 574 //YUD.get("x").innerHTML = pos.x.toFixed(2);
575 575 //YUD.get("y").innerHTML = pos.y.toFixed(2);
576 576 if (item) {
577 577 if (previousPoint != item.datapoint) {
578 578 previousPoint = item.datapoint;
579 579
580 580 var tooltip = YUD.get("tooltip");
581 581 if(tooltip) {
582 582 tooltip.parentNode.removeChild(tooltip);
583 583 }
584 584 var x = item.datapoint.x.toFixed(2);
585 585 var y = item.datapoint.y.toFixed(2);
586 586
587 587 if (!item.series.label){
588 588 item.series.label = 'commits';
589 589 }
590 590 var d = new Date(x*1000);
591 591 var fd = d.toDateString()
592 592 var nr_commits = parseInt(y);
593 593
594 594 var cur_data = dataset[item.series.label].data[item.dataIndex];
595 595 var added = cur_data.added;
596 596 var changed = cur_data.changed;
597 597 var removed = cur_data.removed;
598 598
599 599 var nr_commits_suffix = " ${_('commits')} ";
600 600 var added_suffix = " ${_('files added')} ";
601 601 var changed_suffix = " ${_('files changed')} ";
602 602 var removed_suffix = " ${_('files removed')} ";
603 603
604 604
605 605 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
606 606 if(added==1){added_suffix=" ${_('file added')} ";}
607 607 if(changed==1){changed_suffix=" ${_('file changed')} ";}
608 608 if(removed==1){removed_suffix=" ${_('file removed')} ";}
609 609
610 610 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
611 611 +'<br/>'+
612 612 nr_commits + nr_commits_suffix+'<br/>'+
613 613 added + added_suffix +'<br/>'+
614 614 changed + changed_suffix + '<br/>'+
615 615 removed + removed_suffix + '<br/>');
616 616 }
617 617 }
618 618 else {
619 619 var tooltip = YUD.get("tooltip");
620 620
621 621 if(tooltip) {
622 622 tooltip.parentNode.removeChild(tooltip);
623 623 }
624 624 previousPoint = null;
625 625 }
626 626 }
627 627
628 628 /**
629 629 * MAIN EXECUTION
630 630 */
631 631
632 632 var data = getDataAccordingToRanges(initial_ranges);
633 633 generateCheckboxes(data);
634 634
635 635 //main plot
636 636 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
637 637
638 638 //overview
639 639 var overview = YAHOO.widget.Flot(overviewContainer,
640 640 overview_dataset, overview_options);
641 641
642 642 //show initial selection on overview
643 643 overview.setSelection(initial_ranges);
644 644
645 645 plot.subscribe("plotselected", plotselected);
646 646 plot.subscribe("plothover", plothover)
647 647
648 648 overview.subscribe("plotselected", function (ranges) {
649 649 plot.setSelection(ranges);
650 650 });
651 651
652 652 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
653 653 }
654 654 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
655 655 </script>
656 656
657 657 </div>
658 658 </div>
659 659
660 660 <div class="box">
661 661 <div class="title">
662 662 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
663 663 </div>
664 664 <div class="table">
665 665 <div id="shortlog_data">
666 666 <%include file='../shortlog/shortlog_data.html'/>
667 667 </div>
668 668 ##%if c.repo_changesets:
669 669 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
670 670 ##%endif
671 671 </div>
672 672 </div>
673 673 <div class="box">
674 674 <div class="title">
675 675 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
676 676 </div>
677 677 <div class="table">
678 678 <%include file='../tags/tags_data.html'/>
679 679 %if c.repo_changesets:
680 680 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
681 681 %endif
682 682 </div>
683 683 </div>
684 684 <div class="box">
685 685 <div class="title">
686 686 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
687 687 </div>
688 688 <div class="table">
689 689 <%include file='../branches/branches_data.html'/>
690 690 %if c.repo_changesets:
691 691 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
692 692 %endif
693 693 </div>
694 694 </div>
695 695
696 696 </%def>
General Comments 0
You need to be logged in to leave comments. Login now