##// END OF EJS Templates
Fixed problem with anchor link positioning when passed in ranges....
marcink -
r1320:09c3fb46 beta
parent child Browse files
Show More
@@ -1,693 +1,693
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
11 11 from datetime import datetime
12 12 from pygments.formatters import HtmlFormatter
13 13 from pygments import highlight as code_highlight
14 14 from pylons import url, request, config
15 15 from pylons.i18n.translation import _, ungettext
16 16
17 17 from webhelpers.html import literal, HTML, escape
18 18 from webhelpers.html.tools import *
19 19 from webhelpers.html.builder import make_tag
20 20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 23 password, textarea, title, ul, xml_declaration, radio
24 24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 25 mail_to, strip_links, strip_tags, tag_re
26 26 from webhelpers.number import format_byte_size, format_bit_size
27 27 from webhelpers.pylonslib import Flash as _Flash
28 28 from webhelpers.pylonslib.secure_form import secure_form
29 29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 32 from webhelpers.date import time_ago_in_words
33 33 from webhelpers.paginate import Page
34 34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 35 convert_boolean_attrs, NotGiven
36 36
37 37 from vcs.utils.annotate import annotate_highlight
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib import str2bool, safe_unicode
40 40
41 41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 42 """
43 43 Reset button
44 44 """
45 45 _set_input_attrs(attrs, type, name, value)
46 46 _set_id_attr(attrs, id, name)
47 47 convert_boolean_attrs(attrs, ["disabled"])
48 48 return HTML.input(**attrs)
49 49
50 50 reset = _reset
51 51
52 52
53 53 def get_token():
54 54 """Return the current authentication token, creating one if one doesn't
55 55 already exist.
56 56 """
57 57 token_key = "_authentication_token"
58 58 from pylons import session
59 59 if not token_key in session:
60 60 try:
61 61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 62 except AttributeError: # Python < 2.4
63 63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 64 session[token_key] = token
65 65 if hasattr(session, 'save'):
66 66 session.save()
67 67 return session[token_key]
68 68
69 69 class _GetError(object):
70 70 """Get error from form_errors, and represent it as span wrapped error
71 71 message
72 72
73 73 :param field_name: field to fetch errors for
74 74 :param form_errors: form errors dict
75 75 """
76 76
77 77 def __call__(self, field_name, form_errors):
78 78 tmpl = """<span class="error_msg">%s</span>"""
79 79 if form_errors and form_errors.has_key(field_name):
80 80 return literal(tmpl % form_errors.get(field_name))
81 81
82 82 get_error = _GetError()
83 83
84 84 class _ToolTip(object):
85 85
86 86 def __call__(self, tooltip_title, trim_at=50):
87 87 """Special function just to wrap our text into nice formatted
88 88 autowrapped text
89 89
90 90 :param tooltip_title:
91 91 """
92 92
93 93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 94 .replace('\n', '<br/>')
95 95
96 96 def activate(self):
97 97 """Adds tooltip mechanism to the given Html all tooltips have to have
98 98 set class `tooltip` and set attribute `tooltip_title`.
99 99 Then a tooltip will be generated based on that. All with yui js tooltip
100 100 """
101 101
102 102 js = '''
103 103 YAHOO.util.Event.onDOMReady(function(){
104 104 function toolTipsId(){
105 105 var ids = [];
106 106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107 107
108 108 for (var i = 0; i < tts.length; i++) {
109 109 //if element doesn't not have and id autogenerate one for tooltip
110 110
111 111 if (!tts[i].id){
112 112 tts[i].id='tt'+i*100;
113 113 }
114 114 ids.push(tts[i].id);
115 115 }
116 116 return ids
117 117 };
118 118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 119 context: [[toolTipsId()],"tl","bl",null,[0,5]],
120 120 monitorresize:false,
121 121 xyoffset :[0,0],
122 122 autodismissdelay:300000,
123 123 hidedelay:5,
124 124 showdelay:20,
125 125 });
126 126
127 127 });
128 128 '''
129 129 return literal(js)
130 130
131 131 tooltip = _ToolTip()
132 132
133 133 class _FilesBreadCrumbs(object):
134 134
135 135 def __call__(self, repo_name, rev, paths):
136 136 if isinstance(paths, str):
137 137 paths = safe_unicode(paths)
138 138 url_l = [link_to(repo_name, url('files_home',
139 139 repo_name=repo_name,
140 140 revision=rev, f_path=''))]
141 141 paths_l = paths.split('/')
142 142 for cnt, p in enumerate(paths_l):
143 143 if p != '':
144 144 url_l.append(link_to(p, url('files_home',
145 145 repo_name=repo_name,
146 146 revision=rev,
147 147 f_path='/'.join(paths_l[:cnt + 1]))))
148 148
149 149 return literal('/'.join(url_l))
150 150
151 151 files_breadcrumbs = _FilesBreadCrumbs()
152 152
153 153 class CodeHtmlFormatter(HtmlFormatter):
154 154 """My code Html Formatter for source codes
155 155 """
156 156
157 157 def wrap(self, source, outfile):
158 158 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
159 159
160 160 def _wrap_code(self, source):
161 161 for cnt, it in enumerate(source):
162 162 i, t = it
163 163 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
164 164 yield i, t
165 165
166 166 def _wrap_tablelinenos(self, inner):
167 167 dummyoutfile = StringIO.StringIO()
168 168 lncount = 0
169 169 for t, line in inner:
170 170 if t:
171 171 lncount += 1
172 172 dummyoutfile.write(line)
173 173
174 174 fl = self.linenostart
175 175 mw = len(str(lncount + fl - 1))
176 176 sp = self.linenospecial
177 177 st = self.linenostep
178 178 la = self.lineanchors
179 179 aln = self.anchorlinenos
180 180 nocls = self.noclasses
181 181 if sp:
182 182 lines = []
183 183
184 184 for i in range(fl, fl + lncount):
185 185 if i % st == 0:
186 186 if i % sp == 0:
187 187 if aln:
188 188 lines.append('<a href="#%s%d" class="special">%*d</a>' %
189 189 (la, i, mw, i))
190 190 else:
191 191 lines.append('<span class="special">%*d</span>' % (mw, i))
192 192 else:
193 193 if aln:
194 194 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
195 195 else:
196 196 lines.append('%*d' % (mw, i))
197 197 else:
198 198 lines.append('')
199 199 ls = '\n'.join(lines)
200 200 else:
201 201 lines = []
202 202 for i in range(fl, fl + lncount):
203 203 if i % st == 0:
204 204 if aln:
205 205 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
206 206 else:
207 207 lines.append('%*d' % (mw, i))
208 208 else:
209 209 lines.append('')
210 210 ls = '\n'.join(lines)
211 211
212 212 # in case you wonder about the seemingly redundant <div> here: since the
213 213 # content in the other cell also is wrapped in a div, some browsers in
214 214 # some configurations seem to mess up the formatting...
215 215 if nocls:
216 216 yield 0, ('<table class="%stable">' % self.cssclass +
217 217 '<tr><td><div class="linenodiv" '
218 218 'style="background-color: #f0f0f0; padding-right: 10px">'
219 219 '<pre style="line-height: 125%">' +
220 ls + '</pre></div></td><td class="code">')
220 ls + '</pre></div></td><td id="hlcode" class="code">')
221 221 else:
222 222 yield 0, ('<table class="%stable">' % self.cssclass +
223 223 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
224 ls + '</pre></div></td><td class="code">')
224 ls + '</pre></div></td><td id="hlcode" class="code">')
225 225 yield 0, dummyoutfile.getvalue()
226 226 yield 0, '</td></tr></table>'
227 227
228 228
229 229 def pygmentize(filenode, **kwargs):
230 230 """pygmentize function using pygments
231 231
232 232 :param filenode:
233 233 """
234 234
235 235 return literal(code_highlight(filenode.content,
236 236 filenode.lexer, CodeHtmlFormatter(**kwargs)))
237 237
238 238 def pygmentize_annotation(repo_name, filenode, **kwargs):
239 239 """pygmentize function for annotation
240 240
241 241 :param filenode:
242 242 """
243 243
244 244 color_dict = {}
245 245 def gen_color(n=10000):
246 246 """generator for getting n of evenly distributed colors using
247 247 hsv color and golden ratio. It always return same order of colors
248 248
249 249 :returns: RGB tuple
250 250 """
251 251 import colorsys
252 252 golden_ratio = 0.618033988749895
253 253 h = 0.22717784590367374
254 254
255 for c in xrange(n):
255 for _ in xrange(n):
256 256 h += golden_ratio
257 257 h %= 1
258 258 HSV_tuple = [h, 0.95, 0.95]
259 259 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
260 260 yield map(lambda x:str(int(x * 256)), RGB_tuple)
261 261
262 262 cgenerator = gen_color()
263 263
264 264 def get_color_string(cs):
265 265 if color_dict.has_key(cs):
266 266 col = color_dict[cs]
267 267 else:
268 268 col = color_dict[cs] = cgenerator.next()
269 269 return "color: rgb(%s)! important;" % (', '.join(col))
270 270
271 271 def url_func(repo_name):
272 272 def _url_func(changeset):
273 273 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
274 274 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
275 275
276 276 tooltip_html = tooltip_html % (changeset.author,
277 277 changeset.date,
278 278 tooltip(changeset.message))
279 279 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
280 280 short_id(changeset.raw_id))
281 281 uri = link_to(
282 282 lnk_format,
283 283 url('changeset_home', repo_name=repo_name,
284 284 revision=changeset.raw_id),
285 285 style=get_color_string(changeset.raw_id),
286 286 class_='tooltip',
287 287 title=tooltip_html
288 288 )
289 289
290 290 uri += '\n'
291 291 return uri
292 292 return _url_func
293 293
294 294 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
295 295
296 296 def get_changeset_safe(repo, rev):
297 297 from vcs.backends.base import BaseRepository
298 298 from vcs.exceptions import RepositoryError
299 299 if not isinstance(repo, BaseRepository):
300 300 raise Exception('You must pass an Repository '
301 301 'object as first argument got %s', type(repo))
302 302
303 303 try:
304 304 cs = repo.get_changeset(rev)
305 305 except RepositoryError:
306 306 from rhodecode.lib.utils import EmptyChangeset
307 307 cs = EmptyChangeset()
308 308 return cs
309 309
310 310
311 311 def is_following_repo(repo_name, user_id):
312 312 from rhodecode.model.scm import ScmModel
313 313 return ScmModel().is_following_repo(repo_name, user_id)
314 314
315 315 flash = _Flash()
316 316
317 317
318 318 #==============================================================================
319 319 # MERCURIAL FILTERS available via h.
320 320 #==============================================================================
321 321 from mercurial import util
322 322 from mercurial.templatefilters import person as _person
323 323
324 324 def _age(curdate):
325 325 """turns a datetime into an age string."""
326 326
327 327 if not curdate:
328 328 return ''
329 329
330 330 agescales = [("year", 3600 * 24 * 365),
331 331 ("month", 3600 * 24 * 30),
332 332 ("day", 3600 * 24),
333 333 ("hour", 3600),
334 334 ("minute", 60),
335 335 ("second", 1), ]
336 336
337 337 age = datetime.now() - curdate
338 338 age_seconds = (age.days * agescales[2][1]) + age.seconds
339 339 pos = 1
340 340 for scale in agescales:
341 341 if scale[1] <= age_seconds:
342 342 if pos == 6:pos = 5
343 343 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
344 344 pos += 1
345 345
346 346 return _('just now')
347 347
348 348 age = lambda x:_age(x)
349 349 capitalize = lambda x: x.capitalize()
350 350 email = util.email
351 351 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
352 352 person = lambda x: _person(x)
353 353 short_id = lambda x: x[:12]
354 354
355 355
356 356 def bool2icon(value):
357 357 """Returns True/False values represented as small html image of true/false
358 358 icons
359 359
360 360 :param value: bool value
361 361 """
362 362
363 363 if value is True:
364 364 return HTML.tag('img', src=url("/images/icons/accept.png"),
365 365 alt=_('True'))
366 366
367 367 if value is False:
368 368 return HTML.tag('img', src=url("/images/icons/cancel.png"),
369 369 alt=_('False'))
370 370
371 371 return value
372 372
373 373
374 374 def action_parser(user_log, feed=False):
375 375 """This helper will action_map the specified string action into translated
376 376 fancy names with icons and links
377 377
378 378 :param user_log: user log instance
379 379 :param feed: use output for feeds (no html and fancy icons)
380 380 """
381 381
382 382 action = user_log.action
383 383 action_params = ' '
384 384
385 385 x = action.split(':')
386 386
387 387 if len(x) > 1:
388 388 action, action_params = x
389 389
390 390 def get_cs_links():
391 391 revs_limit = 5 #display this amount always
392 392 revs_top_limit = 50 #show upto this amount of changesets hidden
393 393 revs = action_params.split(',')
394 394 repo_name = user_log.repository.repo_name
395 395
396 396 from rhodecode.model.scm import ScmModel
397 397 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
398 398 invalidation_list=[])
399 399
400 400 message = lambda rev: get_changeset_safe(repo, rev).message
401 401
402 402 cs_links = " " + ', '.join ([link_to(rev,
403 403 url('changeset_home',
404 404 repo_name=repo_name,
405 405 revision=rev), title=tooltip(message(rev)),
406 406 class_='tooltip') for rev in revs[:revs_limit] ])
407 407
408 408 compare_view = (' <div class="compare_view tooltip" title="%s">'
409 409 '<a href="%s">%s</a> '
410 410 '</div>' % (_('Show all combined changesets %s->%s' \
411 411 % (revs[0], revs[-1])),
412 412 url('changeset_home', repo_name=repo_name,
413 413 revision='%s...%s' % (revs[0], revs[-1])
414 414 ),
415 415 _('compare view'))
416 416 )
417 417
418 418 if len(revs) > revs_limit:
419 419 uniq_id = revs[0]
420 420 html_tmpl = ('<span> %s '
421 421 '<a class="show_more" id="_%s" href="#more">%s</a> '
422 422 '%s</span>')
423 423 if not feed:
424 424 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
425 425 % (len(revs) - revs_limit),
426 426 _('revisions'))
427 427
428 428 if not feed:
429 429 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
430 430 else:
431 431 html_tmpl = '<span id="%s"> %s </span>'
432 432
433 433 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
434 434 url('changeset_home',
435 435 repo_name=repo_name, revision=rev),
436 436 title=message(rev), class_='tooltip')
437 437 for rev in revs[revs_limit:revs_top_limit]]))
438 438 if len(revs) > 1:
439 439 cs_links += compare_view
440 440 return cs_links
441 441
442 442 def get_fork_name():
443 443 repo_name = action_params
444 444 return _('fork name ') + str(link_to(action_params, url('summary_home',
445 445 repo_name=repo_name,)))
446 446
447 447 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
448 448 'user_created_repo':(_('[created] repository'), None),
449 449 'user_forked_repo':(_('[forked] repository'), get_fork_name),
450 450 'user_updated_repo':(_('[updated] repository'), None),
451 451 'admin_deleted_repo':(_('[delete] repository'), None),
452 452 'admin_created_repo':(_('[created] repository'), None),
453 453 'admin_forked_repo':(_('[forked] repository'), None),
454 454 'admin_updated_repo':(_('[updated] repository'), None),
455 455 'push':(_('[pushed] into'), get_cs_links),
456 456 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
457 457 'push_remote':(_('[pulled from remote] into'), get_cs_links),
458 458 'pull':(_('[pulled] from'), None),
459 459 'started_following_repo':(_('[started following] repository'), None),
460 460 'stopped_following_repo':(_('[stopped following] repository'), None),
461 461 }
462 462
463 463 action_str = action_map.get(action, action)
464 464 if feed:
465 465 action = action_str[0].replace('[', '').replace(']', '')
466 466 else:
467 467 action = action_str[0].replace('[', '<span class="journal_highlight">')\
468 468 .replace(']', '</span>')
469 469
470 470 action_params_func = lambda :""
471 471
472 472 if callable(action_str[1]):
473 473 action_params_func = action_str[1]
474 474
475 475 return [literal(action), action_params_func]
476 476
477 477 def action_parser_icon(user_log):
478 478 action = user_log.action
479 479 action_params = None
480 480 x = action.split(':')
481 481
482 482 if len(x) > 1:
483 483 action, action_params = x
484 484
485 485 tmpl = """<img src="%s%s" alt="%s"/>"""
486 486 map = {'user_deleted_repo':'database_delete.png',
487 487 'user_created_repo':'database_add.png',
488 488 'user_forked_repo':'arrow_divide.png',
489 489 'user_updated_repo':'database_edit.png',
490 490 'admin_deleted_repo':'database_delete.png',
491 491 'admin_created_repo':'database_add.png',
492 492 'admin_forked_repo':'arrow_divide.png',
493 493 'admin_updated_repo':'database_edit.png',
494 494 'push':'script_add.png',
495 495 'push_local':'script_edit.png',
496 496 'push_remote':'connect.png',
497 497 'pull':'down_16.png',
498 498 'started_following_repo':'heart_add.png',
499 499 'stopped_following_repo':'heart_delete.png',
500 500 }
501 501 return literal(tmpl % ((url('/images/icons/')),
502 502 map.get(action, action), action))
503 503
504 504
505 505 #==============================================================================
506 506 # PERMS
507 507 #==============================================================================
508 508 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
509 509 HasRepoPermissionAny, HasRepoPermissionAll
510 510
511 511 #==============================================================================
512 512 # GRAVATAR URL
513 513 #==============================================================================
514 514
515 515 def gravatar_url(email_address, size=30):
516 516 if not str2bool(config['app_conf'].get('use_gravatar')):
517 517 return "/images/user%s.png" % size
518 518
519 519 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
520 520 default = 'identicon'
521 521 baseurl_nossl = "http://www.gravatar.com/avatar/"
522 522 baseurl_ssl = "https://secure.gravatar.com/avatar/"
523 523 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
524 524
525 525 if isinstance(email_address, unicode):
526 526 #hashlib crashes on unicode items
527 527 email_address = email_address.encode('utf8', 'replace')
528 528 # construct the url
529 529 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
530 530 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
531 531
532 532 return gravatar_url
533 533
534 534
535 535 #==============================================================================
536 536 # REPO PAGER
537 537 #==============================================================================
538 538 class RepoPage(Page):
539 539
540 540 def __init__(self, collection, page=1, items_per_page=20,
541 541 item_count=None, url=None, branch_name=None, **kwargs):
542 542
543 543 """Create a "RepoPage" instance. special pager for paging
544 544 repository
545 545 """
546 546 self._url_generator = url
547 547
548 548 # Safe the kwargs class-wide so they can be used in the pager() method
549 549 self.kwargs = kwargs
550 550
551 551 # Save a reference to the collection
552 552 self.original_collection = collection
553 553
554 554 self.collection = collection
555 555
556 556 # The self.page is the number of the current page.
557 557 # The first page has the number 1!
558 558 try:
559 559 self.page = int(page) # make it int() if we get it as a string
560 560 except (ValueError, TypeError):
561 561 self.page = 1
562 562
563 563 self.items_per_page = items_per_page
564 564
565 565 # Unless the user tells us how many items the collections has
566 566 # we calculate that ourselves.
567 567 if item_count is not None:
568 568 self.item_count = item_count
569 569 else:
570 570 self.item_count = len(self.collection)
571 571
572 572 # Compute the number of the first and last available page
573 573 if self.item_count > 0:
574 574 self.first_page = 1
575 575 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
576 576 self.last_page = self.first_page + self.page_count - 1
577 577
578 578 # Make sure that the requested page number is the range of valid pages
579 579 if self.page > self.last_page:
580 580 self.page = self.last_page
581 581 elif self.page < self.first_page:
582 582 self.page = self.first_page
583 583
584 584 # Note: the number of items on this page can be less than
585 585 # items_per_page if the last page is not full
586 586 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
587 587 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
588 588
589 589 iterator = self.collection.get_changesets(start=self.first_item,
590 590 end=self.last_item,
591 591 reverse=True,
592 592 branch_name=branch_name)
593 593 self.items = list(iterator)
594 594
595 595 # Links to previous and next page
596 596 if self.page > self.first_page:
597 597 self.previous_page = self.page - 1
598 598 else:
599 599 self.previous_page = None
600 600
601 601 if self.page < self.last_page:
602 602 self.next_page = self.page + 1
603 603 else:
604 604 self.next_page = None
605 605
606 606 # No items available
607 607 else:
608 608 self.first_page = None
609 609 self.page_count = 0
610 610 self.last_page = None
611 611 self.first_item = None
612 612 self.last_item = None
613 613 self.previous_page = None
614 614 self.next_page = None
615 615 self.items = []
616 616
617 617 # This is a subclass of the 'list' type. Initialise the list now.
618 618 list.__init__(self, self.items)
619 619
620 620
621 621 def changed_tooltip(nodes):
622 622 if nodes:
623 623 pref = ': <br/> '
624 624 suf = ''
625 625 if len(nodes) > 30:
626 626 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
627 627 return literal(pref + '<br/> '.join([safe_unicode(x.path)
628 628 for x in nodes[:30]]) + suf)
629 629 else:
630 630 return ': ' + _('No Files')
631 631
632 632
633 633
634 634 def repo_link(groups_and_repos):
635 635 groups, repo_name = groups_and_repos
636 636
637 637 if not groups:
638 638 return repo_name
639 639 else:
640 640 def make_link(group):
641 641 return link_to(group.group_name, url('repos_group',
642 642 id=group.group_id))
643 643 return literal(' &raquo; '.join(map(make_link, groups)) + \
644 644 " &raquo; " + repo_name)
645 645
646 646
647 647 def fancy_file_stats(stats):
648 648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
649 649 width = 100
650 650 unit = float(width) / (t or 1)
651 651
652 652 a_p = max(9, unit * a) if a > 0 else 0# needs > 9% to be visible
653 653 d_p = max(9, unit * d) if d > 0 else 0 # needs > 9% to be visible
654 654 p_sum = a_p + d_p
655 655
656 656 if p_sum > width:
657 657 #adjust the percentage to be == 100% since we adjusted to 9
658 658 if a_p > d_p:
659 659 a_p = a_p - (p_sum - width)
660 660 else:
661 661 d_p = d_p - (p_sum - width)
662 662
663 663 a_v = a if a > 0 else ''
664 664 d_v = d if d > 0 else ''
665 665
666 666
667 667 def cgen(l_type):
668 668 mapping = {'tr':'top-right-rounded-corner',
669 669 'tl':'top-left-rounded-corner',
670 670 'br':'bottom-right-rounded-corner',
671 671 'bl':'bottom-left-rounded-corner'}
672 672 map_getter = lambda x:mapping[x]
673 673
674 674 if l_type == 'a' and d_v:
675 675 #case when added and deleted are present
676 676 return ' '.join(map(map_getter, ['tl', 'bl']))
677 677
678 678 if l_type == 'a' and not d_v:
679 679 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
680 680
681 681 if l_type == 'd' and a_v:
682 682 return ' '.join(map(map_getter, ['tr', 'br']))
683 683
684 684 if l_type == 'd' and not a_v:
685 685 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
686 686
687 687
688 688
689 689 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
690 690 a_p, a_v)
691 691 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
692 692 d_p, d_v)
693 693 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
@@ -1,97 +1,136
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('File annotate')} - ${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.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('annotate')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('files')}
17 17 </%def>
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 <ul class="links">
24 24 <li>
25 25 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.cs.branch}</a></span>
26 26 </li>
27 27 </ul>
28 28 </div>
29 29 <div class="table">
30 30 <div id="files_data">
31 31 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
32 32 <dl class="overview">
33 33 <dt>${_('Revision')}</dt>
34 34 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),
35 35 h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))} </dd>
36 36 <dt>${_('Size')}</dt>
37 37 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
38 38 <dt>${_('Mimetype')}</dt>
39 39 <dd>${c.file.mimetype}</dd>
40 40 <dt>${_('Options')}</dt>
41 41 <dd>${h.link_to(_('show source'),
42 42 h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
43 43 / ${h.link_to(_('show as raw'),
44 44 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
45 45 / ${h.link_to(_('download as raw'),
46 46 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
47 47 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
48 48 % if not c.file.is_binary:
49 49 / ${h.link_to(_('edit'),
50 50 h.url('files_edit_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
51 51 % endif
52 52 % endif
53 53 </dd>
54 54 <dt>${_('History')}</dt>
55 55 <dd>
56 56 <div>
57 57 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
58 58 ${h.hidden('diff2',c.file.last_changeset.raw_id)}
59 59 ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
60 60 ${h.submit('diff','diff to revision',class_="ui-button-small")}
61 61 ${h.submit('show_rev','show at revision',class_="ui-button-small")}
62 62 ${h.end_form()}
63 63 </div>
64 64 </dd>
65 65 </dl>
66 66 <div id="body" class="codeblock">
67 67 <div class="code-header">
68 68 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}</div>
69 69 <div class="commit">"${c.file.message}"</div>
70 70 </div>
71 71 <div class="code-body">
72 72 %if c.file.is_binary:
73 73 ${_('Binary file (%s)') % c.file.mimetype}
74 74 %else:
75 75 % if c.file.size < c.cut_off_limit:
76 ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
76 ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
77 77 %else:
78 78 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
79 79 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path))}
80 80 %endif
81 81 <script type="text/javascript">
82 function highlight_lines(lines){
83 for(pos in lines){
84 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
85 }
86 }
87 page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
88 if (page_highlights.length == 2){
89 highlight_ranges = page_highlights[1].split(",");
90
91 var h_lines = [];
92 for (pos in highlight_ranges){
93 var _range = highlight_ranges[pos].split('-');
94 if(_range.length == 2){
95 var start = parseInt(_range[0]);
96 var end = parseInt(_range[1]);
97 if (start < end){
98 for(var i=start;i<=end;i++){
99 h_lines.push(i);
100 }
101 }
102 }
103 else{
104 h_lines.push(parseInt(highlight_ranges[pos]));
105 }
106 }
107 highlight_lines(h_lines);
108
109 //remember original location
110 var old_hash = location.href.substring(location.href.indexOf('#'));
111
112 // this makes a jump to anchor moved by 3 posstions for padding
113 window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
114
115 //sets old anchor
116 window.location.hash = old_hash;
117
118 }
119 </script>
120 %endif
121 </div>
122 </div>
123 <script type="text/javascript">
82 124 YAHOO.util.Event.onDOMReady(function(){
83 YAHOO.util.Event.addListener('show_rev','click',function(e){
125 YUE.on('show_rev','click',function(e){
84 126 YAHOO.util.Event.preventDefault(e);
85 127 var cs = YAHOO.util.Dom.get('diff1').value;
86 128 var url = "${h.url('files_annotate_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
87 129 window.location = url;
88 130 });
89 131 });
90 132 </script>
91 %endif
92 </div>
93 </div>
94 133 </div>
95 134 </div>
96 135 </div>
97 136 </%def> No newline at end of file
@@ -1,97 +1,173
1 1 <dl>
2 2 <dt>${_('Revision')}</dt>
3 3 <dd>
4 4 ${h.link_to("r%s:%s" % (c.files_list.last_changeset.revision,h.short_id(c.files_list.last_changeset.raw_id)),
5 5 h.url('changeset_home',repo_name=c.repo_name,revision=c.files_list.last_changeset.raw_id))}
6 6 </dd>
7 7 <dt>${_('Size')}</dt>
8 8 <dd>${h.format_byte_size(c.files_list.size,binary=True)}</dd>
9 9 <dt>${_('Mimetype')}</dt>
10 10 <dd>${c.files_list.mimetype}</dd>
11 11 <dt>${_('Options')}</dt>
12 12 <dd>${h.link_to(_('show annotation'),
13 13 h.url('files_annotate_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
14 14 / ${h.link_to(_('show as raw'),
15 15 h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
16 16 / ${h.link_to(_('download as raw'),
17 17 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
18 18 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
19 19 % if not c.files_list.is_binary:
20 20 / ${h.link_to(_('edit'),
21 21 h.url('files_edit_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
22 22 % endif
23 23 % endif
24 24 </dd>
25 25 <dt>${_('History')}</dt>
26 26 <dd>
27 27 <div>
28 28 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
29 29 ${h.hidden('diff2',c.files_list.last_changeset.raw_id)}
30 30 ${h.select('diff1',c.files_list.last_changeset.raw_id,c.file_history)}
31 31 ${h.submit('diff','diff to revision',class_="ui-button-small")}
32 32 ${h.submit('show_rev','show at revision',class_="ui-button-small")}
33 33 ${h.end_form()}
34 34 </div>
35 35 </dd>
36 36 </dl>
37 37
38 38
39 39 <div id="body" class="codeblock">
40 40 <div class="code-header">
41 41 <div class="revision">${c.files_list.name}@r${c.files_list.last_changeset.revision}:${h.short_id(c.files_list.last_changeset.raw_id)}</div>
42 42 <div class="commit">"${c.files_list.last_changeset.message}"</div>
43 43 </div>
44 44 <div class="code-body">
45 45 %if c.files_list.is_binary:
46 46 ${_('Binary file (%s)') % c.files_list.mimetype}
47 47 %else:
48 48 % if c.files_list.size < c.cut_off_limit:
49 49 ${h.pygmentize(c.files_list,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
50 50 %else:
51 51 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
52 52 h.url('files_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path))}
53 53 %endif
54
55 54 <script type="text/javascript">
56 55 function highlight_lines(lines){
57 56 for(pos in lines){
58 57 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
59 58 }
60 59 }
61 60 page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
62 61 if (page_highlights.length == 2){
63 62 highlight_ranges = page_highlights[1].split(",");
64 63
65 64 var h_lines = [];
66 65 for (pos in highlight_ranges){
67 66 var _range = highlight_ranges[pos].split('-');
68 67 if(_range.length == 2){
69 68 var start = parseInt(_range[0]);
70 69 var end = parseInt(_range[1]);
71 70 if (start < end){
72 71 for(var i=start;i<=end;i++){
73 72 h_lines.push(i);
74 73 }
75 74 }
76 75 }
77 76 else{
78 77 h_lines.push(parseInt(highlight_ranges[pos]));
79 78 }
80 79 }
81 80 highlight_lines(h_lines);
81
82 //remember original location
83 var old_hash = location.href.substring(location.href.indexOf('#'));
84
85 // this makes a jump to anchor moved by 3 posstions for padding
86 window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
87
88 //sets old anchor
89 window.location.hash = old_hash;
90
82 91 }
83 92 </script>
84 93 %endif
85 94 </div>
86 95 </div>
87 96
88 97 <script type="text/javascript">
89 YAHOO.util.Event.onDOMReady(function(){
90 YAHOO.util.Event.addListener('show_rev','click',function(e){
91 YAHOO.util.Event.preventDefault(e);
98 YUE.onDOMReady(function(){
99 YUE.on('show_rev','click',function(e){
100 YUE.preventDefault(e);
92 101 var cs = YAHOO.util.Dom.get('diff1').value;
93 102 var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
94 103 window.location = url;
95 104 });
105
106 function getIdentNode(n){
107 //iterate thru nodes untill matched interesting node !
108
109 if (typeof n == 'undefined'){
110 return -1
111 }
112
113 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
114 return n
115 }
116 else{
117 return getIdentNode(n.parentNode);
118 }
119 }
120
121 function getSelectionLink() {
122 //get selection from start/to nodes
123 if (typeof window.getSelection != "undefined") {
124 s = window.getSelection();
125
126 from = getIdentNode(s.anchorNode);
127 till = getIdentNode(s.focusNode);
128
129 f_int = parseInt(from.id.replace('L',''));
130 t_int = parseInt(till.id.replace('L',''));
131
132 if (f_int > t_int){
133 //highlight from bottom
134 offset = -35;
135 }
136 else{
137 //highligth from top
138 offset = 35;
139 }
140
141 ranges = [f_int,t_int].sort();
142
143 if (ranges[0] != ranges[1]){
144 if(YUD.get('linktt') == null){
145 hl_div = document.createElement('div');
146 hl_div.id = 'linktt';
147 }
148 anchor = '#L'+ranges[0]+'-'+ranges[1];
149 hl_div.innerHTML = '';
150 l = document.createElement('a');
151 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
152 l.innerHTML = "${_('Selection link')}"
153 hl_div.appendChild(l);
154
155 YUD.get('body').appendChild(hl_div);
156
157 xy = YUD.getXY(till.id);
158
159 YUD.addClass('linktt','yui-tt');
160 YUD.setStyle('linktt','top',xy[1]+offset+'px');
161 YUD.setStyle('linktt','left',xy[0]+'px');
162 YUD.setStyle('linktt','visibility','visible');
163 }
164 else{
165 YUD.setStyle('linktt','visibility','hidden');
166 }
167 }
168 }
169
170 YUE.on('hlcode','mouseup',getSelectionLink)
171
96 172 });
97 173 </script> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now