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